Primitives
Dialog
Modal dialog for focused tasks, confirmations, and custom panels.
Composition
Anatomy
- <Dialog>
- <DialogTrigger />
- <DialogContent>
- <DialogHeader>
- <DialogTitle />
- <DialogDescription />
- <DialogBody>
- <Input />
- <DialogFooter>
- <DialogClose />
- <DialogHeader>
Installation
First install and activate one skin. Core deliberately contains no visual token defaults.
The Control UI source installs this primitive from src/registry/sources/control-ui/ui/dialog.tsx.
npx shadcn@latest add https://control-ui.dev/r/dialog.jsonDependencies
Popup parts
Buttonsrc/registry/sources/control-ui/popup-parts.tsxSupportbuttonPrimitiveRaw code
Primary installed source
"use client";import { Dialog as DialogPrimitive } from "@base-ui/react/dialog";import type { ComponentProps, CSSProperties, ReactNode } from "react";import type { OpenChangeEventDetails } from "@/components/control-ui/control-props";import type { PopupKnobStyle } from "@/components/control-ui/knob-contracts/popup-knobs";import { cn } from "@/components/control-ui/lib/cn";import { PopupCloseButton } from "@/components/control-ui/popup-parts";import { controlEffectsAttribute, skinAdornment } from "@/components/control-ui/skin";import { useSkin } from "@/components/control-ui/skin-provider";import type { ButtonProps } from "@/components/control-ui/ui/button";import { Button } from "@/components/control-ui/ui/button";export type DialogProps = { modal?: boolean | "trap-focus"; children?: ReactNode; open?: boolean; defaultOpen?: boolean; onOpenChange?: (open: boolean, eventDetails: OpenChangeEventDetails) => void;};export type DialogContentProps = Omit<ComponentProps<"div">, "style"> & { style?: CSSProperties & PopupKnobStyle } & { showCloseButton?: boolean; closeLabel?: string;};export function Dialog(props: DialogProps) { return <DialogPrimitive.Root {...props} />;}export function DialogTrigger({ className, ...props}: ComponentProps<typeof DialogPrimitive.Trigger> & { style?: CSSProperties & PopupKnobStyle }) { return <DialogPrimitive.Trigger data-control-ui="dialog" data-popup-kind="dialog" data-slot="trigger" className={className} {...props} />;}export type DialogCloseProps = Omit<ComponentProps<typeof DialogPrimitive.Close>, "render"> & Pick<ButtonProps, "variant" | "size" | "tone" | "iconOnly">;export function DialogClose({ className, children, variant = "surface", size = "sm", tone = "neutral", iconOnly, ...props}: DialogCloseProps) { return ( <DialogPrimitive.Close {...props} render={(renderProps) => ( <Button {...renderProps} data-popup-part="close" variant={variant} size={size} tone={tone} iconOnly={iconOnly} className={cn(renderProps.className, className)} > {children} </Button> )} /> );}export function DialogContent({ className, children, showCloseButton = true, closeLabel = "Close", ...props }: DialogContentProps) { const skin = useSkin(); return ( <DialogPrimitive.Portal> <DialogPrimitive.Backdrop data-control-ui="dialog" data-popup-kind="dialog" data-slot="backdrop" data-control-family="popup" data-popup-part="backdrop" data-skin={skin.id} data-effects={controlEffectsAttribute(skin.effects)} className="fixed inset-0 z-(--z-overlay)" /> <DialogPrimitive.Popup data-skin={skin.id} data-effects={controlEffectsAttribute(skin.effects)} data-control-ui="dialog" data-popup-kind="dialog" data-slot="content" data-control-family="popup" data-popup-part="surface" data-surface="modal" className={cn("fixed left-1/2 top-[12vh] z-(--z-modal) grid w-[calc(100%-2rem)] max-w-lg -translate-x-1/2", className)} {...props} > {skinAdornment(skin, "dialog", "titlebar", {})} {children} {showCloseButton ? <DialogPrimitive.Close render={<PopupCloseButton label={closeLabel} />} /> : null} </DialogPrimitive.Popup> </DialogPrimitive.Portal> );}export function DialogHeader({ className, ...props }: ComponentProps<"div"> & { style?: CSSProperties & PopupKnobStyle }) { return ( <div data-control-ui="dialog" data-control-family="popup" data-popup-kind="dialog" data-slot="header" className={cn("grid", className)} {...props} /> );}export function DialogBody({ className, ...props }: ComponentProps<"div"> & { style?: CSSProperties & PopupKnobStyle }) { return ( <div data-control-ui="dialog" data-control-family="popup" data-popup-kind="dialog" data-slot="body" className={cn("grid", className)} {...props} /> );}export function DialogFooter({ className, ...props }: ComponentProps<"div"> & { style?: CSSProperties & PopupKnobStyle }) { return ( <div data-control-ui="dialog" data-control-family="popup" data-popup-kind="dialog" data-slot="footer" className={cn("flex flex-col-reverse sm:flex-row sm:justify-end", className)} {...props} /> );}export function DialogTitle({ className, ...props }: ComponentProps<"h2"> & { style?: CSSProperties & PopupKnobStyle }) { return ( <DialogPrimitive.Title data-control-ui="dialog" data-control-family="popup" data-popup-kind="dialog" data-slot="title" className={className} {...props} /> );}export function DialogDescription({ className, ...props }: ComponentProps<"p"> & { style?: CSSProperties & PopupKnobStyle }) { return ( <DialogPrimitive.Description data-control-ui="dialog" data-control-family="popup" data-popup-kind="dialog" data-slot="description" className={className} {...props} /> );}Knobs
Typed custom properties the recipe paints with. Set one on the root — style, a utility class, or a skin — and every slot inherits it.
--cui-popup-* · 44 knobsHow the cascade resolves--cui-popup-title-foreground<color>var(--cui-popup-foreground)--cui-popup-title-font-size<length-percentage>var(--text-heading-3)--cui-popup-title-font-weight<number>var(--font-weight-semibold)--cui-popup-title-line-height<number>var(--text-heading-3--line-height)--cui-popup-title-letter-spacing<length>-0.025em--cui-popup-description-foreground<color>var(--muted-foreground)--cui-popup-close-inset<length-percentage>calc(var(--spacing) * 3)--cui-popup-item-inset<length-percentage>calc(var(--spacing) * 8)--cui-popup-label-padding-block<length-percentage>var(--cui-popup-item-padding-block)--cui-popup-label-padding-inline<length-percentage>var(--cui-popup-item-padding-inline)--cui-popup-label-font-size<length-percentage>var(--text-micro)--cui-popup-label-font-weight<number>var(--font-weight-medium)--cui-popup-label-letter-spacing<length>0.08em--cui-popup-label-text-transformnone | uppercase | lowercase | capitalizeuppercase--cui-popup-separator-inset<length-percentage>calc(var(--popover-padding) * -1)--cui-popup-shortcut-font-size<length-percentage>var(--text-body)--cui-popup-shortcut-letter-spacing<length>0.02em--cui-popup-status-success-foreground<color>var(--success-text)--cui-popup-status-warning-foreground<color>var(--warning-text)--cui-popup-status-info-foreground<color>var(--info-text)--cui-popup-status-error-foreground<color>var(--destructive)--cui-popup-radius<length-percentage>var(--radius-popover)--cui-popup-background<color>var(--popover)--cui-popup-foreground<color>var(--popover-foreground)--cui-popup-border-color<color>var(--control-rim)--cui-popup-border-width<length>var(--control-rim-width)--cui-popup-shadow*var(--shadow-pop)--cui-popup-backdrop-filter*blur(var(--backdrop-blur-popover))--cui-popup-backdrop-background<color>oklch(from var(--foreground) l c h / var(--overlay-opacity))--cui-popup-backdrop-blur<length>var(--backdrop-blur-overlay)--cui-popup-item-radius<length-percentage>var(--radius-popup-item-fit)--cui-popup-item-font-size<length-percentage>var(--text-body)--cui-popup-item-icon-size<length-percentage>1rem--cui-popup-item-padding-block<length-percentage>calc(var(--spacing) * 1)--cui-popup-item-padding-inline<length-percentage>calc(var(--padding-x) * 0.5)--cui-popup-item-min-height<length-percentage>max(var(--target-min), var(--control-h-xs))--cui-popup-item-highlight-shadow*inset 0 0 0 1px oklch(from var(--foreground) l c h / 0.35)--cui-popup-item-foreground<color>var(--foreground)--cui-popup-item-highlight-background<color>var(--hover-fill)--cui-popup-item-highlight-foreground<color>var(--cui-popup-item-foreground)--cui-popup-item-highlight-muted-foreground<color>var(--muted-foreground)--cui-popup-item-disabled-opacity<number>var(--disabled-opacity)--cui-popup-separator-color<color>var(--border)--cui-popup-shortcut-foreground<color>var(--muted-foreground)