Skip to content
  1. Keyline UI
  2. Dialog
Keyline UIComponents
  • Get started
    • Installation
    • Theming
    • Modes
    • Configuration
    • Composition
  • Actions
    • Button
    • Button group
    • Split button
    • Toggle
    • Toggle group
    • Toolbar
    • Menu
    • Menubar
    • Context menu
  • Inputs
    • Field and form
    • Checkbox
    • Checkbox group
    • Radio group
    • Switch
    • Slider
    • Segmented
    • Select
    • Native select
    • Combobox
    • Multiselect
    • Number field
    • OTP field
    • Time field
    • Date picker and calendar
    • Date range
  • Data
    • Table
    • Chart
    • Stat and meter
    • Status and badge
    • Progress
    • Skeleton
    • Empty
    • Avatar
    • Kbd
    • Marker
  • Navigation
    • Tabs
    • Breadcrumb
    • Pagination
    • Nav
    • Navigation menu
    • Command menu
  • Overlays
    • Dialog
    • Alert dialog
    • Drawer
    • Sheet
    • Popover
    • Hover card
    • Tooltip
    • Toast
    • Notice
  • Layout
    • App shell
    • Sidebar
    • Panel
    • Resizable
    • Scroll area
    • Accordion
    • Collapsible
    • Carousel
    • Inspector
    • Item
    • Direction
  • Conversation
    • Message
    • Bubble
    • Message scroller
    • Attachment
    • Questionnaire
  • Blocks
    • Shipments

Dialog

A task the page waits for. The dialog is one frame over a dimmed page: a header, a body that scrolls when it runs long, and its actions as cells sharing the bottom keyline. Focus moves in, stays in, and returns to the trigger; Escape and the close cell put it away.

Loading the live example…

"use client"

import { useState } from "react"
import { Button } from "@/components/keyline/ui/button"
import { Dialog, DialogBody, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogTrigger } from "@/components/keyline/ui/dialog"
import { Field, FieldDescription, FieldError, FieldFrame, FieldInput, FieldLabel, Form } from "@/components/keyline/ui/field"

export function NewClient() {
  const [open, setOpen] = useState(false)
  const [clients, setClients] = useState(["Lackawanna County", "Blue Cross of Pennsylvania"])

  return (
    <div className="grid gap-4">
      <ul aria-label="Clients" className="m-0 grid list-none gap-2 p-0 text-[14px]">
        {clients.map((c) => (
          <li key={c}>{c}</li>
        ))}
      </ul>
      <Dialog open={open} onOpenChange={setOpen}>
        <DialogTrigger render={<Button className="justify-self-start" />}>Add a client</DialogTrigger>
        <DialogContent>
          <DialogHeader>
            <DialogTitle>Add a client</DialogTitle>
            <DialogDescription>They join Dwight&apos;s accounts until you assign a rep.</DialogDescription>
          </DialogHeader>
          <DialogBody>
            {/* the footer's submit button names this form, so the form can stay in the scrolling body */}
            <Form
              id="new-client"
              onFormSubmit={(v) => {
                setClients((all) => [...all, String(v.company).trim()])
                setOpen(false)
              }}
            >
              <Field name="company">
                <FieldLabel>Company</FieldLabel>
                <FieldFrame>
                  <FieldInput required autoComplete="organization" />
                </FieldFrame>
                <FieldError match="valueMissing">Which company?</FieldError>
              </Field>
              <Field name="contact">
                <FieldLabel>Contact</FieldLabel>
                <FieldFrame>
                  <FieldInput autoComplete="name" />
                </FieldFrame>
                <FieldDescription>Who orders the paper.</FieldDescription>
              </Field>
            </Form>
          </DialogBody>
          <DialogFooter>
            <DialogClose render={<Button layout="cell" />}>Cancel</DialogClose>
            <Button layout="cell" variant="primary" type="submit" form="new-client">
              Add client
            </Button>
          </DialogFooter>
        </DialogContent>
      </Dialog>
    </div>
  )
}

Installation

npx shadcn@latest add @keyline-ui/dialog

Run it where your components.json is. The first time, map the @keyline-ui registry: one command, in Installation.

Usage

import { Dialog, DialogBody, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogTrigger } from "@/components/keyline/ui/dialog"
<Dialog>
  <DialogTrigger render={<Button />}>Add a client</DialogTrigger>
  <DialogContent>
    <DialogHeader>
      <DialogTitle>Add a client</DialogTitle>
      <DialogDescription>…</DialogDescription>
    </DialogHeader>
    <DialogBody>…</DialogBody>
    <DialogFooter>
      <DialogClose render={<Button layout="cell" />}>Cancel</DialogClose>
      <Button layout="cell" variant="primary" type="submit" form="new-client">Add client</Button>
    </DialogFooter>
  </DialogContent>
</Dialog>

Props

PropTypeDefaultDescription
open · defaultOpenbooleanDefault —On Dialog: control it with onOpenChange, for example to close after a form submits.
renderReactElementDefault —On DialogTrigger and DialogClose: the element to render, usually a Button.
containerHTMLElement | refDefault document.bodyOn DialogContent: where the dialog is portalled.
DialogBodydivDefault —Scrolls in a Keyline Scroll Area once the dialog reaches its maximum height. The header and footer stay put.
DialogFooterdivDefault —A row of cells. Use Button layout="cell"; a submit button can name a form in the body with form.

Built on Base UI Dialog; its other props pass through. To confirm something that can't be undone, use Alert dialog.

Sheet 48 of 73PreviousCommand menuNextAlert dialog
On this page