Skip to content

Dialog

Dialog opens a modal window above the page. Focus is trapped inside it, Escape closes it, and focus returns to the trigger when it closes.

import { Dialog } from '@beyul/sulmu-ui'

Source · Figma

Pass the element that opens the dialog as trigger. Two buttons in footer share the width equally.

import { Button, Dialog } from '@beyul/sulmu-ui'
export default function Example() {
return (
<Dialog
title="Add Client"
description="Eu feugiat enim velit vitae sem diam sed faucibus ac."
trigger={<Button>Add client</Button>}
footer={
<>
<Button variant="secondary" size="lg">
Cancel
</Button>
<Button size="lg">Add</Button>
</>
}
>
Dialog content goes here.
</Dialog>
)
}

Control it with open and onOpenChange when something else has to close it, such as a successful save.

import { Button, Dialog, Divider, Field, Grid, Input, Select } from '@beyul/sulmu-ui'
import { useState } from 'react'
const priorities = [
{ value: 'low', label: 'Low' },
{ value: 'medium', label: 'Medium' },
{ value: 'high', label: 'High' },
]
export default function Example() {
const [open, setOpen] = useState(false)
return (
<Dialog
open={open}
onOpenChange={(d) => setOpen(d.open)}
title="Add Client"
trigger={<Button variant="secondary">Open form dialog</Button>}
footer={
<>
<Button variant="secondary" size="lg" onClick={() => setOpen(false)}>
Cancel
</Button>
<Button size="lg" onClick={() => setOpen(false)}>
Add
</Button>
</>
}
>
<Divider label="Basic info" />
<Grid columns={2} gap={6}>
<Field label="Client Name" required>
<Input defaultValue="Beyond Borders Agency" />
</Field>
<Field label="Priority" required>
<Select items={priorities} defaultValue={['low']} />
</Field>
</Grid>
</Dialog>
)
}

hideClose removes the X button and closeOnInteractOutside={false} stops a click outside from closing it, for dialogs that need an explicit choice.

import { Button, Dialog } from '@beyul/sulmu-ui'
import { useState } from 'react'
export default function Example() {
const [open, setOpen] = useState(false)
return (
<Dialog
size="sm"
hideClose
closeOnInteractOutside={false}
open={open}
onOpenChange={(d) => setOpen(d.open)}
title="Delete this stage?"
description="The 4 deals in it move to the previous stage. This cannot be undone."
trigger={<Button variant="destructive">Delete stage</Button>}
footer={
<>
<Button variant="secondary" onClick={() => setOpen(false)}>
Keep stage
</Button>
<Button variant="destructive" onClick={() => setOpen(false)}>
Delete
</Button>
</>
}
/>
)
}
PropTypeDefaultDescription
title*ReactNode-Required: names the dialog for assistive tech.
descriptionReactNode-
triggerReactElement<unknown, string | JSXElementConstructor<any>>-Optional element that opens the dialog. Or control it with open and onOpenChange.
footerReactNode-Actions. Two buttons share the width equally, as in the mockups.
size"sm" | "md" | "lg"mdsm 400, md 576 (default), lg 768.
hideCloseboolean-Hide the close (X) button, for dialogs that need an explicit choice.
classNamestring-
openboolean-The controlled open state of the dialog
defaultOpenboolean-The initial open state of the dialog when rendered. Use when you don't need to control the open state of the dialog.
onOpenChange((details: OpenChangeDetails) => void)-Function to call when the dialog's open state changes
closeOnInteractOutsideboolean-Whether to close the dialog when the outside is clicked
closeOnEscapeboolean-Whether to close the dialog when the escape key is pressed

The remaining props of Ark UI’s Dialog.Root are accepted too.

  • title is required; it names the dialog for assistive tech. description is linked as its description.
  • Sizes: sm is 400px wide, md 576px and lg 768px.
  • The content is mounted when the dialog opens and removed when it closes, so state inside it (including form values) starts fresh each time.
  • The dialog is rendered in a portal at the end of <body>.