shadcn/ui
Wire @lamstack/react-dialog’s templates to shadcn/ui’s Dialog (a styled wrapper around
Radix UI’s dialog primitive). The component below was generated with the real shadcn
CLI (npx shadcn@latest add dialog) — its exact markup/class names come from the
registry, not hand-written from memory.
Live demo
Try all four ways to open a dialog — the log below shows exactly what each call resolved with:
alert()— acknowledgement only, resolvesvoidonce dismissed.confirm()— resolvestrue/falsedepending on which button was pressed.prompt()— resolves the typed string, ornullif cancelled.open(custom)— opensRatingDialog, built from the same shadcnDialogprimitives plus alucide-reactstar icon, with its own payload/result type ({ itemName: string }→number | null) that has nothing to do with the three built-in kinds above.
Preview
Action buttons call onClose(result) directly rather than being wrapped in shadcn’s
<DialogClose> — that would fire onOpenChange(false) and the button’s own onClick,
resolving the promise twice with two different results.
A custom (non-template) dialog
The open(custom) button above opens this — same shadcn Dialog primitives, but a
component with its own payload/result types instead of one of the three built-in kinds:
import * as React from 'react';
import { Star } from 'lucide-react';
import { useDialogs } from '@lamstack/react-dialog';
import type { DialogProps } from '@lamstack/react-dialog';
import { Button } from '@/components/ui/button';
import {
Dialog,
DialogContent,
DialogFooter,
DialogHeader,
DialogTitle,
} from '@/components/ui/dialog';
interface ItemPayload {
itemName: string;
}
function RatingDialog({ payload, open, onClose }: DialogProps<ItemPayload, number | null>) {
const [rating, setRating] = React.useState(0);
return (
<Dialog open={open} onOpenChange={(next) => !next && onClose(null)}>
<DialogContent>
<DialogHeader>
<DialogTitle>Rate “{payload.itemName}”</DialogTitle>
</DialogHeader>
<div className="flex gap-1">
{[1, 2, 3, 4, 5].map((star) => (
<button key={star} onClick={() => setRating(star)}>
<Star
className={star <= rating ? 'fill-primary text-primary' : 'text-muted-foreground'}
size={20}
/>
</button>
))}
</div>
<DialogFooter>
<Button variant="outline" onClick={() => onClose(null)}>
Skip
</Button>
<Button disabled={rating === 0} onClick={() => onClose(rating)}>
Submit
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
);
}
function RateButton() {
const { open } = useDialogs();
return (
<Button
onClick={async () => console.log(await open(RatingDialog, { itemName: 'Acme Widget' }))}
>
Rate this
</Button>
);
}Setup
npx shadcn@latest init
npx shadcn@latest add dialogThe CLI needs a resolvable import alias (@/*) in tsconfig.json — if it fails with
“Could not find valid path aliases”, add one:
{
"compilerOptions": {
"paths": { "@/*": ["./*"] }
}
}Dark mode
shadcn’s generated tailwind.css defines :root and .dark CSS variable blocks (colors
as oklch() values) — it already assumes class-based dark mode, matching this docs site’s
.dark class toggle with no extra configuration needed (unlike the MUI guide,
which needed colorSchemeSelector: 'class' set explicitly).