srfc logo

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.

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.

.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 or aria-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.

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&amp;layer=mapnik&amp;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