Modal
Modals are focused overlay dialogs that interrupt the user to complete a task, confirm an action, or display contextual information without navigating away.
Overview
Every modal has three sections: header (title + close button), body (content), and footer (actions). The overlay uses backdrop-filter:blur(4px) and rgba(0,0,0,.5). The modal enters with a scale(0.95)→scale(1) animation over 0.3s. Clicking the overlay closes the modal.
Action order: Cancel/secondary action is always on the left. Primary/confirm action is always on the right. For destructive modals, the primary action is
.btn-danger.Form Modal
Click buttons above to see modals
Token Reference
| Part | Class | Value |
|---|---|---|
| Overlay bg | .modal-overlay | rgba(0,0,0,.5) + backdrop-filter:blur(4px) |
| Container | .modal | bg:#fff, max-width:500px, radius:16px, shadow-modal |
| Small | .modal.modal-sm | max-width:380px |
| Large | .modal.modal-lg | max-width:680px |
| Header | .modal-header | padding:20px 24px, border-bottom:1px solid #E2E8F0 |
| Title | .modal-title | 16px, fw-800, letter-spacing:-.3px |
| Close btn | .modal-close | ✕ character, hover:bg:#F1F5F9 |
| Body | .modal-body | padding:24px |
| Footer | .modal-footer | padding:16px 24px, border-top, flex end, gap:12px |
| Enter animation | — | scale(.95)+opacity(0) → scale(1)+opacity(1), 0.3s ease |
Accessibility
| Requirement | Implementation |
|---|---|
| Focus trap | On open, move focus to first focusable element inside modal. Tab cycles within modal only. |
| Escape key | Always closes the modal. Add keydown listener for e.key === 'Escape'. |
| Body scroll | Set document.body.style.overflow = 'hidden' on open, restore on close. |
| ARIA | Add role="dialog", aria-modal="true", aria-labelledby pointing to modal title. |
| Return focus | On close, return focus to the trigger button that opened the modal. |
AI Implementation Notes
For AI coding assistants: Modal overlay uses
display:none by default, changed to display:flex to show. The overlay itself must be the flex container — never the modal container. Clicking outside the modal (on the overlay) should close it: onclick="if(event.target===this)this.style.display='none'". Destructive modals MUST use .btn-danger for confirm, never .btn-primary. Always include a cancel button.
When to Use
Use Modal when
- Collecting form data without leaving current page
- Confirming a destructive action
- Displaying focused information that requires user response
- Multi-step workflow in a contained context
Don't use Modal when
- Content is too complex — use a dedicated page
- Showing non-critical information — use a toast
- On mobile for long forms — use a drawer or page instead
- Nesting modals inside modals — avoid modal stacking
Properties
| Property | Type | Required | Default | Description |
|---|---|---|---|---|
| open | boolean | required | false | Controls visibility. display:none → display:flex toggle. Never use visibility:hidden. |
| onClose | function | required | — | Called when user clicks overlay, presses Escape, or clicks close button. |
| size | string | optional | 'default' | Width variant. 'sm' (380px) | 'default' (500px) | 'lg' (680px) |
| title | string | required | — | Modal title in header. 16px fw-800. Used for aria-labelledby. |
| footer | ReactNode | optional | undefined | Footer content. Always Cancel (left) + Confirm (right). Use .btn-danger for destructive confirms. |
Behavior
| Trigger | Behavior |
|---|---|
| Open | display:none → display:flex. Focus moves to first focusable element. Body scroll locked (overflow:hidden). Animation: scale(.95)+opacity(0) → scale(1)+opacity(1), 0.3s ease. |
| Click overlay | Closes modal. Implemented via if(event.target===this) check. Only fires when clicking the overlay directly, not modal content. |
| Escape key | Always closes modal. Add keydown listener for e.key==='Escape'. Required for accessibility. |
| Close button | × character button in header. Calls onClose handler. |
| Close | display:flex → display:none. Focus returns to trigger element. Body scroll restored. |
Best Practices
Do
Cancel left, confirm right. Primary for safe actions.
Don't
Wrong order. Danger for non-destructive. Cancel on right.
Design Decisions
Why scale(.95) → scale(1) for the open animation?
The slight scale-up creates a sense of the modal emerging from the page rather than just appearing. It draws the eye to the center of the screen without being jarring. The 0.3s duration is long enough to feel intentional but short enough to feel snappy.
Why backdrop-filter:blur(4px) on the overlay?
The blur communicates depth — the modal is above the page. It softens the background without making it completely invisible, helping users maintain spatial context of where they were.
Changelog
v1.0.0July 2026Added
- Initial release — 3 size variants (sm, default, lg)
- backdrop-filter blur overlay with click-outside close
- Scale animation on open
- Focus trap and body scroll lock
- Escape key closes modal
States
Modal Title
Content area
CancelConfirm
Open
Overlay visible, focus trapped, body scroll locked
No overlay
Closed
display:none, focus returns to trigger
Accessibility
| Requirement | Implementation |
|---|---|
| role="dialog" | Add to .modal element. |
| aria-modal="true" | Add to .modal element. Tells screen readers this is a modal. |
| aria-labelledby | Point to the modal title element ID. |
| Focus trap | Tab and Shift+Tab cycle only within modal while open. |
| Escape key | Always closes modal. Required. |
| Return focus | On close, return focus to trigger button. |
Composition
Allowed
Form fields in modal body
Alert/callout inside modal body for warnings
Cancel + Confirm in footer (Cancel left, Confirm right)
Scrollable modal-body for long content
Forbidden
Modal inside modal (no stacking)
Primary button for destructive actions (use danger)
Modal without close button or Escape handler
Best Practices
Do
Clear hierarchy. Cancel left, confirm right.
Don't
Use a modal for content that requires full-page context or extensive scrolling.
Complex workflows belong on dedicated pages, not modals.
Related Components
Anatomy
1
Overlay
div.modal-overlay. position:fixed, inset:0, background:rgba(0,0,0,.5), backdrop-filter:blur(4px), display:flex, align-items/justify-content:center, z-index:1000.
2
Container
div.modal. bg:#fff, max-width:500px, border-radius:16px, overflow:hidden. Animation: scale(.95)+opacity(0) → scale(1)+opacity(1).
3
Header
div.modal-header. padding:20px 24px, border-bottom. Title (16px fw-800) left. Close button (×) right.
4
Body
div.modal-body. padding:24px. Contains form fields, content, or callouts.
5
Footer
div.modal-footer. padding:16px 24px, border-top, justify-content:flex-end, gap:12px. Cancel left, confirm right.
Content Rules
Good modal title
Add New User
Action + object. 16px fw-800. Specific.
Bad modal title
Form
Too vague — does not describe what is being done.
Good confirm button
Save User
Names the action and object
Bad confirm button
OK / Submit / Yes
Vague — does not describe what is confirmed