Sections
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
Section titled ClassesClass | 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. |
JavaScript
Section titled JavaScriptAttributes
Section titled AttributesAttribute | 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 |
Events
Section titled EventsEvent | 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. |
Helpers
Section titled HelpersFunction | 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 |
Accessibility
Section titled Accessibility
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) |
Examples
Section titled ExamplesYou 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__primary" 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__primary" 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"));
});
Danger state
Section titled Danger stateNot 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>
Celebratory
Section titled CelebratorySometimes 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__primary" 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>
Sizes
Section titled SizesMost 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 |