Srfc - Minimal JavaScript Plus CSS Library for Modal UI Components
Docs
Srfc is a JavaScript plus CSS library.
- No dependencies.
- Small size. Just a few hundred lines of code.
- Minimal Javascript API
Note: This project is in early development.
Features
- Responsive.
- Multiple size, position, width, style, and animation options.
- Optional header and footer elements.
- If the modal's content is too long for the screen, it becomes scrollable inside the boundaries of the modal.
- Clicking on the modal "backdrop" closes the modal.
- Pressing Escape closes the modal.
- Only one modal can be open at a time. It does not support the stacking of modals. If an "A" modal gets opened while a "B" modal is still up, "B" gets closed.
- Linked modal functionality. Linked modals modify the URL (hash location) when they are opened or closed. It makes them respond to the browser's back and forward buttons. It also improves the URL sharing experience.
- Autofocus attribute that you can put on an element that should receive focus when the modal opens.
- Open and close events that you can listen to.
Accessibility...
- Aims to follow WAI-ARIA guidelines for modal dialogs.
- Focus is trapped inside the modal.
- Scrolling of the page content is blocked while a modal is open.
- Arrow keys can be used to switch to the previous/next modal in the group.
Many different modal styles are possible...
- Classic modal dialog
- Image lightbox
- Video/embed lightbox
- Off-canvas
Usage
This project is available on NPM:
npm install srfc
Below is an example usage using a CDN.
<!doctype html>
<html>
<head>
<!-- ... head's content ... -->
<link href="https://cdn.jsdelivr.net/npm/srfc@0.9.0/dist/srfc.min.css" rel="stylesheet">
</head>
<body class="srfc-page-body">
<div class="srfc-page-container">
<!-- ... page content ... -->
<button data-srfc-open="example_modal">open example_modal</button>
</div>
<div class="srfc-container srfc-backdrop-100" data-srfc>
<div class="srfc srfc-padded" id="example_modal" role="dialog" aria-modal="true" aria-labelledby="example_modal_label">
<div class="srfc-wrap srfc-width-medium srfc-theme-default">
<div class="srfc-body">
<p id="example_modal_label">example_modal content</p>
<button data-srfc-close data-srfc-autofocus>Close</button>
</div>
</div>
</div>
</div>
<script type="module">
import { Srfc } from 'https://cdn.jsdelivr.net/npm/srfc@0.9.0/dist/srfc.min.js'
const srfc = new Srfc()
srfc.init()
</script>
</body>
</html>
Import CSS
Link to the stylesheet in the <head>
of your HTML document to load the CSS.
Import JS
Place the <script>
near the end of your page, before the closing </body>
tag. Import the Srfc
and initialize it.
The script is primarily distributed as a module. However, as an alternative, you may use the iife
script. Since it is not a module, it may work in older browsers that do not support modules. Also, you do not have to initialize it like the module. Load the script, and it will automatically run and create the srfc
variable in a global namespace.
<script src="https://cdn.jsdelivr.net/npm/srfc@0.9.0/dist/srfc-iife.min.js"></script>
Body And Its Content
Enclose whole body content expect of the modals in a container with a .srfc-page-container
class. Also, put .srfc-page-body
class on the <body>
element.
Create Modals
Now you can place modals on your page. You need to place them after the main page content.
Note that the data-srfc-open
attribute of the button needs to correspond to the id
of the modal you wish to open.
Markup Attribues
Here are the attributes to use in the markup:
data-srfc-open
An element with this attribute opens a modal on click. Its value needs to correspond to the id
attribute of the modal you wish to open.
data-srfc-close
An element with this attribute closes a modal on click. It does not need any value.
data-srfc-autofocus
Put this attribute on an element inside a modal that should receive focus when the modal opens.
Modal Container
data-srfc
This attribute marks the modal container. A modal container is supposed to contain one or more modals.
Each modal needs an id
attribute.
You can group modals in the same modal container. For example, when they are similar and make use of the same classes. It shortens the code.
It does not need any value. However there are several values that you can use to modify the behaviour of modals in the group.
Note that you can combine the following values in a space-separated manner. For example: data-srfc="linked strong arrow"
. Order is not important.
data-srfc="arrow"
Enables the use of arrow keys to switch to the previous/next modal in the group.
data-srfc="strong-esc"
Makes the Escape
keyboard button not close the modal.
data-srfc="strong-backdrop"
Makes the click on the backdrop not close the modal.
data-srfc="strong"
Same as the combination of strong-backdrop
and strong-esc
data-srfc="linked"
Modifies the URL (hash location) when the modals are opened or closed. It also makes the modals respond to the browser's "back" and "forward" buttons. So it is useful if you want to close a modal with a click of the browser's "back" button. You might find yourself desiring this functionality when browsing on a phone.
It also improves the URL sharing experience. If an URL you open has a hash location pointing to a linked modal, it will open it on page load.
Note that there is a limitation with linked modal types. Hash links (that is links point to a certain hash location, for example "<a href="#something">link</a>"
) are not allowed inside a linked modal type.
Embed Helpers
Note that you can combine the following values in a space-separated manner. For example: data-srfc-embed="fit reset"
.
data-srfc-embed="fit"
Iframes can get tricky when you want to manage their size. Especially when it comes to keeping the aspect ratio, like in the case of video embeds. Put this on an iframe
, and it will fit it to the size of the parent while also keeping the aspect ratio. However, this also requires that the iframe lives inside an element with srfc-embed-fit
.
data-srfc-embed="reset"
Put this on an iframe
inside a modal, and it will reset it once the modal is closed. So, for example, if the embed was a video and the user closes the modal, the playback would stop. If the embed was a map, it would reset it to the initial position and state.
Stylesheet
The goal of the stylesheet is to provide a useful base set of styles whether or not you use some other kind of CSS framework or stylesheets in your project. For example, typography styles are not defined. It might be that you have already done that, or you might have used some framework. In any case, it would probably need to be adjusted to fit your project style anyway.
For a similar reason, there are no shadows, rounded corners, etc. Although the provided style is minimal, it has a set of size and positioning utilities that work well for different screen sizes and lengths of content. It aims to take care of the tricky parts so that you would only need a bit of extra CSS to match the style of your project.
Testing Page shows how the default styles look when no other stylesheet is present on a page.
Note that the default modal dialog and the image lightbox are different only because of the CSS classes. There is nothing special in the script that is specific only for either of those. This library enables you to make your modal components that might look very different by only changing CSS classes.
The general structure that the markup is that a modal group is enclosed in a container that has .srfc-container
class. Inside the container, there are individual modals with their .srfc
class. Inside each of those, there is a wrap element to help position the modal with a .srfc-wrap
class.
Container Modifiers
.srfc-backdrop-100
.srfc-backdrop-200
.srfc-backdrop-300
.srfc-backdrop-400
Use these classes to modify the background color of the backdrop of a modal container. Goes from the semi-transparent to the non-transparent black background color.
Modal Modifiers
.srfc-padded
.srfc-padded-left
.srfc-padded-right
.srfc-padded-top
.srfc-padded-bottom
Use these optional classes to modify the padded space around a modal. For example, you would use a .srfc-padded
class to put spacing on each side around a modal. On smaller screens, it gives a user some space to click on the backdrop of the modal.
.srfc-padded-*
are useful for off-canvas style modals when a modal is snapped to the edge of the screen. See how to use it in the off-canvas example.
Wrap Modifiers
.srfc-snap-left
.srfc-snap-right
.srfc-snap-top
.srfc-snap-bottom
.srfc-snap-all
.srfc-width-small
.srfc-width-medium
.srfc-width-large
.srfc-width-xlarge
Use these classes to change the position of the modal and its width. Width goes from 300px (small) to 1200px (xlarge).
.srfc-theme-default
.srfc-theme-on-dark
The .srfc-theme-default
theme is simply a white background. .srfc-theme-on-dark
gives white color to the text. It is useful when the backdrop is darker, for example, in a lightbox-style modal.
Out of Wrap
.srfc-out-of-wrap
To put some content outside of the wrap, put it in an element with the above class.
.srfc-x
.srfc-next
.srfc-prev
Inside the "out of wrap" you might have some button controls like an "x" button or "prev" / "next" buttons on the edges of the screen.
Animations
.srfc-scale-fade
.srfc-fade
.srfc-slide-left
.srfc-slide-right
.srfc-slide-bottom
.srfc-slide-top
Use these on the elements that should be animated.
Other classes
.srfc-page-body
.srfc-page-container
Use these classes to manage the scrollbar and prevention of scrolling when a modal is open. Follow the structure described in the usage section, and don't forget to enclose the body content.
.srfc-header
.srfc-body
.srfc-body-expand
.srfc-footer
Use these classes to manage the content of the modal. Use the .srfc-body-expand
alternatively to the .srfc-body
if the modal should expand its height even if the content is not so tall. It is useful in some cases inside a wrap with the .srfc-snap-all
class.
.srfc-header-content
.srfc-title
.srfc-header-x
These classes help to put the "x" at the top right corner of the modal. See more examples of use in the "modal dialog" example.
.srfc-image
Responsively fit the image inside an element.
.srfc-embed-expand
Expand the iframe
inside this element to its parent.
.srfc-embed-fit
Use this class along with the data-srfc-embed-fit
attribute on the iframe
to fit an embed inside an element while also keeping the aspect ratio.
.srfc-pass
This class might be useful when you want to let the click on an element pass down to the backdrop and thus trigger the closing of the modal.
.srfc-p
.srfc-p-x
Use these spacing classes on the body, header, or footer elements.
Script
The Srfc
class allows you to control the behavior of modals from your script, like to programmatically open or close a modal.
Srfc Class
Srfc()
Constructor. Creates a new Srfc
object instance.
init()
Adds click events for all openers and closers on the page.
open(id: string)
Opens a modal. Takes an id
attribute's value of the modal element you wish to open.
close()
Closes a modal.
Events
Srfc exposes open.srfc
and close.srfc
events for hooking into modal functionality. These events are fired on the modal element.
document.getElementById("my_modal").addEventListener("open.srfc", () => {
console.log("modal was open")
})
Accessibility
This project follows the WAI-ARIA guidelines for modal dialogs.
Some of the things are automatically taken care of:
- Keyboard interaction is taken care of. The focus trap is always on, and the
Escape
key closes the modal by default. - When a dialog closes, focus returns to the element that invoked the dialog.
However, you might need to remember to do these things yourself:
- Guidelines require you to moves focus to an element contained in the dialog. You can use the
data-srfc-autofocus
attribute to have that functionality. - Modal elements are required to have
role="dialog"
attribute. - Modal elements are required to have
aria-modal="true"
attribute. - Modal elements are required to have
aria-labelledby
oraria-label
attributes (read more about these in the guideline). - Optionally, modal elements can have the
aria-describedby
property
Examples
Note that these examples are styled with CSS (buttons, typography) to fit the style of this website.
Modal Dialog
Basic modal dialog featuring a header and a footer. Feel free to remove the header or the footer if not needed.
<div class="srfc-container srfc-backdrop-100" data-srfc>
<div class="srfc srfc-padded" id="example_modal" role="dialog" aria-modal="true" aria-labelledby="example_modal_label" aria-describedby="example_modal_desc">
<div class="srfc-wrap srfc-width-medium srfc-theme-default srfc-scale-fade">
<div class="srfc-header srfc-p">
<div class="srfc-header-content">
<h2 id="example_modal_label">This is a Modal</h2>
</div>
<button class="srfc-header-x" data-srfc-close aria-label="Close"></button>
</div>
<div class="srfc-body srfc-p-x" id="example_modal_desc">
<p>Body text goes here.</p>
</div>
<div class="srfc-footer srfc-p">
<button data-srfc-close data-srfc-autofocus>Cancel</button>
<button>Action</button>
</div>
</div>
</div>
</div>
Image Lightbox
Lightbox style image view.
<div class="srfc-container srfc-backdrop-300" data-srfc="arrow">
<div class="srfc" id="example_lighbox_1" role="dialog" aria-modal="true" aria-label="alt text of image 1">
<div class="srfc-out-of-wrap">
<button class="srfc-x" data-srfc-close data-srfc-autofocus aria-label="Close"></button>
<button class="srfc-prev" disabled aria-label="Previous"></button>
<button class="srfc-next" data-srfc-open="example_lighbox_2" aria-label="Next"></button>
</div>
<div class="srfc-wrap srfc-snap-all srfc-pass">
<div class="srfc-body-expand srfc-pass srfc-scale-fade">
<img class="srfc-image" src="/srfc/img/img_1.jpg" alt="Alt text of image 1" width="1800" height="2400">
</div>
</div>
</div>
<div class="srfc" id="example_lighbox_2" role="dialog" aria-modal="true" aria-label="alt text of image 2">
<div class="srfc-out-of-wrap">
<button class="srfc-x" data-srfc-close data-srfc-autofocus aria-label="Close"></button>
<button class="srfc-prev" data-srfc-open="example_lighbox_1" aria-label="Previous"></button>
<button class="srfc-next" data-srfc-open="example_lighbox_3" aria-label="Next"></button>
</div>
<div class="srfc-wrap srfc-snap-all srfc-pass">
<div class="srfc-body-expand srfc-pass srfc-scale-fade">
<img class="srfc-image" src="/srfc/img/img_2.jpg" alt="Alt text of image 2" width="2400" height="1589">
</div>
</div>
</div>
<div class="srfc" id="example_lighbox_3" role="dialog" aria-modal="true" aria-label="alt text of image 3">
<div class="srfc-out-of-wrap">
<button class="srfc-x" data-srfc-close data-srfc-autofocus aria-label="Close"></button>
<button class="srfc-prev" data-srfc-open="example_lighbox_2" aria-label="Previous"></button>
<button class="srfc-next" disabled aria-label="Next"></button>
</div>
<div class="srfc-wrap srfc-snap-all srfc-pass">
<div class="srfc-body-expand srfc-pass srfc-scale-fade">
<img class="srfc-image" src="/srfc/img/img_3.jpg" alt="Alt text of image 3" width="1591" height="2400">
</div>
</div>
</div>
</div>
Image Lightbox Rich
Lightbox style image view featuring a header and a footer, which might be used for descriptions, extra buttons, etc.
<div class="srfc-container srfc-backdrop-400" data-srfc="arrow">
<div class="srfc" id="example_lighbox_rich_1" role="dialog" aria-modal="true" aria-label="alt text of image 1">
<div class="srfc-out-of-wrap">
<button class="srfc-x" data-srfc-close data-srfc-autofocus aria-label="Close"></button>
<button class="srfc-prev" disabled aria-label="Previous"></button>
<button class="srfc-next" data-srfc-open="example_lighbox_rich_2" aria-label="Next"></button>
</div>
<div class="srfc-wrap srfc-snap-all srfc-pass srfc-theme-on-dark">
<div class="srfc-header srfc-p">
<div>1/3</div>
</div>
<div class="srfc-body-expand srfc-pass srfc-scale-fade">
<img class="srfc-image" src="/srfc/img/img_1.jpg" alt="Alt text of image 1" width="1800" height="2400">
</div>
<div class="srfc-footer srfc-p">
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit.</p>
</div>
</div>
</div>
<div class="srfc" id="example_lighbox_rich_2" role="dialog" aria-modal="true" aria-label="alt text of image 2">
<div class="srfc-out-of-wrap">
<button class="srfc-x" data-srfc-close data-srfc-autofocus aria-label="Close"></button>
<button class="srfc-prev" data-srfc-open="example_lighbox_rich_1" aria-label="Previous"></button>
<button class="srfc-next" data-srfc-open="example_lighbox_rich_3" aria-label="Next"></button>
</div>
<div class="srfc-wrap srfc-snap-all srfc-pass srfc-theme-on-dark">
<div class="srfc-header srfc-p">
<div>2/3</div>
</div>
<div class="srfc-body-expand srfc-pass srfc-scale-fade">
<img class="srfc-image" src="/srfc/img/img_2.jpg" alt="Alt text of image 2" width="2400" height="1589">
</div>
<div class="srfc-footer srfc-p">
<p>Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</p>
</div>
</div>
</div>
<div class="srfc" id="example_lighbox_rich_3" role="dialog" aria-modal="true" aria-label="alt text of image 3">
<div class="srfc-out-of-wrap">
<button class="srfc-x" data-srfc-close data-srfc-autofocus aria-label="Close"></button>
<button class="srfc-prev" data-srfc-open="example_lighbox_rich_2" aria-label="Previous"></button>
<button class="srfc-next" disabled aria-label="Next"></button>
</div>
<div class="srfc-wrap srfc-snap-all srfc-pass srfc-theme-on-dark">
<div class="srfc-header srfc-p">
<div>3/3</div>
</div>
<div class="srfc-body-expand srfc-pass srfc-scale-fade">
<img class="srfc-image" src="/srfc/img/img_3.jpg" alt="Alt text of image 3" width="1591" height="2400">
</div>
<div class="srfc-footer srfc-p">
<p>Sit amet nisl purus in mollis.</p>
</div>
</div>
</div>
</div>
Video Embed
Youtube video embed example.
<div class="srfc-container srfc-backdrop-300" data-srfc>
<div class="srfc srfc-padded" id="example_embed_video" role="dialog" aria-modal="true" aria-label="YouTube video player">
<div class="srfc-out-of-wrap">
<button class="srfc-x" data-srfc-close data-srfc-autofocus aria-label="Close"></button>
</div>
<div class="srfc-wrap srfc-snap-all srfc-width-xlarge srfc-pass srfc-theme-on-dark">
<div class="srfc-body-expand srfc-pass srfc-scale-fade">
<div class="srfc-embed-fit">
<iframe data-srfc-embed="fit reset" width="560" height="315" src="https://www.youtube.com/embed/wgcfwWw7Mj4" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
</div>
</div>
</div>
</div>
</div>
Map Embed
OpenStreetMap embed example.
<div class="srfc-container srfc-backdrop-200" data-srfc>
<div class="srfc srfc-padded" id="example_embed_map" role="dialog" aria-modal="true" aria-label="Our location">
<div class="srfc-out-of-wrap">
<button class="srfc-x" data-srfc-close data-srfc-autofocus aria-label="Close"></button>
</div>
<div class="srfc-wrap srfc-snap-all srfc-scale-fade">
<div class="srfc-body-expand">
<div class="srfc-embed-expand">
<iframe data-srfc-embed="reset" width="425" height="350" frameborder="0" scrolling="no" marginheight="0" marginwidth="0" src="https://www.openstreetmap.org/export/embed.html?bbox=-155.09302318096164%2C19.722827506933665%2C-155.0840646028519%2C19.727372190942855&layer=mapnik&marker=19.725099865094492%2C-155.08854389190674"></iframe>
</div>
</div>
</div>
</div>
</div>
Off-Canvas
This example is snapped to the right edge, but it can be repositioned to any other edge of the screen if you use different classes.
<div class="srfc-container srfc-backdrop-100" data-srfc>
<div class="srfc srfc-padded-left" id="example_off_canvas" role="dialog" aria-modal="true" aria-labelledby="example_off_canvas_label" aria-describedby="example_off_canvas_desc">
<div class="srfc-wrap srfc-snap-right srfc-width-small srfc-slide-right srfc-theme-default">
<div class="srfc-header srfc-p">
<div class="srfc-header-content">
<h2 class="srfc-title" id="example_off_canvas_label">Menu</h2>
</div>
<button class="srfc-header-x" data-srfc-close data-srfc-autofocus aria-label="Close"></button>
</div>
<div class="srfc-body srfc-p-x" id="example_off_canvas_desc">
<p>Navigation menu would go here ...</p>
</div>
</div>
</div>
</div>
Strong Modal
This modal won't close if you press Escape
or click the backdrop. That's because it is a "strong" modal.
Strong modal types have a data-srfc="strong"
attribute set. Alternatively, you can use strong-esc
or strong-backdrop
to use only part of this functionality.
<div class="srfc-container srfc-backdrop-100" data-srfc="strong">
<div class="srfc srfc-padded" id="example_strong" role="dialog" aria-modal="true" aria-labelledby="example_strong_label" aria-describedby="example_strong_desc">
<div class="srfc-wrap srfc-width-small srfc-theme-default srfc-scale-fade">
<div class="srfc-header srfc-p">
<div class="srfc-header-content">
<h2 id="example_strong_label">Strong Modal</h2>
</div>
<button class="srfc-header-x" data-srfc-close aria-label="Close"></button>
</div>
<div class="srfc-body srfc-p-x" id="example_strong_desc">
<p>Body text goes here.</p>
</div>
<div class="srfc-footer srfc-p">
<button data-srfc-close data-srfc-autofocus>Cancel</button>
</div>
</div>
</div>
</div>
Linked Modal
Notice how the URL changes when the modal is opened or closed. Also, try to click the browser's "back" button when you open it.
What happens if you open the modal, copy the URL, and open it in a new tab?
This functionality is brought by the data-srfc="linked"
attribute.
<div class="srfc-container srfc-backdrop-100" data-srfc="linked">
<div class="srfc srfc-padded" id="example_linked" role="dialog" aria-modal="true" aria-labelledby="example_linked_label" aria-describedby="example_linked_desc">
<div class="srfc-wrap srfc-width-small srfc-theme-default srfc-scale-fade">
<div class="srfc-header srfc-p">
<div class="srfc-header-content">
<h2 id="example_linked_label">Linked Modal</h2>
</div>
<button class="srfc-header-x" data-srfc-close aria-label="Close"></button>
</div>
<div class="srfc-body srfc-p-x" id="example_linked_desc">
<p>Body text goes here.</p>
</div>
<div class="srfc-footer srfc-p">
<button data-srfc-close data-srfc-autofocus>Cancel</button>
</div>
</div>
</div>
</div>
Around The Web
- WAI-ARIA Authoring Practices - Dialog (Modal) - accessibility guidelines
- Nielsen Norman Group - Modal & Nonmodal Dialogs - definitions and discussion about the correct use of modal dialogs
- Nielsen Norman Group - Cancel vs Close - using an X icon, canceling and closing a modal
Links
- GitHub Project
- Testing Page - see more examples here