Skip to Content
GuidesTailwind CSS

Tailwind CSS

@lamstack/react-dialog doesn’t render anything itself, so styling the alert/confirm/ prompt templates with Tailwind utility classes is just… writing a normal React component with className props.

Live demo

Try all four ways to open a dialog — the log below each result shows exactly what each call resolved with:

  • alert() — acknowledgement only, resolves void once dismissed.
  • confirm() — resolves true/false depending on which button was pressed.
  • prompt() — resolves the typed string, or null if cancelled.
  • open(custom) — opens RatingDialog, a component with its own payload ({ itemName: string }) and result (number | null) that has nothing to do with the three built-in kinds above — this is the escape hatch for any dialog shape you need.

alert()

Acknowledgement only — resolves void once dismissed.

confirm()

Resolves true/false depending on which button was pressed.

prompt()

Resolves the typed string, or null if cancelled.

open(custom)

Any component works, not just alert/confirm/prompt — here, a rating.

Chaining dialogs with async/await

Each call blocks until its dialog closes, so a multi-step flow — confirm, then prompt, then alert — reads top-to-bottom. No nested callbacks, no extra state for tracking which dialog is currently open.

Loading state via async onClose()

onClose runs before the promise resolves — the dialog awaits it itself to show "Saving…" with no extra state passed in from here.

alert and prompt templates follow the same shape — see the API reference for their exact payload types.

A custom (non-template) dialog

The open(custom) button above opens this — a component with its own payload/result types, passed directly to open() instead of going through templates:

import * as React from 'react'; import { useDialogs } from '@lamstack/react-dialog'; import type { DialogProps } from '@lamstack/react-dialog'; interface ItemPayload { itemName: string; } function RatingDialog({ payload, open, onClose }: DialogProps<ItemPayload, number | null>) { const [rating, setRating] = React.useState(0); if (!open) return null; return ( <div className="fixed inset-0 z-50 flex items-center justify-center bg-black/40 p-4"> <div className="w-full max-w-sm rounded-lg border bg-white p-5 dark:bg-neutral-900"> <h3 className="font-semibold">Rate "{payload.itemName}"</h3> <div className="mt-3 flex gap-1"> {[1, 2, 3, 4, 5].map((star) => ( <button key={star} onClick={() => setRating(star)}> {star <= rating ? '★' : '☆'} </button> ))} </div> <div className="mt-5 flex justify-end gap-2"> <button onClick={() => onClose(null)}>Skip</button> <button disabled={rating === 0} onClick={() => onClose(rating)}> Submit </button> </div> </div> </div> ); } function RateButton() { const { open } = useDialogs(); return ( <button onClick={async () => { const rating = await open(RatingDialog, { itemName: 'Acme Widget' }); console.log(rating); // number, or null if skipped }} > Rate this </button> ); }

Nothing about open() is special-cased for alert/confirm/prompt — those three just call open(templates.alert, ...)/open(templates.confirm, ...)/open(templates.prompt, ...) internally. See the API reference for the full breakdown.

Dark mode

This docs site (and this demo) toggle a dark class on <html> via the theme switcher in the navbar. If your app does the same, add this once to your global CSS so Tailwind’s dark: variant follows that class instead of the OS prefers-color-scheme media query:

@import 'tailwindcss'; @custom-variant dark (&:where(.dark, .dark *));
Last updated on