AppModal
The single dialog primitive — a focused card over the page for a form, details, or a short task.
<app-modal id="edit" title="Edit project">…</app-modal>A modal is a focused card that opens over the page for one contained task — an edit form, a detail view, a short confirmation. <app-modal> is the single dialog primitive; it renders the themed Bootstrap modal and Bootstrap's own JS adds the focus trap, Escape-to-close, backdrop dismiss, and focus restore, so every dialog behaves the same way.
Its look comes entirely from the shared @webority/theme, so the Razor <app-modal> and the React <AppModal> render identically — the same dialog chrome on every Webority surface. A modal is an overlay: open it from a trigger. In Razor that's any element carrying data-bs-toggle="modal" data-bs-target="#id" (or WUI.modal.open('id')).
Playground
Every attribute, live. Each change re-renders the real tag helper on the server, so the markup underneath is the exact call that produced the dialog above it.
A basic dialog
A header (title + close), a body, and a footer of actions. The trigger points at the modal's id; the footer usually pairs a secondary Cancel with a primary confirm.
Show code
<app-button data-bs-toggle="modal" data-bs-target="#scEditModal">Edit project</app-button>
<app-modal id="scEditModal" title="Edit project" header-icon="edit">
<p>Modal body — a form, details, or a short task.</p>
<app-modal-footer>
<app-button variant="secondary" data-bs-dismiss="modal">Cancel</app-button>
<app-button data-bs-dismiss="modal">Save</app-button>
</app-modal-footer>
</app-modal>
Eyebrow, tone & forced-choice
An eyebrow is a small kicker label above the title; tone tints the header icon and eyebrow to signal intent; dismissible="false" with static-backdrop makes a forced-choice dialog with no way out but an explicit action.
eyebrow + tone="danger". Right: tone="success", dismissible="false" + static-backdrop — no close disc, and neither Escape nor the backdrop dismisses it.Show code
<app-modal id="del" eyebrow="Danger zone" title="Delete project" header-icon="trash-2" tone="danger">…</app-modal> <app-modal id="terms" eyebrow="Action required" title="Accept the terms" tone="success" dismissible="false" static-backdrop="true">…</app-modal>
Sizes
size sets the card width — sm, md (default), lg, or xl — or pass a number for an exact custom width in px.
Show code
<app-modal id="lg" title="Large dialog" size="lg">…</app-modal> <app-modal id="custom" title="Custom width" size="960">…</app-modal>
Placement
placement puts the card in the middle of the viewport (center, the default) or pinned near the top (top).
placement="top" pins the card near the top — useful when the trigger is high on the page or the dialog is short.Show code
<app-modal id="note" title="Quick note" placement="top">…</app-modal>
When to use
Use it when
- A short, self-contained task — edit a record, view details, complete a small form.
- You need to interrupt the flow to get one thing done before continuing.
Reach for something else when
- It's a yes/no confirmation before a destructive action. → AppConfirmDialog
- It's a slide-in navigation or filter panel on mobile. → AppSidebarMenu
- The content is a full workflow with many steps and forms. → a dedicated page
- It's a transient success/error message. → a toast
Best practices
Every option
The whole surface of the component, one cell per value — each cell is its own isolated trigger + dialog.
tone — all four
tone="default"
tone="danger"
tone="success"
tone="warning"
size — every preset, plus a custom width
size="sm"
size="md"
size="lg"
size="xl"
size="720"
placement
placement="center"
placement="top"
header — eyebrow, subtitle, header-icon, hide-header
eyebrow only
subtitle only
header-icon only
eyebrow + subtitle + header-icon together
hide-header — no header row at all
dismiss control — dismissible, static-backdrop
dismissible="false" — no close ×, Escape/backdrop still work
static-backdrop — Escape/backdrop disabled
dismissible="false" + static-backdrop — forced choice
footer & long content
no footer
long body — the card scrolls, header and footer stay pinned
Attributes
| Attribute | Type | Default | Description |
|---|---|---|---|
| id | string | — | The modal id a trigger points at with data-bs-target. |
| title | string | — | Header title. |
| size | sm | md | lg | xl | number | md | Card width — a preset, or an exact max-width in px for a custom size. |
| placement | center | top | center | Vertical placement of the card — centred, or pinned near the top of the viewport (React placement). |
| (content) | markup | — | The modal body. A trailing <app-modal-footer> is lifted out as a pinned sticky footer — the React footer prop's equivalent. |
| subtitle | string | — | Secondary line under the title (React subtitle). |
| header-icon | string | — | Bootstrap Icons glyph for the header tile (React headerIcon). |
| eyebrow | string | — | A small kicker label above the title — a section, step, or category marker (React eyebrow). |
| tone | default | danger | success | warning | default | Tints the header icon and eyebrow to signal intent (React tone). |
| dismissible | bool | true | Show the header close (×) button; pair with static-backdrop for a forced-choice dialog (React dismissible). |
| hide-header | bool | false | Render without the header row (React hideHeader). |
| class | string | — | Extra classes merged onto the modal shell (React className). |
| static-backdrop | bool | false | Backdrop click and Escape no longer close the dialog — only an explicit close action can (React staticBackdrop). |
| aria-label | string | — | Accessible name for the dialog, overriding title (React ariaLabel). Set it when hide-header leaves the dialog without a visible title. |