Skip to content

Dialog

A modal window on top of the page that asks for a decision or collects input. DialogTrigger opens it and DialogClose closes it.

Dialog.tsx
import {
  Dialog,
  DialogClose,
  DialogContent,
  DialogDescription,
  DialogFooter,
  DialogHeader,
  DialogTitle,
  DialogTrigger,
  TextField
} from 'alinea/components'

export function DialogExample() {
  return (
    <Dialog>
      <DialogTrigger variant="outline">Rename entry</DialogTrigger>
      <DialogContent>
        <DialogHeader>
          <DialogTitle>Rename entry</DialogTitle>
          <DialogDescription>
            The URL of the entry stays the same.
          </DialogDescription>
        </DialogHeader>
        <TextField label="Title" defaultValue="Summer collection" autoFocus />
        <DialogFooter>
          <DialogClose variant="ghost">Cancel</DialogClose>
          <DialogClose color="primary">Save</DialogClose>
        </DialogFooter>
      </DialogContent>
    </Dialog>
  )
}

Confirm a destructive action

Use role="alertdialog" for a decision editors have to make. With dismissable={false} a click outside the dialog doesn't close it.

DialogAlert.tsx
import {
  Dialog,
  DialogClose,
  DialogContent,
  DialogDescription,
  DialogFooter,
  DialogHeader,
  DialogTitle,
  DialogTrigger
} from 'alinea/components'
import {IcRoundDelete} from 'alinea/dashboard/icons'

export function DialogAlertExample() {
  return (
    <Dialog>
      <DialogTrigger color="destructive" variant="outline" icon={IcRoundDelete}>
        Delete
      </DialogTrigger>
      <DialogContent
        role="alertdialog"
        dismissable={false}
        showCloseButton={false}
      >
        <DialogHeader>
          <DialogTitle>Delete “Linen shirt”?</DialogTitle>
          <DialogDescription>
            The entry and its translations are removed. This can’t be undone.
          </DialogDescription>
        </DialogHeader>
        <DialogFooter>
          <DialogClose variant="ghost">Keep it</DialogClose>
          <DialogClose color="destructive">Delete entry</DialogClose>
        </DialogFooter>
      </DialogContent>
    </Dialog>
  )
}

Controlled

Pass open and onOpenChange to open a dialog from anywhere, and close it once a change is saved. useDialog gives the content of a dialog its open state and a close function.

DialogForm.tsx
import {
  Button,
  Dialog,
  DialogContent,
  DialogFooter,
  DialogHeader,
  DialogTitle,
  TextField
} from 'alinea/components'
import {useState} from 'react'

export function DialogFormExample() {
  const [open, setOpen] = useState(false)
  const [name, setName] = useState('Oak & Loom')
  const [draft, setDraft] = useState(name)
  return (
    <>
      <Button onClick={() => setOpen(true)}>Site name: {name}</Button>
      <Dialog open={open} onOpenChange={setOpen}>
        <DialogContent>
          <DialogHeader>
            <DialogTitle>Site settings</DialogTitle>
          </DialogHeader>
          <TextField label="Site name" value={draft} onValueChange={setDraft} />
          <DialogFooter>
            <Button variant="ghost" onClick={() => setOpen(false)}>
              Cancel
            </Button>
            <Button
              color="primary"
              onClick={() => {
                setName(draft)
                setOpen(false)
              }}
            >
              Save
            </Button>
          </DialogFooter>
        </DialogContent>
      </Dialog>
    </>
  )
}

Submit a form

Pass onSubmit to DialogContent to render its content as a Form: a Button with type="submit" submits it with the values of the named fields. The dialog closes once onSubmit resolves without errors. Errors it returns, keyed by field name, show on the fields and keep the dialog open. onClose on Dialog runs whenever the dialog closes, after onOpenChange(false), for example to reset state. The form takes no room of its own (display: contents): put spacing such as a gap on DialogContent.

DialogSubmit.tsx
import {
  Button,
  Dialog,
  DialogClose,
  DialogContent,
  DialogDescription,
  DialogFooter,
  DialogHeader,
  DialogTitle,
  DialogTrigger,
  TextField
} from 'alinea/components'

// Stands in for a request to your own API, which returns errors by field name
async function movePage(data: FormData) {
  await new Promise(resolve => setTimeout(resolve, 300))
  if (data.get('path') === 'about') return {path: 'This path is taken'}
  return undefined
}

export function DialogSubmitExample() {
  return (
    <Dialog>
      <DialogTrigger variant="outline">Change path</DialogTrigger>
      <DialogContent onSubmit={movePage}>
        <DialogHeader>
          <DialogTitle>Change path</DialogTitle>
          <DialogDescription>Try “about” to see an error.</DialogDescription>
        </DialogHeader>
        <TextField label="Path" name="path" defaultValue="about" autoFocus />
        <DialogFooter>
          <DialogClose variant="ghost">Cancel</DialogClose>
          <Button type="submit" color="primary">
            Save
          </Button>
        </DialogFooter>
      </DialogContent>
    </Dialog>
  )
}

Props

<Dialog>

PropTypeDefaultDescription
onClose() => voidDefault –Called when the dialog closes, after onOpenChange(false)
openbooleanDefault –
defaultOpenbooleanDefault –
onOpenChange(open: boolean) => voidDefault –

<DialogTrigger>

Accepts every prop of Button.

<DialogContent>

PropTypeDefaultDescription
role'dialog' | 'alertdialog'Default –
size'default' | 'lg' | 'full'Default 'default'default fits its content up to a narrow width, lg is a fixed wide dialog and full fills the viewport (minus a margin), defaults to default
dismissablebooleanDefault trueClose the dialog when clicking outside of it, defaults to true
showCloseButtonbooleanDefault trueDefaults to true
onSubmitFormProps['onSubmit']Default –Renders the content as a Form, submitted by a type="submit" Button. The dialog closes once this resolves without returning errors, which are shown on the fields otherwise.
validationErrorsValidationErrorsDefault –Errors keyed by field name, when the content is a form

Also accepts className and style, id and aria-* labelling attributes and data-* attributes.

<DialogHeader>

Accepts every prop of a <div> element.

<DialogTitle>

PropTypeDefaultDescription
children*ReactNodeDefault –

Also accepts className and style.

<DialogDescription>

Accepts every prop of a <p> element.

<DialogFooter>

Accepts every prop of a <div> element.

<DialogClose>

Closes the surrounding Dialog or Popover when pressed

Accepts every prop of Button.

useDialog()

The open state of the surrounding Dialog or Popover, to close it from within its content

Accessibility

Focus moves into the dialog when it opens and returns to the trigger when it closes. Focus stays inside while it is open, Escape closes it and the page behind it is hidden from screen readers.