UI DeployDesign languages Themes For agents llms.txt

Warm Editorial — overlay

Dialog Modal

Built on the native dialog element so focus trapping and the top layer come from the platform. Warm scrim, 28px corners, scroll lock, and focus restored to the trigger on close.

Open preview
Theme
Warm Editorial
Category
overlay
JavaScript
22 lines, no dependencies
Breakpoint
560px
Min width
320px

Paste this into Claude, Cursor or Copilot — or point the agent at https://ui-deploy.com/r/warm-editorial/modal/prompt.md

# Dialog Modal — Warm Editorial

Paste this whole file into Claude, Cursor or Copilot.

---

## What to build

A centred dialog on a warm scrim: eyebrow, heading, lead, one field, two pill
buttons, and a round close button in the top right. 28px corners, `--bg2` surface.

**Build it on the native `<dialog>` element.** Focus trapping, the top layer and
Escape-to-close come from the platform; reimplementing them by hand is how modals
end up inaccessible.

---

## Tokens

```css
:root{
  --bg2:#F6F3ED; --bg3:#E4DED2; --border:#DAD3C6; --border2:#C6BDAC;
  --text:#22201D; --text2:#5C574F; --text3:#8C857A;
  --accent:#A38A6A; --accent-text:#7D6644; --accent-s:rgba(163,138,106,.12);
  --radius:28px; --radius-sm:12px; --radius-pill:999px;
  --shadow-md:0 18px 48px -24px rgba(34,32,29,.22);
}
[data-theme="dark"]{
  --bg2:#1E1A17; --bg3:#262119; --border:#2E2822; --border2:#40382F;
  --text:#EDE7DD; --text2:#A79E92; --text3:#8A8177;
  --accent:#B39B7C; --accent-text:#C0A888; --accent-s:rgba(179,155,124,.13);
  --shadow-md:0 18px 48px -24px rgba(0,0,0,.7);
}
```

---

## Rules — these are not preferences

1. **`showModal()`, never `show()`.** Only `showModal` gives the focus trap, the
   backdrop and the top layer. `show()` looks similar and is not the same thing.
2. **Restore focus to the trigger on close.** Listen on the `close` event rather
   than the close button — `close` also fires for Escape and for a backdrop click,
   so one handler covers every path. Without this, keyboard users are dumped at
   the top of the document.
3. **The scrim is warm**, `rgba(21,18,16,.55)`. A neutral black scrim over this
   palette reads as grey and kills the warmth of everything behind it.
4. **Lock body scroll while open**, and release it in the same `close` handler.
5. **Link the heading with `aria-labelledby`** pointing at the title's id.
6. **A backdrop click is `e.target === dialog`** — the dialog element fills the top
   layer, so a click that lands on it rather than a child came from the backdrop.
7. **`margin:auto`** centres a `<dialog>` in both axes. Do not reach for
   `position:fixed` and transforms.
8. Buttons are pills. Inputs are 12px soft corners, not pills. Icons inline SVG.
9. Below 560px the buttons stack full width.

---

## Adapting it

- **Confirm dialog** — drop the field, keep both buttons, and give the destructive
  action `--accent` fill with `--on-accent` text. Never a red band; this palette
  has no red.
- **Long content** — put `max-height:85dvh; overflow-y:auto` on `.we-dialog-body`,
  not on the dialog, so the close button stays pinned.
- **A form that submits** — keep `method="dialog"` only on the close form. A real
  submit needs its own `<form>` with an action.

---

## Reference implementation

See `code.html` — 22 lines of dependency-free JS.

Dialog Modal — Warm Editorial

Paste this whole file into Claude, Cursor or Copilot.


What to build

A centred dialog on a warm scrim: eyebrow, heading, lead, one field, two pill buttons, and a round close button in the top right. 28px corners, --bg2 surface.

Build it on the native <dialog> element. Focus trapping, the top layer and Escape-to-close come from the platform; reimplementing them by hand is how modals end up inaccessible.


Tokens

:root{
  --bg2:#F6F3ED; --bg3:#E4DED2; --border:#DAD3C6; --border2:#C6BDAC;
  --text:#22201D; --text2:#5C574F; --text3:#8C857A;
  --accent:#A38A6A; --accent-text:#7D6644; --accent-s:rgba(163,138,106,.12);
  --radius:28px; --radius-sm:12px; --radius-pill:999px;
  --shadow-md:0 18px 48px -24px rgba(34,32,29,.22);
}
[data-theme="dark"]{
  --bg2:#1E1A17; --bg3:#262119; --border:#2E2822; --border2:#40382F;
  --text:#EDE7DD; --text2:#A79E92; --text3:#8A8177;
  --accent:#B39B7C; --accent-text:#C0A888; --accent-s:rgba(179,155,124,.13);
  --shadow-md:0 18px 48px -24px rgba(0,0,0,.7);
}

Rules — these are not preferences

  1. showModal(), never show(). Only showModal gives the focus trap, the backdrop and the top layer. show() looks similar and is not the same thing.
  2. Restore focus to the trigger on close. Listen on the close event rather than the close button — close also fires for Escape and for a backdrop click, so one handler covers every path. Without this, keyboard users are dumped at the top of the document.
  3. The scrim is warm, rgba(21,18,16,.55). A neutral black scrim over this palette reads as grey and kills the warmth of everything behind it.
  4. Lock body scroll while open, and release it in the same close handler.
  5. Link the heading with aria-labelledby pointing at the title's id.
  6. A backdrop click is e.target === dialog — the dialog element fills the top layer, so a click that lands on it rather than a child came from the backdrop.
  7. margin:auto centres a <dialog> in both axes. Do not reach for position:fixed and transforms.
  8. Buttons are pills. Inputs are 12px soft corners, not pills. Icons inline SVG.
  9. Below 560px the buttons stack full width.

Adapting it

  • Confirm dialog — drop the field, keep both buttons, and give the destructive

action --accent fill with --on-accent text. Never a red band; this palette has no red.

  • Long content — put max-height:85dvh; overflow-y:auto on .we-dialog-body,

not on the dialog, so the close button stays pinned.

  • A form that submits — keep method="dialog" only on the close form. A real

submit needs its own <form> with an action.


Reference implementation

See code.html — 22 lines of dependency-free JS.