Skip to the content

Kongens Nytorv

Metro M1, M2 and M3.

Props

NameTypeDefaultDescription
title*ReactNode—The dialog's title, in sentence case, such as "Keys". Required: it names the dialog for screen readers.
descriptionReactNode—One sentence under the title that says what the dialog is for, read out with its name when it opens. Phrasing content only: it renders in a <p>.
childrenReactNode—The dialog's body, under the description: sentences, a list or controls.
actionsReactNode—The dialog's actions, such as Buttons, in a row at its end, the main action last. An action that closes the dialog sets open to false.
triggerReactElement<unknown, JSXElementConstructor<any> | string>—The element that opens the dialog, such as a Button, rendered in place of the dialog. Focus returns to it when the dialog closes. Leave it out for a dialog opened only through open.
openboolean—Whether the dialog is open. Set it to control the dialog, with onClose to hear when it asks to close; leave it out for a dialog its trigger opens.
defaultOpenbooleanfalseWhether the dialog is open at first, when open is left out.
onOpen(event: Event, details: Dialog.Root.ChangeEventDetails) => void—Called when the dialog asks to open, from its trigger. details is Base UI's, with its reason and cancel().
onClose(event: Event, reason: Dialog.Root.ChangeEventReason, details: Dialog.Root.ChangeEventDetails) => void—Called when the dialog asks to close: reason is 'escape-key', 'outside-press', 'close-press' or another of Base UI's reasons, and details.cancel() keeps it open.
classNamestring—Reaches the root, the popup. className is merged after pn-Dialog.
styleCSSProperties—Reaches the root, the popup. className is merged after pn-Dialog.
refRef<HTMLDivElement>—Reaches the root, the popup. className is merged after pn-Dialog.
closeLabelstringThe locale'sOverrides "Close", the close button's name.
slotProps{ trigger?: Props<unknown> | undefined; portal?: DialogPortalProps | undefined; backdrop?: DialogBackdropProps | undefined; title?: DialogTitleProps | undefined; description?: DialogDescriptionProps | undefined; close?: Omit<DialogCloseProps, "disabled"> | undefined; }—Props for each part: trigger, portal, backdrop, title, description, close.
title (required)ReactNode, default —
The dialog's title, in sentence case, such as "Keys". Required: it names the dialog for screen readers.
descriptionReactNode, default —
One sentence under the title that says what the dialog is for, read out with its name when it opens. Phrasing content only: it renders in a <p>.
childrenReactNode, default —
The dialog's body, under the description: sentences, a list or controls.
actionsReactNode, default —
The dialog's actions, such as Buttons, in a row at its end, the main action last. An action that closes the dialog sets open to false.
triggerReactElement<unknown, JSXElementConstructor<any> | string>, default —
The element that opens the dialog, such as a Button, rendered in place of the dialog. Focus returns to it when the dialog closes. Leave it out for a dialog opened only through open.
openboolean, default —
Whether the dialog is open. Set it to control the dialog, with onClose to hear when it asks to close; leave it out for a dialog its trigger opens.
defaultOpenboolean, default false
Whether the dialog is open at first, when open is left out.
onOpen(event: Event, details: Dialog.Root.ChangeEventDetails) => void, default —
Called when the dialog asks to open, from its trigger. details is Base UI's, with its reason and cancel().
onClose(event: Event, reason: Dialog.Root.ChangeEventReason, details: Dialog.Root.ChangeEventDetails) => void, default —
Called when the dialog asks to close: reason is 'escape-key', 'outside-press', 'close-press' or another of Base UI's reasons, and details.cancel() keeps it open.
classNamestring, default —
Reaches the root, the popup. className is merged after pn-Dialog.
styleCSSProperties, default —
Reaches the root, the popup. className is merged after pn-Dialog.
refRef<HTMLDivElement>, default —
Reaches the root, the popup. className is merged after pn-Dialog.
closeLabelstring, default The locale's
Overrides "Close", the close button's name.
slotProps{ trigger?: Props<unknown> | undefined; portal?: DialogPortalProps | undefined; backdrop?: DialogBackdropProps | undefined; title?: DialogTitleProps | undefined; description?: DialogDescriptionProps | undefined; close?: Omit<DialogCloseProps, "disabled"> | undefined; }, default —
Props for each part: trigger, portal, backdrop, title, description, close.

Use it

import { Dialog } from '@mylder/perron'

Ask your agent

get_reference({ library: "base-ui", component: "Dialog" })

Keyboard and ARIA

From the Base UI reference for Dialog and the installed DialogRoot, DialogTrigger, DialogPopup, DialogBackdrop, DialogTitle and DialogClose. "From" says who does it.

KeyDoesFrom
Enter, Space on the triggerOpen the dialog, calling onOpen with reason 'trigger-press', and move focus to the first tabbable element in the popup, the close button, unless initialFocus says otherwiseThe browser activates the button; Base UI opens and moves focus
Tab, Shift+TabMove focus through the popup's tabbable elements in reading order, the close button, then the content's and the actions', wrapping from the last to the first and back: focus never leaves while openBase UI
EscapeCloses the topmost dialog, calling onClose with reason 'escape-key', and returns focus to the triggerBase UI
Enter, Space on the close buttonClose the dialog, calling onClose with reason 'close-press', and return focus to the triggerThe browser activates the button; Base UI closes

A press on the backdrop closes the dialog too, with reason 'outside-press'.

ElementRoleAccessible name fromStates announced
triggerbutton, native, or the caller's element's ownIts own content, from the calleraria-haspopup="dialog", aria-expanded, from Base UI
rootdialog, from Base UIaria-labelledby the title; described by the description through aria-describedbyModal by Base UI hiding everything outside it with aria-hidden
titleheading, level 2, from the native <h2>Its text, the caller's titlenone
closebutton, nativeThe owned "Close", or closeLabel, in the CloseButton's VisuallyHidden spannone: it is never disabled (CR-6)
backdroppresentation, from Base UInonenone
header, content, actionsnone: plain <div>snonenone

What axe won't catch, from the contract, each with how this component meets it:

  • Everything the eye is told, the ear is told, for every shape each prop accepts: the dialog is announced with its title as its name and its description, the close button's aria-hidden icon has the owned "Close" as its text, and the scrim only dims what the aria-hidden outside the popup already removes.
  • Every landmark and named region has an accessible name: the dialog is named by its own heading, the title, through aria-labelledby, and title is required.
  • Every default label can be overridden: closeLabel.
  • Every shape the type accepts renders, in every variant: title, description, children and actions render a string, an element and a list alike; a long word in the title, the description or the content wraps (CR-9).
  • A hidden copy kept for animation is aria-hidden, and a collapsed region is inert: none here; the closed dialog renders nothing, and Base UI hides the page behind the open one with aria-hidden.
  • Native semantics first: Base UI's dialog role, a native <h2> for the title, native <button>s for the trigger and the close button, and no ARIA of Perron's own.
  • Focus moves in reading order and returns on close: the header comes first in the DOM, then the description, the content and the actions, as on screen; opening moves focus into the popup and closing, by any reason, returns it to the trigger, also for a dialog open at first render (CR-5).
  • Every target is at least size.target-min square: the close button, size.control (34 px) square.
  • Loops stop when the system asks for less motion: none here; the dialog has no loop and no transition (CR-10).

Owned strings

KeyEnglishOverrideSeen or heard where
close"Close"closeLabelclose, heard as the close button's name from the CloseButton's VisuallyHidden span; never seen

close is the key packages/perron/src/locale/en.ts already has, "A button that closes a dialog, panel or notice", so Dialog adds no key. Every other word is the caller's, through title, description, children, actions and trigger.

Do and don't

For a caller using the component, with the reason. Each row is a rule: a must row's Don't is never right, a should row's needs a reason, and Checked by says what catches the Don't in a caller's code.

RuleLevelDoDon'tChecked by
DD-1mustGive every dialog a short title in sentence case, such as "Keys" or "Clear all filters?", which names it for screen readersLeave the title empty or hide it: the dialog then has no namereview
DD-2mustPass the button that opens the dialog as trigger, so it is announced as opening a dialog and gets focus backOpen a dialog from a button placed beside it with open alone, when a trigger would do: the button loses aria-haspopup and aria-expandedreview
DD-3mustSet slotProps.trigger.nativeButton to false when trigger is not a <button>, as Base UI warnsPass a link as the trigger for a dialog: a link goes somewhere, and a dialog opens over the screenreview
DD-4mustKeep the description to one sentence, and put lists, steps and controls in childrenPut block content, such as a list, in description: it renders in a <p>review
DD-5mustPut the main action last in actions, the one brand Button, and close the dialog from an action by setting open to falseMark more than one action brand, or put a second close button in the actions: the header already has one, and Esc closes tooreview
DD-6shouldUse a dialog for a question that must be answered or help that interrupts, kept shortUse a dialog for a vehicle's or a stop's details: they belong in the detail panel beside the map, which leaves the map usablereview
DD-7mustKeep a dialog open, such as while it saves, by calling details.cancel() in onCloseTry to disable the close button: it is never disabled, so touch screen readers can always leavetypes
DD-8mustLeave the close button's label to the locale, or override it with closeLabelHide the close button with a className: Base UI asks for it in a modal dialog, so touch screen readers can leavereview
DD-9mustLet the dialog take z-index.dialog and its place in the middle of the screenSet a z-index, a position or an opacity on the dialog: it would fall under another overlay or leave its scrimreview