chrome

<tc-modal>

Modal dialog backed by the native `<dialog>` element. Built-in focus trap, Escape to close, and backdrop dismiss.

import "@ra9/tan-compose-kit/modal"

Props

NameTypeDefaultDescription
openbooleanfalseWhether the dialog is open. Reflects to the host. Set to `true` to show, `false` to close.
titlestring""Optional header title. When empty, the header bar is omitted unless `dismissible` is true.
dismissiblebooleantrueAllow Escape and backdrop click to close. Disable for confirmation flows that require an explicit choice.
widthstring"min(560px, 92vw)"Dialog width. Any CSS length.

Events

EventDetailWhen
tc-close{ reason: "backdrop" | "escape" | "button" | "api" }Fires when the dialog is closed, with the trigger that caused it.

Slots

SlotDescription
(default)Main body content.
footerAction row (e.g. Cancel / Confirm buttons). Rendered with a divider above when present.

CSS variables

VariableDefaultDescription
--tc-modal-surfacevar(--tc-color-surface, #ffffff)Dialog background.
--tc-modal-inkvar(--tc-color-ink, #14171f)Body text color.
--tc-modal-rulevar(--tc-color-rule, #ece5d3)Header + footer divider.
--tc-modal-softvar(--tc-color-ink-soft, #5a6072)Close-button color.
--tc-modal-radiusvar(--tc-radius-lg, 12px)Corner radius.
--tc-modal-backdroprgba(20, 23, 31, 0.5)Backdrop tint.
--tc-modal-shadowvar(--tc-shadow-lg, …)Dialog drop shadow.
--tc-modal-fontvar(--tc-font-sans, …)Font family.

Examples

Basic usage

Toggle open to show or hide. The dialog renders into the top layer, above everything else.

<tc-button id="open-confirm">Delete</tc-button> <tc-modal id="confirm" title="Delete this post?"> <p>This action is permanent. The post will be removed from your blog and feed.</p> <div slot="footer"> <tc-button variant="ghost" id="cancel">Cancel</tc-button> <tc-button variant="danger" id="confirm-btn">Delete</tc-button> </div> </tc-modal> <script type="module"> const dlg = document.getElementById("confirm"); document.getElementById("open-confirm").addEventListener("click", () => { dlg.open = true; }); document.getElementById("cancel").addEventListener("click", () => { dlg.open = false; }); </script>

Without a title

Omit title for plain prompts. The close button still appears when dismissible is true.

<tc-modal open> <p>Saved.</p> </tc-modal>

Non-dismissible

For destructive confirmations or required terms-of-service prompts, disable Escape and backdrop closing:

<tc-modal open title="Confirm" dismissible="false"> <p>You must accept the new terms to continue.</p> <div slot="footer"> <tc-button variant="primary" id="accept">Accept</tc-button> </div> </tc-modal>

Custom width

<tc-modal open title="Wide modal" width="min(880px, 95vw)"> <p>For data tables, embedded code, etc.</p> </tc-modal>

Listening for the close reason

<tc-modal id="m" title="…">…</tc-modal> <script type="module"> document.getElementById("m").addEventListener("tc-close", (e) => { console.log("closed by", e.detail.reason); }); </script>

Theming

<tc-modal style="--tc-modal-radius: 4px; --tc-modal-backdrop: rgba(0,0,0,0.7);" open title="Sharper edges" >…</tc-modal>

Accessibility

  • Built on the native <dialog> element with showModal(), so the browser provides focus trapping inside the dialog and inert-ifies the rest of the page.
  • Escape dismisses the dialog when dismissible is true. The close button has an aria-label="Close".
  • The dialog has aria-labelledby pointing at the title when present.
  • Focus returns to the previously-focused element when the dialog closes — handled by the native dialog API.