Dialog

A focused task over the page.

StableSince 1.0.0
On this page

Import

import { Dialog, DialogClose } from '@envsemble/envui';

Usage

A Dialog opens from its trigger, such as a Button, and holds its title, which names it, a description, its content as children, and its own actions, after the built-in close button, with the primary action last.

  • closeLabel names the close button; name it Cancel for a decision.
  • Leave it uncontrolled, or control it with open and onOpenChange, which receives the requested state and why: the trigger, Esc, an outside press, the close button or a DialogClose.
  • defaultOpen opens it on the first render.
ProjectDetails.tsx
import { Button, Dialog, DialogClose, Text } from '@envsemble/envui';

export default function ProjectDetails() {
  return (
    <Dialog
      trigger={<Button variant="secondary">View project</Button>}
      title="Northwind launch"
      description="Owned by Priya Shah · Due 14 November"
      actions={<DialogClose variant="primary">Open project</DialogClose>}
    >
      <Text>
        Plans, milestones and responsibilities for the launch, shared with everyone in the
        workspace.
      </Text>
    </Dialog>
  );
}

Pending work

pending keeps the dialog open until it returns to false: Esc, outside presses and the close buttons don't dismiss it.

  • It disables the built-in close button and every DialogClose inside.
  • The last action, the primary one, shows it pending, where it is a Button that sets no pending of its own.
  • Put a <form> in children and make the primary action its submit button, through the form attribute, so Enter in a field submits it too.
<Dialog
  title="Rename project"
  closeLabel="Cancel"
  pending={saving}
  actions={
    <Button type="submit" form={formId}>
      Save name
    </Button>
  }
>
  <form id={formId} onSubmit={save}>
    <TextField label="Project name" readOnly={saving} />
  </form>
</Dialog>

Closing actions

An action closes the dialog only where it is a DialogClose, a Button that closes it after its own onClick, or where it sets open to false. DialogClose takes Button's props and is secondary by default.

<Dialog
  title="Save changes to Launch plan?"
  closeLabel="Cancel"
  actions={
    <>
      <DialogClose onClick={discard}>Don’t save</DialogClose>
      <DialogClose variant="primary" onClick={save}>
        Save
      </DialogClose>
    </>
  }
/>

Placement, width and modal

placement is center, the default, or top, near the top of the window, so results grow down without moving the field.

  • width is md, 28 rem and the default, or lg, 44 rem. Both narrow to fit the window.
  • By default the dialog is modal: it traps focus over a backdrop, hides the page from assistive technology, blocks presses on it and locks its scroll. modal={false} leaves the page usable beside the dialog, such as an inspector.
  • disablePointerDismissal keeps it open on presses outside it, and a non-modal one also when focus moves outside.
<Dialog width="lg" title="Review changes" trigger={<Button>Review</Button>}>
  …
</Dialog>

Focus

initialFocus sets where focus goes on opening, as a ref or a function of how it was opened; by default the first focusable element inside, and a confirmation's close button. finalFocus sets where it goes on closing, such as the row that opened it, where the trigger is gone or something other than trigger opened the dialog.

Composition

Keep one task per dialog: show a field's error on the field rather than in a second dialog over the first.

Customize

The parts are the surface, env-dialog, its heading, env-dialog-heading, with the title, env-dialog-title, the description, env-dialog-description, and the actions, env-dialog-actions, which start with the close button. The backdrop behind a modal dialog is env-dialog-backdrop.

The surface takes the popups' color and their shadow, --env-popup-shadow, as Popover describes, with no edge in light and their faint hairline in dark. It stays solid over the backdrop, so --env-popup-opacity and --env-popup-blur don't reach it. At high contrast, with reduced transparency and under forced colors it has no shadow and its level-3 elevation edge returns.

Accessibility

You provide

  • A title, and a description where the consequence needs saying.
  • closeLabel in the page's language, such as Cancel.
  • finalFocus where the trigger is gone, or something other than trigger opened the dialog.

envUI provides

  • role="dialog", named by title and described by description.
  • Focus moved in on open, kept inside a modal dialog and returned on close; a confirmation starts on its close button.
  • The page behind a modal dialog hidden from assistive technology, closed to presses and locked from scrolling.
  • A close button in every dialog, for touch screen readers that have no Esc.

Props

Dialog

A focused task over the page: a centered modal, or a modal near the top of the window. It has a title, an optional description, content and actions that start with a built-in close button. Base UI's dialog owns focus, Escape and outside presses. className, style, ref and data attributes go to the dialog's surface.

PropTypeDescription
open
boolean
Whether the dialog is open, for a dialog the application controls with onOpenChange.
defaultOpenDefault false
boolean
Whether a dialog the application doesn't control is open on the first render.
onOpenChange
(open: boolean, details: DialogOpenChangeDetails) => void
Called with the requested state, and why, when the dialog asks to open or close: from the trigger, Escape, an outside press, the close button or a DialogClose. Not called for a dismissal that pending blocks.
trigger
ReactElement
The element that opens the dialog, such as a Button. It reports the dialog it opens, and focus returns to it on close. Omit it for a dialog the application opens with open.
titlerequired
ReactNode
The dialog's heading and accessible name, such as “Delete project?”.
description
ReactNode
Muted text under the title that describes the dialog to assistive technology too, such as what its action changes.
children
ReactNode
The content between the heading and the actions, such as text, fields or a list.
actions
ReactNode
The dialog's own actions, after the built-in close button, with the primary action last. They close the dialog only where they are DialogClose or set open to false. While the dialog is pending, the last one shows it, where it is a Button that sets no pending of its own.
closeLabelDefault 'Close', or EnvUIProvider's messages.dialog.close
string
The label of the built-in close button that starts the actions, such as Cancel.
placementDefault 'center'
'center''top'
center for a modal in the middle of the window; top for a modal near the top of the window for search-like tasks, so results grow downward without moving the field. For a panel at an edge of the window, use Sheet.
widthDefault 'md'
'md''lg'
md, 28 rem wide, or lg, 44 rem, for content-heavy dialogs such as tables, comparisons and longer forms. Both narrow to fit the window.
modalDefault true
boolean
true traps focus in the dialog over a backdrop, hides the page from assistive technology, blocks presses on it and locks its scroll. false leaves the page usable beside the dialog; it then closes when focus or a press goes outside it, unless disablePointerDismissal.
disablePointerDismissalDefault false
boolean
Keep the dialog open on presses outside it, and a non-modal dialog also when focus moves outside. Escape and the close buttons still close it.
initialFocus
DialogFocusTarget
The element to focus when the dialog opens, as a ref or a function of how it was opened. By default focus moves to the first focusable element inside, such as the first field, or to the dialog itself when opened by touch.
finalFocus
DialogFocusTarget
The element to focus when the dialog closes, such as the row that opened it. By default focus returns to the trigger, or to the element that had focus when the dialog opened.
pendingDefault false
boolean
Work started from the dialog is in progress. Escape, outside presses and the close buttons cannot dismiss it until pending returns to false: the built-in close button and every DialogClose inside are disabled, the primary action, the last of actions, shows it pending, and the dialog reports busy.
skeletonDefault false
boolean
Shows it and its content as a busy skeleton while that loads; turning it on or off renders the content anew.

Also accepts the native props of <div>, such as ref, className and aria-* attributes.

DialogClose

A button inside a Dialog or a Sheet that closes it after its own onClick, for an action other than the built-in close button, such as Don't save or Done. It takes Button's props and is secondary by default, and is disabled while the dialog or sheet is pending.

PropTypeDescription
variantDefault 'secondary'
'primary''secondary''outline''ghost''text'
The emphasis, as Button's; secondary by default, as an action beside the dialog's main one.
toneDefault 'neutral'
'neutral''danger''accent'
The meaning: neutral for most actions, danger for one that deletes or ends something, and accent, on a primary only, for a main action that must stand apart from the brand blue.
underlineDefault false
boolean
Show an underline for the text variant.
size
'sm''md''lg'
The control height, sm, md or lg; without it, the container's, and else md.
fullWidthDefault false
boolean
Fill the width of the container, such as the actions of a narrow form stacked in a column.
startIcon
ReactNode
A decorative icon before the label, such as IconAdd.
endIcon
ReactNode
A decorative icon after the label, such as a ChevronRight for a next step.
end
ReactNode
Quiet content at the far end, such as a shortcut's Kbd, with aria-hidden on its keys and aria-keyshortcuts on the button.
pendingDefault false
boolean
Its work is running: a spinner turns over it, it keeps its width and name, and presses do nothing.

Also accepts the native props of <button>, such as ref, className and aria-* attributes.

Part names

Each part's class and state attributes are public and versioned, for an application's CSS to style.

env-dialog
The dialog.
env-dialog-actions
The row of actions at the bottom.
env-dialog-backdrop
The backdrop behind a modal dialog.
env-dialog-content
The dialog's content between the header and the actions.
env-dialog-description
The sentence under the title.
env-dialog-header
The row with the title and the close button.
env-dialog-heading
The column with the title and the description.
env-dialog-positioner
The positioner that centres the dialog.
env-dialog-surface
The dialog's surface.
env-dialog-title
The title.
data-ending-style
Any value, or present on env-dialog, env-dialog-backdrop
data-env-skeleton
Present
data-placement
center · top on env-dialog, env-dialog-positioner
data-starting-style
Any value, or present on env-dialog, env-dialog-backdrop
data-tone
danger · neutral
data-width
lg · md

Strings

EnvUIProvider's messages overrides each, and a component's own label prop wins over it.

messages.dialog.close
"Close" The built-in close button's label.
messages.dialog.resize
"Resize" Names the handle that resizes a resizable side Sheet.
messages.dialog.width
({ width }) => string The resize handle's value: the Sheet's width in rem, such as 24 rem, in the locale's digits.

Changelog

1.0.0

width (md or lg) for size; the side panel, placement="right", is Sheet; onOpenChange reports the reason; initialFocus and finalFocus take functions too; the surface's class is env-dialog. The page is dialog, after the component.

1.0.0-prerelease.6

A dialog fades in with --env-motion-ease-enter, so it shows from its first frames rather than late, while it rises into place as before.

While a dialog is pending, every DialogClose inside is disabled as the built-in close button is, and its last action, where it is a Button that sets no pending of its own, shows it pending, so pending need not be set on each.

In dark, the backdrop is black at 70 %, rather than 50 %, so it sets the dark page back as clearly as the light backdrop does.

1.0.0-prerelease.5

Dialogs take the popups' color and soft shadow, with no outline edge, and stay solid over the scrim; at high contrast and with reduced transparency the shadow goes and the edge returns.