Dialog
Client leafA modal overlay for focused tasks.
Live preview
Rendered from the installed VEXA source — the same files you receive.
Installation
Adds the component source to your project. Registry dependencies are pulled in automatically.
npx shadcn@latest add https://vexa.valfiguer.com/r/dialog.jsonInstalls to components/vexa/dialog.tsx
npm dependencies
- @radix-ui/react-dialog
Registry dependencies
- @vexa/utils
Other VEXA items this component needs. Install them first — they are resolved automatically by the CLI.
Source
Copy this into its target path, or install it with the command above.
components/vexa/dialog.tsx
"use client";
import type * as React from "react";
import * as DialogPrimitive from "@radix-ui/react-dialog";
import { cn } from "@/lib/utils";
function CloseIcon({ className }: { className?: string }) {
return (
<svg
viewBox="0 0 24 24"
fill="none"
aria-hidden="true"
className={className}
>
<path
d="M6 6l12 12M18 6 6 18"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
/>
</svg>
);
}
/**
* Dialog — a modal overlay for focused tasks.
*
* Client leaf. Radix handles the modal contract required by §14.2: focus moves
* in on open and is trapped, Escape closes, focus returns to the trigger on
* close, and background content is inert to assistive technology. Every dialog
* needs a `DialogTitle`; add a `DialogDescription` (referenced automatically)
* when extra context helps.
*/
export function Dialog(
props: React.ComponentProps<typeof DialogPrimitive.Root>,
) {
return <DialogPrimitive.Root data-slot="dialog" {...props} />;
}
export function DialogTrigger(
props: React.ComponentProps<typeof DialogPrimitive.Trigger>,
) {
return <DialogPrimitive.Trigger data-slot="dialog-trigger" {...props} />;
}
export function DialogPortal(
props: React.ComponentProps<typeof DialogPrimitive.Portal>,
) {
return <DialogPrimitive.Portal data-slot="dialog-portal" {...props} />;
}
export function DialogClose(
props: React.ComponentProps<typeof DialogPrimitive.Close>,
) {
return <DialogPrimitive.Close data-slot="dialog-close" {...props} />;
}
export function DialogOverlay({
className,
...props
}: React.ComponentProps<typeof DialogPrimitive.Overlay>) {
return (
<DialogPrimitive.Overlay
data-slot="dialog-overlay"
className={cn(
"fixed inset-0 z-50 bg-vexa-background/70 backdrop-blur-sm",
"transition-opacity duration-[var(--vexa-duration-base)] ease-[var(--vexa-ease-standard)] motion-reduce:transition-none",
"data-[state=closed]:opacity-0 data-[state=open]:opacity-100",
className,
)}
{...props}
/>
);
}
export function DialogContent({
className,
children,
...props
}: React.ComponentProps<typeof DialogPrimitive.Content>) {
return (
<DialogPortal>
<DialogOverlay />
<DialogPrimitive.Content
data-slot="dialog-content"
className={cn(
"fixed left-1/2 top-1/2 z-50 grid w-full max-w-lg -translate-x-1/2 -translate-y-1/2 gap-4",
"rounded-vexa-lg border border-vexa-border bg-vexa-surface-raised p-6 text-vexa-foreground shadow-vexa-lg",
"transition-[opacity,transform] duration-[var(--vexa-duration-base)] ease-[var(--vexa-ease-standard)] motion-reduce:transition-none",
"data-[state=closed]:scale-95 data-[state=closed]:opacity-0 data-[state=open]:scale-100 data-[state=open]:opacity-100",
"focus-visible:outline-none",
className,
)}
{...props}
>
{children}
<DialogPrimitive.Close
data-slot="dialog-close-button"
className={cn(
"absolute right-4 top-4 inline-flex size-8 items-center justify-center rounded-vexa-sm text-vexa-muted-foreground",
"transition-colors duration-[var(--vexa-duration-fast)] hover:bg-vexa-accent hover:text-vexa-foreground",
"outline-none focus-visible:ring-2 focus-visible:ring-vexa-ring focus-visible:ring-offset-2 focus-visible:ring-offset-vexa-background",
"disabled:pointer-events-none",
)}
>
<CloseIcon className="size-4" />
<span className="sr-only">Close</span>
</DialogPrimitive.Close>
</DialogPrimitive.Content>
</DialogPortal>
);
}
export function DialogHeader({
className,
...props
}: React.ComponentProps<"div">) {
return (
<div
data-slot="dialog-header"
className={cn("flex flex-col gap-1.5 text-left", className)}
{...props}
/>
);
}
export function DialogFooter({
className,
...props
}: React.ComponentProps<"div">) {
return (
<div
data-slot="dialog-footer"
className={cn(
"flex flex-col-reverse gap-2 sm:flex-row sm:justify-end",
className,
)}
{...props}
/>
);
}
export function DialogTitle({
className,
...props
}: React.ComponentProps<typeof DialogPrimitive.Title>) {
return (
<DialogPrimitive.Title
data-slot="dialog-title"
className={cn(
"text-lg font-semibold leading-none tracking-tight",
className,
)}
{...props}
/>
);
}
export function DialogDescription({
className,
...props
}: React.ComponentProps<typeof DialogPrimitive.Description>) {
return (
<DialogPrimitive.Description
data-slot="dialog-description"
className={cn("text-sm text-vexa-muted-foreground", className)}
{...props}
/>
);
}
Details
Example
tsx
<Dialog><DialogTrigger asChild><Button variant="secondary">Open settings</Button></DialogTrigger><DialogContent><DialogHeader><DialogTitle>Settings</DialogTitle><DialogDescription>Update your workspace preferences.</DialogDescription></DialogHeader></DialogContent></Dialog>Accessibility
Modal contract (§14.2): focus moves in and is trapped, Escape closes, focus returns to the trigger, background is inert. Requires a DialogTitle. Overlay/content animation respects reduced motion.