Dialog
The Dialog component provides an accessible and customizable dialog overlay for displaying important content or requesting user input. It supports different sizes, themes, and can include an icon or logo in the header.
Variations
Sizes
The Dialog component offers four size options: sm, md, lg, and max (which sets the dialog to 80% of the viewport’s width and height).
Themes
Headers can have themes default, warning and danger with matching icons:
Logo and icon
Headers can display a logo or an icon:
Hidden close button
You can hide the close button in the top-right corner using the hideClose prop:
Scrollable content
The Body component automatically handles scrolling for long content:
Custom trigger focus
If a dialog is opened without the Trigger component (e.g., a dropdown item), focus will not return to the trigger element when the dialog is closed. To address this use useCustomDialogTrigger hook to register your custom trigger element.
Installation
import { Dialog } from "@harnessio/ui/components";
// ...
return ( <Dialog.Root> <Dialog.Trigger> <Button>Open Dialog</Button> </Dialog.Trigger>
<Dialog.Content size="md"> <Dialog.Header icon="plus-circle"> <Dialog.Title>Dialog Title</Dialog.Title> <Dialog.Description>Optional description text</Dialog.Description> </Dialog.Header>
<Dialog.Body> Dialog content goes here </Dialog.Body>
<Dialog.Footer> <Dialog.Close>Cancel</Dialog.Close> <Button variant="primary">Confirm</Button> </Dialog.Footer> </Dialog.Content> </Dialog.Root>)Anatomy
All parts of the Dialog are composed together. It’s built on Radix UI’s dialog primitive, so it comes with a focus trap, an overlay, and Escape-to-close out of the box.
<Dialog.Root> <Dialog.Trigger /> <Dialog.Content> <Dialog.Header> <Dialog.Title /> <Dialog.Description /> </Dialog.Header> <Dialog.Body /> <Dialog.Footer> <Dialog.Close /> </Dialog.Footer> </Dialog.Content></Dialog.Root>- Root — controls the open state; use it uncontrolled, or drive it with
open/onOpenChange. - Trigger — wraps the element that opens the dialog and registers it so focus returns there on close.
- Content — the modal surface, rendered in a portal over an overlay. It draws a close (✕) button in the top-right unless
hideCloseis set, andsizescales it (sm→max). TheFooteris pulled to the bottom outside the scrolling area automatically. - Header / Title / Description — the top section.
Titlelabels the dialog andDescriptiondescribes it.Headertakes aniconor alogo(not both) and atheme(default/warning/danger). If you omit aDescription, a visually-hidden placeholder is inserted so the dialog always has an accessible description. - Body — the main content region; it scrolls on overflow by default (set
scrollable={false}to opt out). - Footer / Close — the action row.
Closerenders a secondary button that dismisses the dialog and can sit in the footer or stand alone.
Best practices
Do
- Keep a dialog focused on a single task or decision; move anything larger to a full page.
- Pair a
theme="danger"header with a clearly-labeled destructive action (“Delete”), and keep “Cancel” as the safe default. - Choose the smallest
sizethat fits the content — reservemaxfor genuinely large forms, tables, or dashboards. - Always give the dialog a
Title, and aDescriptionwhen the purpose isn’t obvious from the title alone.
Don’t
- Don’t hide the close button (
hideClose) unless the flow requires an explicit choice, and always leave another way out (a Cancel button). - Don’t pass both
iconandlogoto a header — only one is allowed and passing both renders nothing. - Don’t stack dialogs; resolve one before opening the next.
Content
- Write titles as a short noun phrase or clear action (“Delete item”, “Generate clone credential”), in sentence case.
- Keep descriptions to a sentence or two of supporting context — not a place for long instructions.
- Label footer buttons with the action they perform (“Connect”, “Create branch”) rather than generic “OK”.
Accessibility
Dialog is built on Radix UI’s dialog, which provides the modal semantics: role="dialog", aria-modal, a focus trap, and an overlay that blocks the rest of the page.
Keyboard
- Opening the dialog moves focus into it; Tab / Shift+Tab cycle through focusable elements and are trapped inside while it’s open.
- Escape closes the dialog, and closing returns focus to the element that opened it.
Labeling
Dialog.Titleis wired as the dialog’s accessible name (aria-labelledby) andDialog.Descriptionas its accessible description (aria-describedby).- The top-right close button is icon-only but carries an accessible label so it’s announced as a close control.
Focus & states
- Focus returns to the
Triggeron close. When you open a dialog withoutDialog.Trigger(e.g. from a dropdown item), register the opener with theuseCustomDialogTriggerhook so focus still returns to it.
Known limitations
- A missing description is filled with a placeholder. If you don’t provide a
Dialog.Description, a visually-hidden “No description available” is inserted to satisfy the accessible-description requirement — screen-reader users will hear that generic text. Provide a realDescription(or your ownaria-describedby) whenever the dialog needs context. - Icon and logo are mutually exclusive. Passing both
iconandlogotoHeaderlogs a warning and renders nothing — choose one.
Properties
Root
Controls the open/close behavior of the modal.
Prop | Required | Default | Type |
|---|---|---|---|
| open | false | boolean | |
| onOpenChange | false | (open: boolean) => void | |
| children | true | ReactNode |
Trigger
Used to open the dialog. Essential for registering the trigger element with the dialog focus manager. If a dialog is opened without the Trigger component (e.g., a dropdown item), focus will not return to the trigger element when the dialog is closed. To address this use the useCustomDialogTrigger hook to register your custom trigger element.
Prop | Required | Default | Type |
|---|---|---|---|
| children | true | ReactNode |
Content
The main content container of the dialog. Also forwards the underlying Radix Dialog.Content props (e.g. onEscapeKeyDown, onInteractOutside, onCloseAutoFocus).
Prop | Required | Default | Type |
|---|---|---|---|
| size | false | 'sm' | 'sm' | 'md' | 'lg' | 'max' |
| hideClose | false | false | boolean |
| className | false | string | |
| children | true | ReactNode |
Header
The header section of the dialog, containing the title and optional description.
Prop | Required | Default | Type |
|---|---|---|---|
| icon | false | IconV2NamesType | |
| logo | false | LogoV2NamesType | |
| theme | false | 'default' | 'default' | 'warning' | 'danger' |
| className | false | string | |
| children | true | ReactNode |
Title
The title text component for the dialog header.
Prop | Required | Default | Type |
|---|---|---|---|
| className | false | string | |
| children | true | ReactNode |
Description
Optional description text below the title.
Prop | Required | Default | Type |
|---|---|---|---|
| className | false | string | |
| children | true | ReactNode |
Body
Main content area of the dialog. Scrolls on overflow by default.
Prop | Required | Default | Type |
|---|---|---|---|
| scrollable | false | true | boolean |
| className | false | string | |
| classNameContent | false | string | |
| children | true | ReactNode |
Footer
Footer section for actions (e.g., buttons).
Prop | Required | Default | Type |
|---|---|---|---|
| className | false | string | |
| children | true | ReactNode |
Close
Can be used inside Footer or as a standalone button to close the modal. Renders a secondary Button and accepts its props.
Prop | Required | Default | Type |
|---|---|---|---|
| className | false | string | |
| children | true | ReactNode |