Skip to content
Kappa
Dialog
@dicehub/kappav0.4.2

Dialog

Presents a focused task or decision in an accessible modal or alert-dialog layer.

<script setup>
import { Button } from "@dicehub/kappa/components/button";
import { Dialog } from "@dicehub/kappa/components/dialog";
</script>

<template>
  <Dialog.Root>
    <Dialog.Trigger as-child>
      <Button variant="outline">Edit run details</Button>
    </Dialog.Trigger>
    <Dialog.Content>
      <Dialog.Header>
        <Dialog.Title>Edit run details</Dialog.Title>
        <Dialog.Description>Change the label used in reports and result archives.</Dialog.Description>
      </Dialog.Header>
      <label>Run label <input value="PIMPLE baseline 07" /></label>
      <Dialog.Footer>
        <Dialog.Close as-child><Button variant="secondary">Cancel</Button></Dialog.Close>
        <Dialog.Close as-child><Button variant="primary">Save changes</Button></Dialog.Close>
      </Dialog.Footer>
    </Dialog.Content>
  </Dialog.Root>
</template>

Installation

Barrel

import {
  Dialog,
  DialogRoot,
  DialogContent,
  DialogTitle,
  DialogDescription,
  DialogClose,
  type DialogProps,
  type DialogSize,
} from "@dicehub/kappa";

Granular

import {
  Dialog,
  DialogRoot,
  DialogContent,
  DialogTitle,
  DialogDescription,
  DialogClose,
  type DialogProps,
  type DialogSize,
} from "@dicehub/kappa/components/dialog";

Usage

Put Trigger and Content inside Dialog.Root.Content supplies the portal layers and close control. UseHeader and Footer for stable title and action regions.

<script setup>
import { Button } from "@dicehub/kappa/components/button";
import { Dialog } from "@dicehub/kappa/components/dialog";
</script>

<template>
  <Dialog.Root>
    <Dialog.Trigger as-child><Button>Open dialog</Button></Dialog.Trigger>
    <Dialog.Content>
      <Dialog.Header>
        <Dialog.Title>Simulation summary</Dialog.Title>
        <Dialog.Description>Run 4189 completed successfully.</Dialog.Description>
      </Dialog.Header>
      <p>Review the generated fields and reports.</p>
      <Dialog.Footer>
        <Dialog.Close as-child><Button variant="primary">Done</Button></Dialog.Close>
      </Dialog.Footer>
    </Dialog.Content>
  </Dialog.Root>
</template>

Composition

Typical part hierarchy
Dialog.Root
├── Dialog.Trigger
└── Dialog.Content
    ├── Dialog.Header
    │   ├── Dialog.Title
    │   └── Dialog.Description
    ├── Content
    ├── Dialog.Footer
    └── Dialog.Close

Ark UI Dialog owns open state, modality, focus trapping, dismissal, aria relationships, presence, and nested layers. Kappa owns the compound API, composed portal surface, close control, layout, sizing, semantic tokens, and motion.

  • Dialog and Dialog.Root are the same root component, matching other Kappa compound APIs.
  • Content teleports to body; pass :teleport="false" for an in-place test or constrained integration.
  • Backdrop and Positioner remain public for selectors and advanced styling, while Content composes them.
  • Use as-child on Trigger and Close when a Kappa Button owns the visual element.
<script setup>
import {
  DialogRoot,
  DialogTrigger,
  DialogContent,
  DialogHeader,
  DialogTitle,
  DialogDescription,
  DialogFooter,
  DialogClose,
} from "@dicehub/kappa/components/dialog";
</script>

<template>
  <DialogRoot>
    <DialogTrigger>Open</DialogTrigger>
    <DialogContent>
      <DialogHeader>
        <DialogTitle>Title</DialogTitle>
        <DialogDescription>Description</DialogDescription>
      </DialogHeader>
      <DialogFooter><DialogClose>Close</DialogClose></DialogFooter>
    </DialogContent>
  </DialogRoot>
</template>

Reference Differences

The main Dialog export is the root, not the content surface. It supports header, footer, custom-close, no-close, fixed-action, scrolling, and RTL composition patterns. The Kappa close label is configurable for localization.

Behavior and Focus

  • The first focusable element receives focus. Use initialFocusEl when the safest target is not first.
  • Escape and allowed outside interactions close a normal dialog. Focus returns to the opening trigger.
  • role="alertdialog" disables outside dismissal by default. Put focus on the least destructive action.
  • disablePointerDismissal protects a confirmation without changing its role.
  • Keep Dialog.Root mounted. Use controlled state plus the default lazy mount and exit unmount behavior.
  • For a child popup that teleports to body, use its lazy mount and exit unmount options. This keeps the popup in the active modal accessibility tree.
  • For a non-modal surface, set :modal="false", :trap-focus="false", and :show-backdrop="false".

Examples

Basic

Content supplies the portal, backdrop, centered positioner, surface, and default close control.

<Dialog.Root>
  <Dialog.Trigger as-child><Button>Open dialog</Button></Dialog.Trigger>
  <Dialog.Content>
    <Dialog.Header>
      <Dialog.Title>Simulation summary</Dialog.Title>
      <Dialog.Description>Run 4189 completed in 02:14:38.</Dialog.Description>
    </Dialog.Header>
  </Dialog.Content>
</Dialog.Root>

Alert Dialog

Use alertdialog for a destructive decision. Kappa disables outside dismissal and the example removes the corner close control.

<Dialog.Root role="alertdialog">
  <Dialog.Trigger as-child><Button variant="destructive">Delete run</Button></Dialog.Trigger>
  <Dialog.Content :show-close-button="false">
    <Dialog.Header>
      <Dialog.Title>Delete run 4189?</Dialog.Title>
      <Dialog.Description>This action cannot be undone.</Dialog.Description>
    </Dialog.Header>
    <Dialog.Footer>
      <Dialog.Close as-child><Button variant="secondary">Keep run</Button></Dialog.Close>
      <Dialog.Close as-child><Button variant="destructive">Delete permanently</Button></Dialog.Close>
    </Dialog.Footer>
  </Dialog.Content>
</Dialog.Root>

Confirmation

disablePointerDismissal keeps an ordinary dialog open after backdrop clicks. Escape still works unless closeOnEscape is false.

<Dialog.Root disable-pointer-dismissal>
  <Dialog.Trigger as-child><Button variant="warning">Stop solver</Button></Dialog.Trigger>
  <Dialog.Content :show-close-button="false">
    <Dialog.Title>Stop the active solver?</Dialog.Title>
    <Dialog.Footer>
      <Dialog.Close as-child><Button>Continue run</Button></Dialog.Close>
      <Dialog.Close as-child><Button variant="warning">Stop solver</Button></Dialog.Close>
    </Dialog.Footer>
  </Dialog.Content>
</Dialog.Root>

Controlled

Bind v-model:open when application state must own visibility. Do not conditionally remove Dialog.Root.

State: closed
<script setup>
import { ref } from "vue";
import { Dialog } from "@dicehub/kappa/components/dialog";

const open = ref(false);
</script>

<template>
  <Button @click="open = true">Open controlled dialog</Button>
  <Dialog.Root v-model:open="open">
    <Dialog.Content>
      <Dialog.Title>Controlled state</Dialog.Title>
      <Button @click="open = false">Done</Button>
    </Dialog.Content>
  </Dialog.Root>
</template>

Sizes and Custom Width

Use sm, base, lg, or xl. Override --kappa-dialog-inline-size for a one-off cap; the viewport limit still applies.

<Dialog.Content size="sm">...</Dialog.Content>
<Dialog.Content size="base">...</Dialog.Content>
<Dialog.Content size="lg">...</Dialog.Content>
<Dialog.Content size="xl">...</Dialog.Content>

<Dialog.Content style="--kappa-dialog-inline-size: 40rem">...</Dialog.Content>

Custom Close Button

The close slot replaces the built-in icon. Keep Dialog.Close so Ark UI still owns dismissal and focus restoration.

<Dialog.Content>
  <template #close>
    <Dialog.Close as-child>
      <Button size="xs" variant="ghost">Dismiss</Button>
    </Dialog.Close>
  </template>
  <Dialog.Title>Custom close control</Dialog.Title>
</Dialog.Content>

No Close Button

Disable the corner control when a visible action row gives the user a clearer next step.

<Dialog.Content :show-close-button="false">
  <Dialog.Title>Review required</Dialog.Title>
  <Dialog.Footer>
    <Dialog.Close as-child><Button>Acknowledge</Button></Dialog.Close>
  </Dialog.Footer>
</Dialog.Content>

Native Form Control

Native inputs and selects keep their normal keyboard behavior inside the focus trap.

<Dialog.Content>
  <Dialog.Header>
    <Dialog.Title>Configure resource</Dialog.Title>
    <Dialog.Description>Choose the target region.</Dialog.Description>
  </Dialog.Header>
  <label>
    Region
    <select v-model="region">
      <option value="eu-central">EU Central · Frankfurt</option>
      <option value="us-east">US East · Virginia</option>
    </select>
  </label>
</Dialog.Content>

With Combobox

Lazy mount a teleported Kappa Combobox so its popup stays above the dialog and inside the active modal accessibility tree.

<Dialog.Content size="lg">
  <Dialog.Header>
    <Dialog.Title>Move simulation</Dialog.Title>
  </Dialog.Header>
  <Combobox
    v-model="region"
    :items="regions"
    label="Destination region"
    lazy-mount
    placeholder="Search regions"
    unmount-on-exit
  />
</Dialog.Content>

Give the content a block size and let a min-block-size: 0 body scroll. Header and Footer remain fixed flex children.

Scrollable Content

Keep long copy in a named scroll region. The surface stays inside the dynamic viewport limit.

<Dialog.Content>
  <Dialog.Header><Dialog.Title>Solver notes</Dialog.Title></Dialog.Header>
  <div class="scroll-region" tabindex="0">Long content...</div>
</Dialog.Content>

<style scoped>
.scroll-region { max-block-size: 16rem; overflow-y: auto; }
</style>

Nested Dialog

Place another Root inside the parent Content. Ark UI coordinates focus, dismissal, aria hiding, and layer order.

<Dialog.Root>
  <Dialog.Trigger>Open parent</Dialog.Trigger>
  <Dialog.Content>
    <Dialog.Title>Run settings</Dialog.Title>
    <Dialog.Root>
      <Dialog.Trigger>Edit advanced settings</Dialog.Trigger>
      <Dialog.Content size="sm">
        <Dialog.Title>Advanced settings</Dialog.Title>
      </Dialog.Content>
    </Dialog.Root>
  </Dialog.Content>
</Dialog.Root>

Right-to-left

Logical spacing keeps the close control and action layout correct in RTL content.

<Dialog.Content dir="rtl">
  <Dialog.Header>
    <Dialog.Title>نتائج المحاكاة</Dialog.Title>
    <Dialog.Description>النتائج جاهزة للمراجعة.</Dialog.Description>
  </Dialog.Header>
  <Dialog.Footer><Dialog.Close>تم</Dialog.Close></Dialog.Footer>
</Dialog.Content>

Accessibility

  • Render a visible Title and normally a Description. Use ariaLabel only when no title is visible.
  • Every icon-only close control needs an accessible label. closeLabel localizes the built-in control.
  • Tab and Shift+Tab stay inside a modal dialog. Escape closes it when enabled.
  • Do not use an alert dialog for ordinary information. Reserve it for an urgent decision or acknowledgement.
  • Give long scroll regions tabindex="0" when keyboard users must scroll them independently.
  • All focus indicators remain visible. Open and close motion stops under reduced-motion preferences.

API Reference

Dialog.Root

PropTypeDefaultDescription
open / defaultOpenbooleanfalseControlled or initial visibility. Pair open with update:open.
role"dialog" | "alertdialog""dialog"Sets the accessible role. Alert dialogs disable outside dismissal by default.
ariaLabelstring-Accessible name when no visible Title is rendered.
closeOnEscapebooleantrueCloses the top dialog when Escape is pressed.
closeOnInteractOutsidebooleantrueCloses after an allowed outside interaction.
disablePointerDismissalbooleanfalseForces outside dismissal off without changing the dialog role.
modalbooleantrueBlocks pointer and assistive technology access outside the dialog.
trapFocusbooleantrueKeeps keyboard focus inside while open.
preventScrollbooleantruePrevents the page behind the modal from scrolling.
restoreFocusbooleantrueReturns focus to the opening trigger or finalFocusEl.
initialFocusEl / finalFocusEl() => HTMLElement | null-Overrides initial and restored focus targets.
lazyMount / unmountOnExitbooleantrueDefers dialog DOM and removes it after the exit motion.
triggerValue / defaultTriggerValuestring | nullnullTracks which value-bearing Trigger opened the dialog.
persistentElements(() => Element | null)[]-Outside elements that stay interactive and do not dismiss the layer.
id / idsstring / objectgeneratedOverrides machine and individual part identifiers.

Dialog.Content

PropTypeDefaultDescription
size"sm" | "base" | "lg" | "xl""base"Sets the preferred inline size while keeping the viewport cap.
showCloseButtonbooleantrueRenders the built-in corner close control.
closeLabelstring"Close dialog"Localizes the built-in close control's accessible name.
showBackdropbooleantrueRenders the dimmed backdrop. Disable it with modal=false for a non-modal surface.
teleportbooleantrueMoves the layers out of clipping and stacking contexts.
teleportToTeleport target"body"Sets the Vue Teleport destination.

Parts

PartElementDescription
Dialog.RootrenderlessOwns open state, focus, modality, dismissal, and trigger state.
Dialog.RootProviderrenderlessConnects parts to an external useDialog machine.
Dialog.TriggerbuttonOpens the dialog; asChild merges behavior onto a Kappa Button.
Dialog.ContentdivComposes Teleport, Backdrop, Positioner, surface, and optional close control.
Dialog.BackdropdivPublic dimmed layer used by the composed Content.
Dialog.PositionerdivPublic viewport layer used by the composed Content.
Dialog.Header / FooterdivKappa layout parts for stable title and action regions.
Dialog.Titleh2Visible accessible name connected to Content.
Dialog.DescriptionpVisible accessible description connected to Content.
Dialog.Close / CloseTriggerbuttonDismisses the layer; renders a localized icon when no slot is provided.
Dialog.ContextrenderlessExposes the unwrapped dialog API to its slot.

Events

EventPayloadDescription
update:openbooleanDrives v-model:open.
openChange{ open: boolean }Reports every visibility change.
update:triggerValuestring | nullDrives v-model:triggerValue.
triggerValueChange{ triggerValue: string | null }Reports the active value-bearing trigger.
escapeKeyDownKeyboardEventFires when Escape reaches the layer.
focusOutside / interactOutside / pointerDownOutsideArk outside eventLets consumers inspect or prevent outside behavior.
requestDismissDialogRequestDismissEventFires when a parent layer requests nested dismissal.
exitCompletevoidFires after the closed-state motion completes.

Exports

ExportDescription
DialogCompound root API exposing every named part.
DialogRoot / DialogContent / DialogClose …Named unaugmented component exports.
DialogProps / DialogContentProps / DialogSizePublic prop and variant contracts.
DialogOpenChangeDetails and outside-event typesTyped Ark UI event payloads.
DIALOG_SIZES / DIALOG_ROLESReadonly supported-value lists.
useDialog / useDialogContextArk UI hooks for external state and descendant access.
dialogAnatomyArk UI part anatomy metadata.