Skip to content
    Style: Wireframe

    Blocks · Overlays

    Modal

    A confirm dialog behind a trigger button: title, one paragraph, Cancel and the confirm button. Destructive actions get the destructive button.

    0.77% of the 2,073 sites in The UI Index have a dialog (16 sites).

    Publish 3 changes?

    The changes go live on the site within a minute. You can unpublish them from the dashboard.

    Delete Northwind site?

    The project, its deploys and its domain settings are removed. This can't be undone.

    Both previews render the same markup; the HTML tab below is that markup without a framework.

    Install

    React
    npx shadcn@latest add https://www.themes.dev/ui/r/modal.json
    Astro
    npx shadcn@latest add https://www.themes.dev/ui/r/astro/modal.json

    Or copy the files below into your project. They need clsx, tailwind-merge and the components they import, and the themes.dev UI theme. The HTML needs only the compiled CSS.

    modal.classes.ts
    /** A confirm modal: a trigger button and a dialog with a title, one paragraph, Cancel and the confirm button. The
     *  dialog itself (native <dialog>, focus trap, Escape, focus back to the trigger) is the dialog item. When the
     *  action destroys something the confirm button is the destructive variant, and the trigger matches it. */
    export const modal = {
      root: 'inline-flex',
    };
    export const confirmVariant = (destructive?: boolean) => (destructive ? ('destructive' as const) : ('default' as const));
    export const triggerVariant = (destructive?: boolean) => (destructive ? ('destructive' as const) : ('outline' as const));
    modal.tsx
    import { Button } from '@/components/ui/button';
    import { Dialog } from '@/components/ui/dialog';
    import { cn } from '@/lib/utils';
    import { confirmVariant, modal as c, triggerVariant } from './modal.classes';
    
    export interface ModalProps {
      /** Unique on the page: the dialog's id. */
      id: string;
      /** The button that opens the modal. */
      triggerLabel: string;
      title: string;
      /** One paragraph: what happens when the person confirms. */
      description: string;
      confirmLabel: string;
      cancelLabel?: string;
      /** The action deletes or can't be undone: the confirm button (and the trigger) are the destructive variant. */
      destructive?: boolean;
      className?: string;
    }
    
    // command / commandfor: the browser's Invoker Commands (dialog.client.ts fills them in where missing).
    export function Modal({ id, triggerLabel, title, description, confirmLabel, cancelLabel = 'Cancel', destructive, className }: ModalProps) {
      const opens = { command: 'show-modal', commandfor: id } as Record<string, string>;
      return (
        <div className={cn(c.root, className)}>
          <Button variant={triggerVariant(destructive)} {...opens}>{triggerLabel}</Button>
          <Dialog
            id={id}
            title={title}
            description={description}
            footer={
              <>
                <Button variant="ghost" type="submit" value="cancel">{cancelLabel}</Button>
                <Button variant={confirmVariant(destructive)} type="submit" value="confirm">{confirmLabel}</Button>
              </>
            }
          />
        </div>
      );
    }