Modal
Overview
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.
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.
Example Usage
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">
<p>Open Complex Modal
</p>
</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>
<p>This modal features a hero image on the left side, content, and a
backdrop click away
</p>
</p>
</div>
<button type="button" class="skin-primary-emphasised interactive btn full-width">
<p>Primary CTA
</p>
</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">
<p>Open Basic Modal
</p>
</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 class="modal-content">
<div id="basic-modal-content" class="text-body">
<p>Click the close button or press ESC to close
</p>
</div>
</div>
</div>
</dialog>
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">
<p>Open Modal with no Backdrop
</p>
</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">
<p>Open Modal with Backdrop Click Away
</p>
</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">
<p>This modal can be closed with ESC key or by clicking the background
</p>
</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).
<button class="skin-primary-emphasised interactive btn" onclick="heroModal.showModal()" aria-haspopup="dialog" aria-controls="heroModal">
<p>Open Modal with Hero Image
</p>
</button>
<dialog id="heroModal" class="modal-dialog" role="dialog" aria-modal="true" aria-label="Hero Modal">
<div class="skin-modal modal-container modal-container--with-hero modal">
<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>
<div class="text-body">This modal can be closed with ESC key
</div>
</div>
</div>
</dialog>
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 desktopmodal-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">
<p>Open Fullscreen Modal
</p>
</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">
<p>Open Modal Without Entrance Animation
</p>
</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">
<p>This modal simply fades in and out, with no pop-up or slide-in
animations.
</p>
</div>
</div>
</div>
</dialog>
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.
CSS Variables
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 |
Hero Image Variables
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 |
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 contextaria-labelto provide an accessible name for the modalaria-controlson 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
JavaScript Functionality
When implementing modals in your project, several JavaScript features are necessary for full accessibility and functionality:
Keyboard Navigation & Focus
- JavaScript must be used to entirely trap the focus within the modal
- In some browsers such as Chrome, when the user reaches the end of the modal, tabbing again will move focus to the browser’s toolbar
- To make the modal fully accessible, focus should be trapped within the modal, ignoring the toolbar and returning to the start of the modal’s focusable elements
Content Structure
- Use semantic HTML elements for content structure
- Include descriptive text for all buttons and interactive elements
- Maintain proper focus management
ARIA Attributes
- Add appropriate
aria-labelvalue to the dialog element - Include
aria-controlson trigger buttons - Add descriptive
aria-labelvalues for action buttons - Use
aria-hidden="true"for decorative elements