Modal
FreeA centered overlay dialog with a backdrop that blocks interaction with the page beneath until dismissed. Contains a close button, title, body content, and footer actions. Used for focused tasks that require the user's full attention — form submissions, record editing, or displaying critical information. Includes a focus trap, scroll lock on the body, and animated entrance. Available in Small, Medium, Large, and Full-screen width variants.
8 variants
Variants
| Variant | Description | When to use |
|---|---|---|
| Default | ||
| With Form | ||
| Confirmation | ||
| Full Screen |
Usage Guidelines
Trap focus within the modal while it is open.
Open a modal from within another modal — restructure the flow instead.
Return focus to the trigger element when the modal closes.
Use modals for non-blocking information — use a Toast or Banner instead.
Provide a visible close button and allow Escape to dismiss.
Remove the backdrop — users need a clear signal that the page is inactive.
Use a descriptive title that explains the modal's purpose.
Auto-open modals on page load without user initiation.
Code
<div class="lex-modal-backdrop" aria-hidden="true"></div>
<div class="lex-modal" role="dialog" aria-modal="true"
aria-labelledby="modal-title">
<header class="lex-modal__header">
<h2 id="modal-title" class="lex-modal__title">Edit profile</h2>
<button class="lex-icon-button lex-icon-button--tertiary"
aria-label="Close dialog">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none"
stroke="currentColor" stroke-width="2">
<line x1="18" y1="6" x2="6" y2="18" />
<line x1="6" y1="6" x2="18" y2="18" />
</svg>
</button>
</header>
<div class="lex-modal__body">
<p>Modal body content goes here.</p>
</div>
<footer class="lex-modal__footer">
<button class="lex-button lex-button--secondary lex-button--md">Cancel</button>
<button class="lex-button lex-button--primary lex-button--md">Save</button>
</footer>
</div>.lex-modal-backdrop {
position: fixed;
inset: 0;
background: var(--lex-modal-backdrop-bg, rgba(0, 0, 0, 0.5));
z-index: var(--lex-z-modal-backdrop, 999);
}
.lex-modal {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background: var(--lex-modal-bg, var(--lex-bg-surface-raised));
border-radius: var(--lex-modal-radius, var(--lex-radius-xl));
box-shadow: var(--lex-modal-shadow, var(--lex-shadow-xl));
width: min(560px, calc(100vw - 32px));
max-height: calc(100vh - 64px);
display: flex;
flex-direction: column;
z-index: var(--lex-z-modal, 1000);
}
.lex-modal__header {
display: flex;
align-items: center;
justify-content: space-between;
padding: var(--lex-space-16) var(--lex-space-24);
border-bottom: 1px solid var(--lex-border-default);
}
.lex-modal__title {
font-size: var(--lex-font-size-lg);
font-weight: 600;
margin: 0;
}
.lex-modal__body {
padding: var(--lex-space-24);
overflow-y: auto;
flex: 1;
}
.lex-modal__footer {
display: flex;
justify-content: flex-end;
gap: var(--lex-space-8);
padding: var(--lex-space-16) var(--lex-space-24);
border-top: 1px solid var(--lex-border-default);
}// Using Lexicon CSS classes with React
export function EditProfileModal({ open, onClose }: { open: boolean; onClose: () => void }) {
return (
<Modal open={open} onClose={onClose} size="md" title="Edit profile">
<Modal.Body>
<p>Modal body content goes here.</p>
</Modal.Body>
<Modal.Footer>
<Button variant="secondary" onClick={onClose}>Cancel</Button>
<Button variant="primary">Save</Button>
</Modal.Footer>
</Modal>
);
}Design Tokens
| Token | Value (dark) | Value (light) | CSS property |
|---|---|---|---|
| --lex-modal-bg | — | — | bg |
| --lex-modal-border | — | — | border |
| --lex-modal-radius | — | — | radius |
| --lex-modal-shadow | — | — | shadow |
| --lex-modal-padding | — | — | padding |
| --lex-modal-max-width | — | — | width |
| --lex-modal-backdrop | — | — | backdrop |
Accessibility
- Use role="dialog" with aria-modal="true" to indicate a modal context.
- Focus must be trapped inside the modal — Tab and Shift+Tab cycle within.
- On open, move focus to the first focusable element or the close button.
- On close, return focus to the element that triggered the modal.
- Provide aria-labelledby pointing to the modal title.
- The backdrop should have aria-hidden="true" — it is decorative.
- Body scroll must be locked while the modal is open.