Dialog

An overlay shown above other content in the application.

The HTML <dialog> element can be used to build dialogs. However, it is not yet widely supported across browsers, and building fully accessible custom dialogs from scratch is very difficult and error prone. Dialog helps achieve accessible dialogs that can be styled as needed.

Features

  • Can be dialog or alert dialog.
  • Opens at the center (default), left, right, top, or bottom of the screen.
  • Focus is moved into the dialog on mount, and restored to the trigger element on unmount.
  • While open, focus is contained within the dialog, preventing the user from tabbing outside.
  • Exposed to assistive technology as a dialog with ARIA.
  • The dialog is automatically labeled by a nested DialogTitle element.
  • Content outside the dialog is hidden from assistive technologies while it is open.
  • Esc closes the component automatically.

To do

Keyboard interactions

PropTypeDefault
SpaceOpens/closes the dialog.
EnterOpens/closes the dialog.
TabMoves focus to the next focusable element.
Shift + TabMoves focus to the previous focusable element.
EscCloses the dialog and moves focus to the DialogTrigger.

Parts and their API

DialogTrigger

The DialogTrigger component does not render any DOM elements. It only passes through its children.

Props

PropTypeDefaultDescription
children *ReactNodeDialogTrigger needs at least two children: a <Button> and a <Dialog>.
isOpenbooleanWhether the overlay is open by default (controlled).
defaultOpenbooleanWhether the overlay is open by default (uncontrolled).

Events

NameTypeDescription
onOpenChange(isOpen: boolean) => voidHandler that is called when the overlay's open state changes.

Dialog

A dialog is an overlay shown above other content in an application.

Props

PropTypeDefaultDescription
alertbooleanfalseWhether the dialog is an alert dialog.
side'center' | 'left' | 'right' | 'top' | 'bottom''center'Side of the screen the dialog will open from.
childrenReactNode | (opts: DialogRenderProps) => ReactNodeChildren of the dialog. A function may be provided to access a function to close the dialog.
classNamestring | (values: ButtonRenderProps) => stringThe CSS className for the element. A function may be provided to compute the class based on component state.
styleCSSPropertiesThe inline style for the element.

Accessibility

NameTypeDescription
idstringThe element's unique identifier.
aria-labelstringDefines a string value that labels the current element.
aria-labelledbystringIdentifies the element (or elements) that labels the current element.
aria-describedbystringIdentifies the element (or elements) that describes the object.
aria-detailsstringIdentifies the element (or elements) that provide a detailed, extended description for the object.

The <DialogTitle> has the same props as an <H2> and is mandatory so assistive technology announces the dialog correctly.

If you want to visually hide the title, wrap it with <VisuallyHidden>.

Usage

DialogExample.tsx
  1. 1
  2. 2
  3. 3
  4. 4
  5. 5
  6. 6
  7. 7
  8. 8
  9. 9
  10. 10
  11. 11
  12. 12
  13. 13
  14. 14
  15. 15
  16. 16
  17. 17
  18. 18
  19. 19
  20. 20
  21. 21
  22. 22
  23. 23
  24. 24
  25. 25
  26. 26
  27. 27
  28. 28
  29. 29
  30. 30
  31. 31
  32. 32
  33. 33
  34. 34
  35. 35
  36. 36
  37. 37
  38. 38
  39. 39
  40. 40
  41. 41
  42. 42
  43. 43
  44. 44
  45. 45
  46. 46
  47. 47
  48. 48
  49. 49
  50. 50
  51. 51
  52. 52
  53. 53
  54. 54
  55. 55
<DialogTrigger>
<Button variant="primary">Create new post</Button>
<Dialog>
{({ close }) => (
<>
<VisuallyHidden>
<DialogTitle>What is happening?!</DialogTitle>
</VisuallyHidden>
<div className="flex items-start space-x-4">
<div className="flex-shrink-0">
<img
className="inline-block h-10 w-10 rounded-full"
src="https://images.unsplash.com/photo-1550525811-e5869dd03032?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80"
alt=""
/>
</div>
<div className="min-w-0 flex-1">
<div className="border-b border-muted-200 focus-within:border-brand">
<label htmlFor="comment" className="sr-only">
What is happening?!
</label>
<textarea
rows={3}
name="comment"
id="comment"
className="block w-full resize-none border-0 border-b border-transparent p-0 pb-2 text-muted-900 outline-none placeholder:text-muted-400 focus:border-brand focus:ring-0 sm:text-sm sm:leading-6"
placeholder="What is happening?!"
defaultValue={''}
/>
</div>
<div className="flex justify-between pt-2">
<div className="flex items-center">
<div className="flow-root">
<Button variant="ghost" size="sm" type="button" className="text-muted-400">
<Icon name="image" className="h-5 w-5" />
<span className="sr-only">Attach an image</span>
</Button>
<Button variant="ghost" size="sm" type="button" className="text-muted-400">
<Icon name="calendar" className="h-5 w-5" />
<span className="sr-only">Schedule</span>
</Button>
</div>
</div>
<div className="flex-shrink-0">
<Button type="submit" onPress={close}>
Post
</Button>
</div>
</div>
</div>
</div>
</>
)}
</Dialog>
</DialogTrigger>

Here's the <Dialog /> component in action.

And here are all the variantions of the dialog.

Notes on an alert dialog

An alert dialog should be used when the user can't continue without addressing the dialog. He can choose an option or press Escape, but he can't click away.

When the dialog opens, make sure the Cancel button has focus to avoid accidental actions.

Also don't label the cancellation button with the text "Close"; use "Cancel/Undo/etc" to make it clear for the user that the confirmation has been denied.

Source

Styling

tailwind.css
  1. 1
  2. 2
  3. 3
  4. 4
  5. 5
  6. 6
  7. 7
  8. 8
  9. 9
  10. 10
/* ### Modals ### */
@layer components {
/* Dialog */
.dialog {
@apply relative outline-none;
}
.dialog-title {
@apply my-0 text-size-2xl font-semibold leading-6 text-muted-700;
}
}