Skip to main content

Modals are dialog overlays that prevent the user from interacting with the rest of the website until an action is taken or the dialog is dismissed. Modals are purposefully disruptive and should be used thoughtfully and sparingly.

Class Applies to Description
.s-modal N/A Base parent container for modals
.s-modal__danger .s-modal Adds styling for potentially dangerous actions
.s-modal--dialog Child of .s-modal Creates a container that holds the modal dialog with proper padding and shadows
.s-modal--header Child of .s-modal--dialog Adds proper styling to the modal dialog’s header
.s-modal--body Child of .s-modal--dialog Adds proper styling to the modal dialog’s body text
.s-modal--footer Child of .s-modal--dialog Adds the desired spacing to the row of button actions.
.s-modal--close Child of .s-modal--dialog Used to dismiss a modal
.s-modal__full .s-modal--dialog Makes the .s-modal--dialog container take up as much of the screen as possible.
Attribute Applied to Description
data-controller="s-modal" Controller element Wires up the element to the modal controller. This may be a .s-modal element or a wrapper element.
data-s-modal-target="modal" .s-modal element Wires up the element that is to be shown/hidden
data-s-modal-target="initialFocus" Any child focusable element Optional Designates which element to focus on modal show. If absent, defaults to the first focusable element within the modal.
data-action="s-modal#toggle" Toggling element Wires up the element to toggle the visibility of a modal
data-s-modal-return-element="[css selector]" Controller element Optional Designates the element to return focus to when the modal is closed. If left unset, focus is not altered on close.
data-s-modal-remove-when-hidden="true" Controller element Optional Removes the modal from the DOM entirely when it is hidden
Event Element Description
s-modal:show Modal target Default preventable Fires immediately before showing the modal. Calling .preventDefault() cancels the display of the modal.
s-modal:shown Modal target Fires after the modal has been visually shown
s-modal:hide Modal target Default preventable Fires immediately before hiding the modal. Calling .preventDefault() cancels the removal of the modal.
s-modal:hidden Modal target Fires after the modal has been visually hidden
event.detail Applicable events Description
dispatcher s-modal:* Contains the Element that initiated the event. For instance, the button clicked to show, the element clicked outside the modal that caused it to hide, etc.
returnElement s-modal:show, s-modal:hide Contains the Element to return focus to on hide. If a value is set to this property inside an event listener, it will be updated on the controller as well.
Function Parameters Description
Stacks.showModal element: the element the data-controller="s-modal" attribute is on

Helper to manually show an s-modal element via external JS
Stacks.hideModal element: the element the data-controller="s-modal" attribute is on

Helper to manually hide an s-modal element via external JS

Modals are designed with accessibility in mind by default. When a modal is open, navigation with the keyboard will be constrained to only those elements within the modal. To ensure maximum compatibility, all a tags must have href attributes and any default focusable items you don’t want focusable must have their tabindex set to -1.

Item Applied to Description
aria-describedby="[id]" .s-modal Supply the modal’s summary copy id. Assistive technologies (such as screen readers) use this to attribute to associate static text with a widget, element groups, headings, definitions, etc. (Source)
aria-hidden="[state]" .s-modal Informs assistive technologies (such as screen readers) if they should ignore the element. This should not be confused with the HTML5 hidden attribute which tells the browser to not display an element. (Source)
aria-label="[text]" .s-modal--close Labels the element for assistive technologies (such as screen readers). (Source)
aria-labelledby="[id]" .s-modal Supply the modal’s title id here. Assistive technologies (such as screen readers) use this to attribute to catalog the document objects correctly. (Source)
role="dialog" .s-modal Identifies dialog elements for assistive technologies (Source)
role="document" .s-modal--dialog Helps assistive technologies to switch their reading mode from the larger document to a focused dialog window. (Source)

You can wire up a modal along with the corresponding button by wrapping both in a s-modal controller and attaching the corresponding data-* attributes. Make sure to set data-s-modal-return-element if you want your button to refocus on close.

<div data-controller="s-modal" data-s-modal-return-element="#js-return-focus">
<button type="button" id="js-return-focus" data-action="s-modal#show">Show modal</button>
<aside class="s-modal" data-s-modal-target="modal" id="modal-base" tabindex="-1" role="dialog" aria-labelledby="modal-title" aria-describedby="modal-description" aria-hidden="true">
<div class="s-modal--dialog" role="document">
<h1 class="s-modal--header" id="modal-title"></h1>
<p class="s-modal--body" id="modal-description"></p>
<div class="d-flex gx8 s-modal--footer">
<button class="flex--item s-btn s-btn__filled" type="button"></button>
<button class="flex--item s-btn" type="button" data-action="s-modal#hide"></button>
</div>
<button class="s-modal--close s-btn s-btn__muted" type="button" aria-label="@_s("Close")" data-action="s-modal#hide">
@Svg.ClearSm
</button>
</div>
</aside>
</div>

Alternatively, you can also use the built in helper to display a modal straight from your JS file. This is useful for the times when your modal markup can’t live next to your button or if it is generated dynamically (e.g. from an AJAX call).

<button class="s-btn js-modal-toggle" type="button">Show modal</button>
<aside class="s-modal" id="modal-base" role="dialog" aria-labelledby="modal-title" aria-describedby="modal-description" aria-hidden="true"
data-controller="s-modal" data-s-modal-target="modal">

<div class="s-modal--dialog" role="document">
<h1 class="s-modal--header" id="modal-title"></h1>
<p class="s-modal--body" id="modal-description"></p>
<div class="d-flex gx8 s-modal--footer">
<button class="flex--item s-btn s-btn__filled" type="button"></button>
<button class="flex--item s-btn" type="button" data-action="s-modal#hide"></button>
</div>
<button class="s-modal--close s-btn s-btn__muted" type="button" aria-label="@_s("Close")" data-action="s-modal#hide">
@Svg.ClearSm
</button>
</div>
</aside>
document.querySelector(".js-modal-toggle").addEventListener("click", function(e) {
Stacks.showModal(document.querySelector("#modal-base"));
});

Not every modal is sunshine and rainbows. Sometimes there are potentially drastic things that could happen by hitting a confirm button in a modal—such as deleting an account. In moments like this, add the .s-modal__danger class to .s-modal. Additionally, you should switch the buttons to .s-btn__danger.s-btn__filled, since the main call to action will be destructive.

<aside class="s-modal s-modal__danger" id="modal-base" tabindex="-1" role="dialog" aria-labelledby="modal-title" aria-describedby="modal-description" aria-hidden="true"
data-controller="s-modal" data-s-modal-target="modal">

<div class="s-modal--dialog" role="document">
<h1 class="s-modal--header" id="modal-title"></h1>
<p class="s-modal--body" id="modal-description"></p>
<div class="d-flex gx8 s-modal--footer">
<button class="flex--item s-btn s-btn__filled s-btn__danger" type="button"></button>
<button class="flex--item s-btn s-btn__muted" type="button" data-action="s-modal#hide"></button>
</div>
<button class="s-modal--close s-btn s-btn__muted" type="button" aria-label="@_s("Close")" data-action="s-modal#hide">
@Svg.ClearSm
</button>
</div>
</aside>

Sometimes it’s appropriate to confirm a user’s action with some confetti. You can combine our confetti background utility with some extra spacing by adding the s-modal__celebration modifier.

<aside class="s-modal s-modal__celebration" id="modal-base" tabindex="-1" role="dialog" aria-labelledby="modal-title" aria-describedby="modal-description" aria-hidden="true" data-controller="s-modal" data-s-modal-target="modal">
<div class="s-modal--dialog" role="document">
<h1 class="s-modal--header" id="modal-title"></h1>
<p class="s-modal--body" id="modal-description"></p>
<div class="d-flex gx8 s-modal--footer">
<button class="flex--item s-btn s-btn__filled" type="button"></button>
<button class="flex--item s-btn s-btn__muted" type="button" data-action="s-modal#hide"></button>
</div>
<button class="s-modal--close s-btn s-btn__muted" type="button" aria-label="@_s("Close")" data-action="s-modal#hide">
@Svg.ClearSm
</button>
</div>
</aside>

Most modal dialogs look good by default, but may need some combination of .ws[x] or .wmx[x] classes applied to .s-modal--dialog. Additionally, the following class is available for modals:

Class Value Pixels (13px base) Pixels (15px base)
.s-modal__full 100% - 48px N/A N/A
Deploys by Netlify