Modals

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.

Classes

ClassDescriptionParentModifies
.s-modalBase parent container for modals.N/AN/A
.s-modal--dialogCreates a container that holds the modal dialog with proper padding and shadows..s-modalN/A
.s-modal--bodyAdds proper styling to the modal dialog's body text..s-modal--dialogN/A
.s-modal--closeUsed to dismiss a modal..s-modal--dialogN/A
.s-modal--headerAdds proper styling to the modal dialog's header..s-modal--dialogN/A
.s-modal--footerAdds the desired spacing to the row of button actions..s-modal--dialogN/A
.s-modal__dangerAdds styling for potentially dangerous actions.N/A.s-modal
.s-modal__fullMakes the container take up as much of the screen as possible.N/A.s-modal--dialog

JavaScript

Attributes

AttributeDescriptionApplies to
data-controller="s-modal"Wires up the element to the modal controller. This may be a `.s-modal` element or a wrapper element.Controller element
data-s-modal-target="modal"Wires up the element that is to be shown/hidden..s-modal element
data-s-modal-target="initialFocus"Designates which element to focus on modal show. If absent, defaults to the first focusable element within the modal.Any child focusable element
data-action="s-modal#toggle"Wires up the element that is to be shown/hidden.Any child focusable element
data-action="s-modal#hide"Wires up the element that is to be shown/hidden.Any child focusable element
data-s-modal-return-element="[selector]"Designates the element to return focus to when the modal is closed. If left unset, focus is not altered on close.Controller element
data-s-modal-remove-when-hidden="true"Removes the modal from the DOM entirely when it is hidden.Controller element

Events

EventDescriptionApplies to
s-modal:showFires immediately before showing the modal. Calling `.preventDefault()` cancels the display of the modal.Modal target
s-modal:shownFires after the modal has been visually shown.Modal target
s-modal:hideFires immediately before hiding the modal. Calling `.preventDefault()` cancels the removal of the modal.Modal target
s-modal:hiddenFires after the modal has been visually hidden.Modal target

Event details

PropertyDescriptionApplies to
dispatcherContains 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.Modal target
returnElementContains 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.Modal target

Helpers

HelperDescriptionApplies to
Stacks.showModalHelper to manually show an s-modal element via external JS.Controller element
Stacks.hideModalHelper to manually hide an s-modal element via external JS.Controller element

Accessibility

AttributeDescriptionApplies to
aria-describedby="[id]"Supply the modal's summary copy id. Assistive technologies use this to associate static text with a widget, element groups, headings, definitions, etc.Modal target
aria-hidden="[state]"Informs assistive technologies if they should ignore the element. This should not be confused with the HTML5 `hidden` attribute.Modal target
aria-label="[text]"Labels the element for assistive technologies.Modal target
aria-labelledby="[id]"Supply the modal's title id here. Assistive technologies use this to catalog the document objects correctly.Modal target
role="dialog"Identifies dialog elements for assistive technologies.Modal target
role="document"Helps assistive technologies to switch their reading mode from the larger document to a focused dialog window.Modal target

Examples

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="s-btn" type="button"></button>
                <button class="s-btn s-btn__danger" type="button" data-action="s-modal#hide"></button>
            </div>
            <button class="s-modal--close s-btn s-btn__clear" type="button" aria-label="Close" data-action="s-modal#hide">
                @Svg.Cross
            </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"></aside>
document.querySelector(".js-modal-toggle").addEventListener("click", function(e) {
    Stacks.showModal(document.querySelector("#modal-base"));
});

Danger state

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, 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="s-btn s-btn__danger" type="button"></button>
            <button class="s-btn s-btn__clear" type="button" data-action="s-modal#hide"></button>
        </div>
        <button class="s-modal--close s-btn s-btn__clear" type="button" aria-label="Close" data-action="s-modal#hide">
            @Svg.Cross
        </button>
    </div>
</aside>

Celebratory

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="s-btn" type="button"></button>
        </div>
        <button class="s-modal--close s-btn s-btn__clear" type="button" aria-label="Close" data-action="s-modal#hide">
            @Svg.Cross
        </button>
    </div>
</aside>

Sizes

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:

ClassValue
.s-modal__full100% - 48px