Overlays

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.

<app-modal id="pgModal" title="Playground modal">…</app-modal>

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.

Open a dialog
Escape, the backdrop, and the close disc all dismiss the modal — you never wire those by hand.
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>
Keep a modal to one taskA dialog interrupts the flow, so it should hold exactly one job — edit this, confirm that, view these details. If it grows tabs and multiple unrelated forms, it wants to be a page, not a modal.

Sizes

size sets the card width — sm, md (default), lg, or xl — or pass a number for an exact custom width in px.

Presets and a custom width
Same primitive, same trigger mechanism — only the width changes. A number is an exact max-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).

Top-aligned
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

Do
A clear trigger opens a single-purpose dialog; the id wiring keeps Escape and the backdrop working.
Don't
A modal with no dismiss control traps the user — the backdrop and Escape have nothing to close.
Do
A footer that pairs a secondary Cancel with one primary confirm — the eye knows the main action.
Don't
Three competing primaries in the footer leave the user unsure which one commits.

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

AttributeTypeDefaultDescription
idstring—The modal id a trigger points at with data-bs-target.
titlestring—Header title.
sizesm | md | lg | xl | numbermdCard width — a preset, or an exact max-width in px for a custom size.
placementcenter | topcenterVertical 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.
subtitlestring—Secondary line under the title (React subtitle).
header-iconstring—Bootstrap Icons glyph for the header tile (React headerIcon).
eyebrowstring—A small kicker label above the title — a section, step, or category marker (React eyebrow).
tonedefault | danger | success | warningdefaultTints the header icon and eyebrow to signal intent (React tone).
dismissiblebooltrueShow the header close (×) button; pair with static-backdrop for a forced-choice dialog (React dismissible).
hide-headerboolfalseRender without the header row (React hideHeader).
classstring—Extra classes merged onto the modal shell (React className).
static-backdropboolfalseBackdrop click and Escape no longer close the dialog — only an explicit close action can (React staticBackdrop).
aria-labelstring—Accessible name for the dialog, overriding title (React ariaLabel). Set it when hide-header leaves the dialog without a visible title.