Components
Dialog
Dialog allows you to create both modal and non-modal dialogs based on the HTML dialog element.
React
const Preview = () => { return ( <Dialog.TriggerContext> <Dialog.Trigger>Open Dialog</Dialog.Trigger> <Dialog> <Heading style={{ marginBottom: 'var(--ds-size-2)' }}> Dialog header </Heading> <Paragraph style={{ marginBottom: 'var(--ds-size-2)' }}> Lorem ipsum dolor sit, amet consectetur adipisicing elit. Blanditiis doloremque obcaecati assumenda odio ducimus sunt et. </Paragraph> <Paragraph data-size='sm'>Dialog footer</Paragraph> </Dialog> </Dialog.TriggerContext> ); }; render(<Preview />)
- closeButton
- Description
Screen reader label of close button. Set false to hide the close button.
- Type
string | false- Default
Lukk dialogvindu
- closedby
- Description
Light dismiss behavior, allowing to close on backdrop click by setting `closedby="any"`. @see [mdn closedBy](https://developer.mozilla.org/en-US/docs/Web/API/HTMLDialogElement/closedBy)
- Type
"none" | "closerequest" | "any"- Default
closerequest
- modal
- Description
Toogle modal and non-modal dialog. @see [mdn modal dialog](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/dialog#creating_a_modal_dialog)
- Type
boolean- Default
true
- open
- Description
@note Unlike standard html, where the open attribute always opens a non-modal dialog, Dialog's open prop uses the `modal` prop to determine whether the Dialog is modal or non-modal
- Type
boolean
- onClose
- Description
Callback that is called when the dialog is closed.
- Type
(event: Event) => void
- asChild
- Description
Change the default rendered element for the one passed as a child, merging their props and behavior. @deprecated Will be removed in the next major version. Should always be a `<dialog>` element
- Type
boolean- Default
false
| Name | Type | Default | Description |
|---|---|---|---|
| closeButton | string | false | Lukk dialogvindu | Screen reader label of close button. Set false to hide the close button. |
| closedby | "none" | "closerequest" | "any" | closerequest | Light dismiss behavior, allowing to close on backdrop click by setting `closedby="any"`. @see [mdn closedBy](https://developer.mozilla.org/en-US/docs/Web/API/HTMLDialogElement/closedBy) |
| modal | boolean | true | Toogle modal and non-modal dialog. @see [mdn modal dialog](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/dialog#creating_a_modal_dialog) |
| open | boolean | - | @note Unlike standard html, where the open attribute always opens a non-modal dialog, Dialog's open prop uses the `modal` prop to determine whether the Dialog is modal or non-modal |
| onClose | (event: Event) => void | - | Callback that is called when the dialog is closed. |
| asChild | boolean | false | Change the default rendered element for the one passed as a child, merging their props and behavior. @deprecated Will be removed in the next major version. Should always be a `<dialog>` element |
DialogBlock
- asChild
- Description
Change the default rendered element for the one passed as a child, merging their props and behavior.
- Type
boolean- Default
false
| Name | Type | Default | Description |
|---|---|---|---|
| asChild | boolean | false | Change the default rendered element for the one passed as a child, merging their props and behavior. |
Usage
To add your own close button, you can either use ref and Dialog.close() on a button you create yourself, or add data-command="close" to a button inside the dialog.
If the button is empty, it will get an icon.
If the button is a direct child of the dialog and the first element, it will float to the top right.
data-command is inspired by Invoker Commands (mozilla.org).
Examples
With ref and without context
If you don't want to use Dialog.TriggerContext, you can use ref to open the dialog from an external trigger.
You then use native methods on the <dialog> element, such as showModal() or show().
React
const WithRef = () => { const dialogRef = useRef<HTMLDialogElement>(null); return ( <> <Button onClick={() => dialogRef.current?.showModal()}> Open Dialog with ref </Button> <Dialog ref={dialogRef}> <Heading style={{ marginBottom: 'var(--ds-size-2)' }}> Dialog header </Heading> <Paragraph style={{ marginBottom: 'var(--ds-size-2)' }}> Lorem ipsum dolor sit, amet consectetur adipisicing elit. Blanditiis doloremque obcaecati assumenda odio ducimus sunt et. </Paragraph> </Dialog> </> ); }; render(<WithRef />)
With form and focus
If you want a field in a form inside the dialog to get focus when the dialog opens, you can use the native autofocus attribute on the input element.
When using this with React, you must write it in lowercase as autofocus, not autoFocus.
This prop does not exist in React's type definitions, so we have ignored the error with an @ts-expect-error comment in the example below.
React
const WithForm = () => { const dialogRef = useRef<HTMLDialogElement>(null); const [input, setInput] = useState(''); return ( <Dialog.TriggerContext> <Dialog.Trigger>Open Dialog</Dialog.Trigger> <Dialog ref={dialogRef} onClose={() => setInput('')} closedby='any'> <Heading style={{ marginBottom: 'var(--ds-size-2)' }}> Dialog med skjema </Heading> <Textfield label='Navn' value={input} onChange={(e) => setInput(e.target.value)} // @ts-expect-error We want the native "autofocus" and Reacts onMount smartness (see https://react.dev/reference/react-dom/components/input#input) autofocus='true' /> <div style={{ display: 'flex', gap: 'var(--ds-size-4)', marginTop: 'var(--ds-size-4)', }} > <Button onClick={() => { window.alert(`Du har sendt inn skjema med navn: ${input}`); dialogRef.current?.close(); }} > Send inn skjema </Button> <Button variant='secondary' data-color='danger' data-command='close'> Avbryt </Button> </div> </Dialog> </Dialog.TriggerContext> ); }; render(<WithForm />)
With blocks
Use multiple Dialog.Block if you want to divide the dialog with dividers into, for example, a top and bottom area. Note that content cannot be placed directly in Dialog if you use Dialog.Block; then all content should be inside one of the dialog's Dialog.Block sections.
React
const WithBlocks = () => { return ( <Dialog.TriggerContext> <Dialog.Trigger>Open Dialog</Dialog.Trigger> <Dialog> <Dialog.Block> <Paragraph data-size='sm'>Dialog subtitle</Paragraph> <Heading>Dialog with dividers</Heading> </Dialog.Block> <Dialog.Block> <Paragraph> Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur sodales eros justo. </Paragraph> </Dialog.Block> <Dialog.Block> <Button variant='secondary' data-command='close'> Lukk </Button> </Dialog.Block> </Dialog> </Dialog.TriggerContext> ); }; render(<WithBlocks />)
Close on click outside
We use closedby="any" to close the dialog when the user clicks outside.
This only works when modal={true}, because a non-modal dialog has no :backdrop.
React
const CloseWithClickOutside = () => { return ( <Dialog.TriggerContext> <Dialog.Trigger>Open Dialog</Dialog.Trigger> <Dialog closedby='any'> <Heading>Click outside to close</Heading> </Dialog> </Dialog.TriggerContext> ); }; render(<CloseWithClickOutside />)
HTML
The main class name is ds-dialog, which is placed on the <dialog> element.
Blocks inside the dialog are given class names like ds-dialog__block.
In HTML, you have to connect the <dialog> element to a trigger yourself, and handle opening and closing the dialog with JavaScript.
Note that the button that opens the dialog should have the aria-haspopup="dialog" attribute.
We have extended Dialog in React to support data-command for close button, and closedby attribute to close dialog on click outside.
This must be implemented in HTML/JavaScript yourself if you are not using React component.
closedby is supported in some browsers (mozilla.org)
CSS variables and data attributes
All CSS variables used by ds-dialog__block are set to ds-dialog.
| Name | Value |
|---|---|
| --dsc-dialog-backdrop-background | rgba(0,0,0,.5) |
| --dsc-dialog-background | var(--ds-color-neutral-surface-default) |
| --dsc-dialog-icon-spacing | var(--ds-size-3) |
| --dsc-dialog-icon-url | url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='1em' height='1em' fill='none' viewBox='0 0 24 24'%3E%3Cpath fill='currentColor' d='M6.53 5.47a.75.75 0 0 0-1.06 1.06L10.94 12l-5.47 5.47a.75.75 0 1 0 1.06 1.06L12 13.06l5.47 5.47a.75.75 0 1 0 1.06-1.06L13.06 12l5.47-5.47a.75.75 0 0 0-1.06-1.06L12 10.94z'/%3E%3C/svg%3E") |
| --dsc-dialog-color | var(--ds-color-neutral-text-default) |
| --dsc-dialog-divider-border-width | var(--ds-border-width-default) |
| --dsc-dialog-divider-border-style | solid |
| --dsc-dialog-divider-border-color | var(--ds-color-neutral-border-subtle) |
| --dsc-dialog-border-width | var(--ds-border-width-default) |
| --dsc-dialog-border-style | solid |
| --dsc-dialog-border-color | var(--ds-color-neutral-border-subtle) |
| --dsc-dialog-max-height | 80vh |
| --dsc-dialog-max-width | 40rem |
| --dsc-dialog-spacing | var(--ds-size-6) |
| Name | Value |
|---|---|
| data-command | close |