On this page
Proposed CSC Standards · 0.1.0
Dialogs and modals
A modal dialog for a short decision or a focused edit.
Interrupt the page for a task that must be finished or cancelled before returning.
What this describes
When to use
- Confirming a destructive action.
- Editing one record without leaving the list.
- A short decision that needs a title and a description.
When not to use
- Showing a page of content. Navigate instead.
- A message that does not need a response. Use Alert or a toast.
Allowed variants
Standard
A centered confirm or review.
- Hierarchy
- Page overlay.
- Use in
- Short decisions.
- Restriction
- Keep Escape and a close button.
- Accessibility
- Focus is trapped.
- Responsive
- On a narrow screen the panel uses the viewport width minus padding.
Minimal
A shorter confirm.
- Hierarchy
- Narrower dialog.
- Use in
- Yes or no decisions.
- Restriction
- Still needs a title.
- Accessibility
- Same focus rules.
- Responsive
- Does not shrink below the action row.
Drawer
Edit a record beside the list.
- Hierarchy
- Side panel.
- Use in
- Record details.
- Restriction
- Do not use a drawer for a one-word confirm.
- Accessibility
- Same dialog semantics.
- Responsive
- On a narrow frame the drawer should cover the width instead of leaving a sliver of page.
Fullscreen
A workflow that needs the whole screen.
- Hierarchy
- Full viewport.
- Use in
- Multi-step tasks on small screens.
- Restriction
- Provide a way back.
- Accessibility
- Focus still starts in the dialog.
- Responsive
- This is the small-screen alternative to a cramped centered modal.
Bottom sheet
Quick actions from the bottom edge.
- Hierarchy
- Bottom panel.
- Use in
- A few actions, not a long form.
- Restriction
- Do not hide the actions below the fold.
- Accessibility
- Title and description remain.
- Responsive
- Sits on the bottom of the frame. Content scrolls inside the sheet.
Mandatory rules
Move focus into the dialog and keep it there
While the dialog is open, Tab cycles inside it and focus returns to the trigger on close.
Implementation detail
The APG dialog pattern requires focus movement and a trap. Radix Dialog does this for Modal.
Use a dialog that moves focus inside and traps Tab. Do not use a positioned div unless it does the same.
Check: Open the dialog. The title or first control is focused. Tab does not reach the page behind it.
Close on Escape unless the action is destructive
Escape closes a standard dialog. A destructive confirm does not dismiss on Escape or outside click.
Implementation detail
Accidental dismissal can cancel a warning the person has not read. The CSC destructive modal already prevents Escape and outside close.
A destructive confirm stays open on Escape and outside click. Other dialogs close on Escape.
Applies when: The dialog asks to delete or another irreversible action.
Check: Escape closes a standard dialog and does not close a destructive one.
Provide a name and a description
The dialog has Dialog.Title and Dialog.Description, which Modal already renders.
Implementation detail
aria-labelledby and aria-describedby come from those parts.
Do not remove the title. The description states what will happen.
Check: The accessibility name is the visible title.
Lock background scroll while a modal is open
The page behind the dialog does not scroll.
Implementation detail
Radix Dialog locks body scroll. A custom overlay that forgets this lets the page move under the dialog.
Lock body scroll while the dialog is open.
Check: With the dialog open, the background does not scroll.
Choose the kind that matches the viewport
Drawer, fullscreen, and bottom sheet are the supported adaptations. Do not invent a fifth overlay.
Implementation detail
Proposed CSC Standard. kind="drawer" is a side panel, kind="sheet" is a bottom sheet, kind="fullscreen" covers small tasks that need the whole screen.
Use drawer for a record edit, sheet for quick actions, fullscreen for a multi-step workflow on a small screen.
Check: At 375px the chosen kind fits the viewport and its actions remain reachable.
Do not open a dialog on page load
A dialog opens from a person activating a trigger, not from the first render.
Implementation detail
Proposed CSC Standard. An auto-open dialog steals focus from the page heading.
Leave the dialog closed until the person activates a trigger.
Check: Loading the page leaves focus on the page, not inside a dialog.
Restrictions
Accessibility
- Focus moves in and returns to the trigger.
- Escape closes non-destructive dialogs.
- Title and description are present.
- Background content is inert while the dialog is open.
Responsive behavior
- Centered dialogs keep 16px of viewport padding.
- Prefer sheet or fullscreen under 480px when the form is taller than the viewport.
- Action rows wrap. The primary action stays reachable.
Component states
Closed
Only the trigger is available.
Open
Focus is inside, background scroll is locked.
Destructive
Escape and outside click do not dismiss. The person chooses Delete or Cancel.
Correct implementation
A dialog element carries the title. Focus stays inside while it is open.
Imports: None. Dependencies: None. Rules: MOD-001, MOD-003.
export function ReviewAction() {
return (
<dialog open aria-labelledby="review-title">
<h2 id="review-title">Review application</h2>
<p>Check the details before you continue.</p>
<button type="button">Close</button>
</dialog>
);
}The warning stays until the person chooses. Escape does not dismiss a destructive confirm.
Imports: None. Dependencies: None. Rules: MOD-002.
export function DeleteConfirm() {
return (
<dialog open aria-labelledby="delete-title">
<h2 id="delete-title">Delete record</h2>
<p>This cannot be undone.</p>
<button type="button" style={{ background: '#C41222', color: '#fff', borderRadius: 8 }}>Delete record</button>
<button type="button">Cancel</button>
</dialog>
);
}Incorrect implementation
A clickable div does not trap focus, name the dialog, or lock scroll.
Imports: None. Dependencies: None. Rules: MOD-001, MOD-004.
export function FakeDialog() {
return <div onClick={() => {}}>Confirm</div>;
}Common mistakes
- A div overlay with no focus trap.
- Auto-opening on load.
- A destructive dialog that closes when the person clicks the scrim.
Testing checklist
Agent prompts for this component are in the AI Development Hub.