gray
radius
Overlays

Dialog

A modal panel. Focus trapping, scroll locking, Escape and the backdrop click all come from base-react, so this layer only adds the shape.

Basic

Open it, then press Tab and Escape.

<Dialog.Root>
<Dialog.Trigger asChild><Button>Publish</Button></Dialog.Trigger>
<Dialog.Content>
<Dialog.Header>
<Dialog.Title>Publish style-tokens?</Dialog.Title>
<Dialog.Description>
This uploads 0.1.0 to the public npm registry. Published versions cannot be replaced.
</Dialog.Description>
</Dialog.Header>
<Dialog.Body>
<Alert.Root tone="warning">
<Alert.Icon />
<Alert.Body>
<Alert.Description>
Two packages depend on this one at workspace:^ and will need rebuilding.
</Alert.Description>
</Alert.Body>
</Alert.Root>
</Dialog.Body>
<Dialog.Footer>
<Dialog.Close asChild><Button variant="secondary">Cancel</Button></Dialog.Close>
<Dialog.Close asChild><Button>Publish</Button></Dialog.Close>
</Dialog.Footer>
</Dialog.Content>
</Dialog.Root>
Tab through it. Focus never leaves the dialog and returns to the trigger on close, and the page behind does not scroll. None of that is written here; it is why the base layer exists.

Size

Use size to set the width. The body scrolls, not the page, so the actions never leave the screen.

{(['s', 'm', 'l'] as const).map(size => (
<Dialog.Root key={size}>
<Dialog.Trigger asChild>
<Button variant="secondary">size {size}</Button>
</Dialog.Trigger>
<Dialog.Content size={size}>
<Dialog.Header>
<Dialog.Title>Size {size}</Dialog.Title>
<Dialog.Description>400, 520 and 720px at most.</Dialog.Description>
</Dialog.Header>
<Dialog.Footer>
<Dialog.Close asChild><Button variant="secondary">Close</Button></Dialog.Close>
</Dialog.Footer>
</Dialog.Content>
</Dialog.Root>
))}
Dialog.Title is not really optional. Without it the dialog has nothing to announce itself as, and Radix warns. If the design has no visible heading, wrap the title in VisuallyHidden rather than dropping it.

Parts

Import it as a namespace, and the parts work on either side of the server boundary.

import * as Dialog from '@minuk-hwang-design-system/components-react/dialog';
PartWhat it is
Dialog.RootOwns the open state.
Dialog.TriggerOpens it. Use asChild to keep your own button.
Dialog.ContentThe panel, with the scrim and the close button.
Dialog.HeaderTitle and description. Padded to clear the close button.
Dialog.TitleThe accessible name and the visible heading, same node. A Heading, so it takes every prop of one.
Dialog.DescriptionAnnounced with the title on open. A Text, so it takes every prop of one, including leading="reading".
Dialog.BodyThe scrolling region.
Dialog.FooterActions, aligned right.
Dialog.CloseCloses it from anywhere inside.

Props

PropTypeDefaultNotes
openbooleanControlled state, on Root.
onOpenChange(open: boolean) => voidOn Root. Fires for every way out, including Escape and the scrim.
size's' | 'm' | 'l''m'On Content. 400, 520 or 720px.
hideClosebooleanfalseOnly if a footer action closes the dialog.