Dialog

A dialog opens a focused task over the page, such as editing a record or filling in a short form.

A dialog opens over the page, dims everything behind it and holds focus until it closes. Use it for a short task that people finish or cancel before they go back, such as renaming a project. A question that confirms a destructive action is an Alert Dialog, and a panel that slides in from an edge is a Sheet.

Playground

Usage

import { Button, Dialog, Flex, Text, TextField } from '@kushagradhawan/kookie-ui';
 
export function RenameProject({ name }: { name: string }) {
  return (
    <Dialog.Root>
      <Dialog.Trigger>
        <Button>Rename</Button>
      </Dialog.Trigger>
      <Dialog.Content>
        <Dialog.Title>Rename project</Dialog.Title>
        <Dialog.Description>People in the project see the new name.</Dialog.Description>
        <Flex direction="column" gap="2" mt="4">
          <Text as="label" htmlFor="project-name" size="2" weight="medium">
            Project name
          </Text>
          <TextField.Root id="project-name" defaultValue={name} />
        </Flex>
        <Flex gap="2" justify="end" mt="5">
          <Dialog.Close>
            <Button emphasis="quiet" color="gray">Cancel</Button>
          </Dialog.Close>
          <Button emphasis="loud">Save</Button>
        </Flex>
      </Dialog.Content>
    </Dialog.Root>
  );
}

Anatomy

<Dialog.Root>
  <Dialog.Trigger />
  <Dialog.Content>
    <Dialog.Title />
    <Dialog.Description />
    <Dialog.Close />
  </Dialog.Content>
</Dialog.Root>
  • Root: Holds the open state. A dialog is always modal.
  • Trigger: The element that opens the dialog. It takes one child element, usually a Button.
  • Content: The panel, rendered in a portal with a dimmed overlay behind it.
  • Title: The dialog's name. It renders a Heading at size 5.
  • Description: A sentence that explains the task. It renders a p Text at size 3.
  • Close: Closes the dialog when its child is pressed. It takes one child element.

Best practices

Keep it to one task. A dialog interrupts the page. If the content needs scrolling sections or tabs of its own, it belongs on a page.

Name the task in the title. "Rename project" tells people what they are about to do. "Settings" or "Are you sure?" makes them read the body to find out.

Give it one loud button and a way out. Put the action that finishes the task on emphasis="loud" and pair it with a quiet Cancel in Dialog.Close. Label the loud button with the verb from the title.

Don't open a dialog from a dialog. A stack of overlays hides where people came from. Change the content of the open dialog instead, or move the flow to a page.

Use an Alert Dialog to confirm something that can't be undone. A dialog closes when people press outside it, which is wrong for a question that must be answered. Alert Dialog doesn't.

Size

size sets the padding, the corner and the largest width the panel takes. Size 2 is the default, and an unset size follows the Theme size.

SizeMax widthUse it for
1360pxA single field or a short message
2440pxMost forms. This is the default
3560pxForms with two columns or longer text
4720pxPreviews and wide tables

The panel fills its width up to that cap, minus the space kept around it. Set maxWidth to go wider than the size allows, or width for a fixed width.

Align

align="center" places the panel in the middle of the window. align="start" places it near the top, so the panel doesn't jump when its content grows, such as search results that load in.

Examples

Controlled

Hold the open state yourself to close the dialog after a request succeeds, rather than when the button is pressed.

const [open, setOpen] = React.useState(false);
 
async function save() {
  await updateProject();
  setOpen(false);
}
 
<Dialog.Root open={open} onOpenChange={setOpen}>
  {/* trigger and content */}
</Dialog.Root>

Content that bleeds to the edge

The panel publishes its padding, so a child with m="bleed" reaches the panel's edges. Use it for an image or a full-width list at the top of the dialog.

Long content

When the panel is taller than the window, the overlay scrolls and the panel moves with it. Keep the title and the actions in the content so they scroll into view together.

Props

Root

PropTypeDefaultDescription
openbooleanThe open state. Controlled
defaultOpenbooleanfalseThe open state on first render
onOpenChange(open: boolean) => voidCalled when the dialog opens or closes

Content

PropTypeDefaultDescription
size'1' | '2' | '3' | '4', responsiveTheme sizePadding, corner and maximum width
align'start' | 'center', responsive'center'Where the panel sits in the window
widthstring, responsiveA fixed width
minWidthstring, responsiveThe narrowest width
maxWidthstring, responsiveThe size's widthThe widest width. Overrides the size's cap
heightstring, responsiveA fixed height
minHeightstring, responsiveThe shortest height
maxHeightstring, responsiveThe tallest height
containerHTMLElementdocument.bodyWhere the portal renders
forceMountbooleanKeeps the dialog in the DOM while closed
onEscapeKeyDown(event) => voidCalled on Escape. Call preventDefault() to keep it open
onPointerDownOutside(event) => voidCalled on a press outside. Call preventDefault() to keep it open
onOpenAutoFocus(event) => voidCalled when focus moves into the dialog
onCloseAutoFocus(event) => voidCalled when focus returns to the trigger. Call preventDefault() to move it yourself

Content also accepts every attribute of the HTML div element.

Title and Description

Title takes every Heading prop and Description every Text prop. Set size on either to change its step.

Trigger and Close

Both take one child element and pass their behaviour to it. They accept every attribute of the HTML button element.

Accessibility

KeyAction
EscapeCloses the dialog
TabMoves focus to the next control in the dialog
Shift + TabMoves focus to the previous control in the dialog
  • Name: The dialog is named by Dialog.Title and described by Dialog.Description. Always include a title. Wrap it in VisuallyHidden if the design has no visible heading.
  • Role: A screen reader announces a modal dialog, and content behind it is hidden from assistive technology.
  • Focus: Focus moves to the first control in the dialog when it opens, and Tab cycles inside it. Focus returns to the trigger when it closes. Call preventDefault() in onCloseAutoFocus to move it somewhere else.
  • Dismissal: Escape and a press on the overlay both close the dialog.