The modal system is built using the native HTML <dialog> element and enhanced with CSS for styling and animations. It’s designed to be highly composable, allowing you to build modals ranging from simple to complex by combining various features.
Default Behavior
The default modal implementation centers in the viewport on desktop and slides up from the bottom on mobile devices. It includes a semi-transparent, blurred backdrop and can be closed with the ESC key. All modals are fully accessible with ARIA attributes and support both mouse and keyboard navigation.
Mobile Implementation
On mobile devices (screens below 640px), modals slide up from the bottom and take full width of the viewport. They feature reduced padding, and hero images adjust to a top-banner layout.
Modal Construction
Complete Modal Example
Here’s an example that combines multiple features into a complete modal. This demonstrates how the modal system’s composable nature allows you to build complex interfaces by combining simple components. This example includes a hero image with responsive layout, a close button in the control bar, structured content with title and body, primary and secondary actions, and supports backdrop click-to-close.
<button class="skin-primary-emphasised interactive btn" onclick="complexModal.showModal()" aria-haspopup="dialog" aria-controls="complexModal"> Open Complex Modal</button><dialog id="complexModal" class="modal-dialog" role="dialog" aria-modal="true" aria-labelledby="complex-modal-title" aria-describedby="complex-modal-body"> <form method="dialog" class="modal-backdrop" aria-hidden="true"> <button aria-hidden="true" aria-label="Close Modal" tabindex="-1"></button> </form> <div class="skin-modal modal-container modal-container--with-hero"> <form method="dialog" class="modal-header"> <button class="modal-close"> <span>Close</span> <span aria-hidden="true">✕</span> </button> </form> <div class="modal-hero"> <img class="modal-hero-image" src="https://picsum.photos/id/558/1920/1080" alt="Hero image" /> </div> <div> <h3 id="complex-modal-title" class="text-lg">Modal with Hero Image</h3> <div id="complex-modal-body" class="text-body py-4"> <p> This modal features a hero image on the left side, content, and a backdrop click away </p> </div> <button type="button" class="skin-primary-emphasised interactive btn full-width" > Primary CTA </button> </div> </div></dialog>
<button class="skin-primary-emphasised interactive btn" onclick="complexModal.showModal()" aria-haspopup="dialog" aria-controls="complexModal"> Open Complex Modal</button><dialog id="complexModal" class="modal-dialog" role="dialog" aria-modal="true" aria-labelledby="complex-modal-title" aria-describedby="complex-modal-body"> <form method="dialog" class="modal-backdrop" aria-hidden="true"> <button aria-hidden="true" aria-label="Close Modal" tabindex="-1"></button> </form> <div class="skin-modal modal-container modal-container--with-hero"> <form method="dialog" class="modal-header"> <button class="modal-close"> <span>Close</span> <span aria-hidden="true">✕</span> </button> </form> <div class="modal-hero"> <img class="modal-hero-image" src="https://picsum.photos/id/558/1920/1080" alt="Hero image" /> </div> <div> <h3 id="complex-modal-title" class="text-lg">Modal with Hero Image</h3> <div id="complex-modal-body" class="text-body py-4"> <p> This modal features a hero image on the left side, content, and a backdrop click away </p> </div> <button type="button" class="skin-primary-emphasised interactive btn full-width" > Primary CTA </button> </div> </div></dialog>
Basic Modal
The simplest implementation of a modal includes basic container styling, a backdrop with blur effect, ESC key to close, and default padding and margins.
<button class="skin-primary-emphasised interactive btn" onclick="basicModal.showModal()" aria-haspopup="dialog" aria-controls="basicModal"> Open Basic Modal</button><dialog id="basicModal" class="modal-dialog" role="dialog" aria-modal="true" aria-label="Basic Modal"> <div class="skin-modal modal-container"> <form method="dialog" class="modal-header"> <button class="modal-close interactive-foreground"> <span>Close</span> <span aria-hidden="true">✕</span> </button> </form> <div> <div id="basic-modal-content" class="text-body"> Click the close button or press ESC to close </div> </div> </div></dialog>
<button class="skin-primary-emphasised interactive btn" onclick="basicModal.showModal()" aria-haspopup="dialog" aria-controls="basicModal"> Open Basic Modal</button><dialog id="basicModal" class="modal-dialog" role="dialog" aria-modal="true" aria-label="Basic Modal"> <div class="skin-modal modal-container"> <form method="dialog" class="modal-header"> <button class="modal-close interactive-foreground"> <span>Close</span> <span aria-hidden="true">✕</span> </button> </form> <div> <div id="basic-modal-content" class="text-body"> Click the close button or press ESC to close </div> </div> </div></dialog>
The styling of the base modal is defined by the following variables. As with all components, they will use these default values, but can be redefined within your main css file.
Variable
Default Value
Description
--modal-min-width
320px
Minimum width of the modal container
--modal-margin
2rem
Margin around the modal on desktop
--modal-padding
2rem
Internal padding of the modal content
--modal-mobile-padding
1rem
Internal padding on mobile devices
--modal-mobile-margin
0rem
Margin around the modal on mobile
--modal-hero-width
18rem
Width of hero image section on desktop
--modal-scale
0.95
Initial scale of modal for animation
--modal-mobile-hero-height
150px
Height of hero image on mobile devices
--modal-mobile-max-height
90vh
Maximum height of modal on mobile
--modal-backdrop-blur
4px
Blur effect intensity for the backdrop
--modal-backdrop-color
rgba(0, 0, 0, 0.3)
Color and opacity of the modal backdrop
--modal-animation-duration
var(--duration-speed-slow)
Duration of modal animations
--modal-radius
var(--radius-site)
Border radius of the modal
--modal-box-shadow
var(--shadow-site)
Shadow effect for the modal box
--modal-mobile-shadow
var(--shadow-site)
Shadow effect for the modal box on mobile
Disable Backdrop
To remove the backdrop blur and dimming effects, add the modal-disable-backdrop class to the dialog element alongside the modal class.
<button class="skin-primary-emphasised interactive btn" onclick="noBackdropModal.showModal()" aria-haspopup="dialog" aria-controls="noBackdropModal"> Open Modal with no Backdrop</button><dialog id="noBackdropModal" class="modal-dialog modal-disable-backdrop" role="dialog" aria-modal="true" aria-label="No Backdrop Modal"> <div class="skin-modal modal-container"> <form method="dialog" class="modal-header"> <button class="modal-close"> <span>Close</span> <span aria-hidden="true">✕</span> </button> </form> <div> <div class="text-body">This modal can be closed with ESC key</div> </div> </div></dialog>
<button class="skin-primary-emphasised interactive btn" onclick="noBackdropModal.showModal()" aria-haspopup="dialog" aria-controls="noBackdropModal"> Open Modal with no Backdrop</button><dialog id="noBackdropModal" class="modal-dialog modal-disable-backdrop" role="dialog" aria-modal="true" aria-label="No Backdrop Modal"> <div class="skin-modal modal-container"> <form method="dialog" class="modal-header"> <button class="modal-close"> <span>Close</span> <span aria-hidden="true">✕</span> </button> </form> <div> <div class="text-body">This modal can be closed with ESC key</div> </div> </div></dialog>
Backdrop Click Away
To enable closing the modal by clicking the backdrop, add a form element with method="dialog" and the modal-clickaway class after the modal content.
<button class="skin-primary-emphasised interactive btn" onclick="backdropModal.showModal()" aria-haspopup="dialog" aria-controls="backdropModal"> Open Modal with Backdrop Click Away</button><dialog id="backdropModal" class="modal-dialog" role="dialog" aria-modal="true" aria-labelledby="backdrop-modal-content"> <form method="dialog" class="modal-backdrop" aria-hidden="true"> <button aria-hidden="true" tabindex="-1" aria-label="Close Modal"></button> </form> <div class="skin-modal modal-container"> <form method="dialog" class="modal-header"> <button class="modal-close"> <span>Close</span> <span aria-hidden="true">✕</span> </button> </form> <div class=""> <div id="backdrop-modal-content" class="text-body"> This modal can be closed with ESC key or by clicking the background </div> </div> </div></dialog>
<button class="skin-primary-emphasised interactive btn" onclick="backdropModal.showModal()" aria-haspopup="dialog" aria-controls="backdropModal"> Open Modal with Backdrop Click Away</button><dialog id="backdropModal" class="modal-dialog" role="dialog" aria-modal="true" aria-labelledby="backdrop-modal-content"> <form method="dialog" class="modal-backdrop" aria-hidden="true"> <button aria-hidden="true" tabindex="-1" aria-label="Close Modal"></button> </form> <div class="skin-modal modal-container"> <form method="dialog" class="modal-header"> <button class="modal-close"> <span>Close</span> <span aria-hidden="true">✕</span> </button> </form> <div class=""> <div id="backdrop-modal-content" class="text-body"> This modal can be closed with ESC key or by clicking the background </div> </div> </div></dialog>
Hero Image
To create a modal with a hero image, add the modal-container--with-hero class to your modal container. Place your image with the modal-hero-image class in a separate div before your content. The layout will automatically adjust between desktop (left-aligned) and mobile (top-aligned).
The hero image is affected by the following variables:
Variable
Default Value
Description
--modal-hero-width
18rem
Width of hero image section on desktop
--modal-mobile-hero-height
150px
Height of hero image on mobile devices
Fullscreen Modals
The modal system supports fullscreen layouts for both desktop and mobile views. You can control this behavior using two classes:
modal-fullscreen: Makes the modal fill the entire viewport on desktop
modal-fullscreen-mobile: Makes the modal fill the entire viewport on mobile devices
These classes can be used independently or together for different responsive behaviors.
<button class="skin-primary-emphasised interactive btn" onclick="fullscreenModal.showModal()" aria-haspopup="dialog" aria-controls="fullscreenModal"> Open Fullscreen Modal</button><dialog id="fullscreenModal" class="modal-dialog modal-fullscreen modal-fullscreen-mobile" role="dialog" aria-modal="true" aria-label="Fullscreen Modal"> <div class="skin-modal modal-container"> <form method="dialog" class="modal-header"> <button class="modal-close"> <span>Close</span> <span aria-hidden="true">✕</span> </button> </form> <div> <div class="text-body"> <h1>This modal is fullscreen on both desktop and mobile</h1> </div> </div> </div></dialog>
<button class="skin-primary-emphasised interactive btn" onclick="fullscreenModal.showModal()" aria-haspopup="dialog" aria-controls="fullscreenModal"> Open Fullscreen Modal</button><dialog id="fullscreenModal" class="modal-dialog modal-fullscreen modal-fullscreen-mobile" role="dialog" aria-modal="true" aria-label="Fullscreen Modal"> <div class="skin-modal modal-container"> <form method="dialog" class="modal-header"> <button class="modal-close"> <span>Close</span> <span aria-hidden="true">✕</span> </button> </form> <div> <div class="text-body"> <h1>This modal is fullscreen on both desktop and mobile</h1> </div> </div> </div></dialog>
Disable Entrance animation
By applying the modal-disable-entrance class, the pop up / slide in animations are disabled for desktop and mobile respectively.
<button class="skin-primary-emphasised interactive btn" onclick="noEntranceModal.showModal()" aria-haspopup="dialog" aria-controls="noEntranceModal"> Open Modal Without Entrance Animation</button><dialog id="noEntranceModal" class="modal-dialog modal-disable-entrance" role="dialog" aria-modal="true" aria-label="No Entrance Animation Modal"> <div class="skin-modal modal-container modal"> <form method="dialog" class="modal-header"> <button class="modal-close"> <span>Close</span> <span aria-hidden="true">✕</span> </button> </form> <div> <div class="text-body"> This modal simply fades in and out, with no pop-up or slide-in animations. </div> </div> </div></dialog>
<button class="skin-primary-emphasised interactive btn" onclick="noEntranceModal.showModal()" aria-haspopup="dialog" aria-controls="noEntranceModal"> Open Modal Without Entrance Animation</button><dialog id="noEntranceModal" class="modal-dialog modal-disable-entrance" role="dialog" aria-modal="true" aria-label="No Entrance Animation Modal"> <div class="skin-modal modal-container modal"> <form method="dialog" class="modal-header"> <button class="modal-close"> <span>Close</span> <span aria-hidden="true">✕</span> </button> </form> <div> <div class="text-body"> This modal simply fades in and out, with no pop-up or slide-in animations. </div> </div> </div></dialog>
Accessibility
The modal system is built following the WAI-ARIA Modal Dialog Pattern, implementing key accessibility features through semantic HTML and ARIA attributes.
Current Implementation Features
Structure and Labeling
Each modal includes:
The role="dialog" attribute to identify it as a modal dialog
aria-modal="true" to indicate it’s a modal context
aria-label to provide an accessible name for the modal
aria-controls on trigger buttons to associate them with their respective modals
Keyboard Support
The native HTML <dialog> element provides:
ESC key to close the modal
Tab key to navigate through focusable elements
Automatic focus management within the modal
Screen Reader Support
Each modal variant includes:
Clear labeling of all interactive elements
Proper content organization
Hidden decorative elements using aria-hidden="true"
Descriptive button labels for actions and close buttons
Mobile Accessibility
For mobile devices:
Clear close mechanisms (either close button or backdrop)
Touch-friendly target sizes
Maintained content structure
Required Implementation Steps
When implementing modals in your project, ensure you:
Content Structure
Use semantic HTML elements for content structure
Include descriptive text for all buttons and interactive elements
Maintain proper focus management
Keyboard Navigation & focus
Javascript must be used to entirely trap the focus within the modal. In some browsers such as chrome, one the user has reached the end of the modal, tabbing again will move focus to the browsers toolbar. In order to make the modal as accessible as possible, it should ignore the tool bar and go back to the start of the modals index.
ARIA Attributes
Add appropriate aria-label value to the dialog element
Include aria-controls on trigger buttons
Add descriptive aria-label values for action buttons