# Modal (themes.dev UI block)

A confirm dialog behind a trigger button: title, one paragraph, Cancel and the confirm button. Destructive actions get the destructive button. Black and white, dark mode, shadcn-compatible; React, Astro and plain HTML.

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

## 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`
- HTML: https://www.themes.dev/ui/r/html/modal.html (with https://www.themes.dev/ui/r/html/caseroom.css)

Page: https://www.themes.dev/ui/blocks/modal/ · Rules for agents: https://www.themes.dev/ui/AGENT.md

## Code

### modal.astro

```astro
---
import Button from '@/components/ui/button.astro';
import Dialog from '@/components/ui/dialog.astro';
import { cn } from '@/lib/utils';
import { confirmVariant, modal as c, triggerVariant } from './modal.classes';

interface Props {
  /** 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;
  class?: string;
}
const { id, triggerLabel, title, description, confirmLabel, cancelLabel = 'Cancel', destructive, class: className } = Astro.props;
---

<div class={cn(c.root, className)}>
  <Button variant={triggerVariant(destructive)} command="show-modal" commandfor={id}>{triggerLabel}</Button>
  <Dialog id={id} title={title} description={description}>
    <Fragment slot="footer">
      <Button variant="ghost" type="submit" value="cancel">{cancelLabel}</Button>
      <Button variant={confirmVariant(destructive)} type="submit" value="confirm">{confirmLabel}</Button>
    </Fragment>
  </Dialog>
</div>
```

### modal.classes.ts

```tsx
/** 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

```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>
  );
}
```
