<!-- GENERATED by scripts/build-llms.mjs from llms/overlays.md — do not edit this file. -->

# `lr-callout`

- **Import** `import '@aceshooting/lyra-ui/components/lr-callout.js';` (stable tag alias; registers the tag)
- **Class** `LyraCallout`, also available unregistered from `@aceshooting/lyra-ui/components/overlays/callout/callout.class.js`
- **Family** `components/overlays/` — see `llms/index.md` for its siblings
- **Status** `stable` since `4.0.0` — see the maturity and deprecation policy in `llms/shared.md`
- **Release history** [CHANGELOG.md](../../CHANGELOG.md); family-wide breaking-change summaries: [llms-full.txt](../../llms-full.txt)
- **Deprecations** none
- **Optional peers** none
- **Themeable via** 8 parts, 7 custom properties — see this component's own `@csspart`/`@cssprop` list below
- **Library-wide behavior** (events, form association, `locale`/`strings`, tokens, TS types): `llms/shared.md`

---

## `lr-callout`

An inline status, warning, or error surface. Set `inline` for lightweight reactive form or mutation
errors without panel chrome.

**Properties:** `variant: 'neutral'|'brand'|'success'|'warning'|'danger' = 'brand'` (reflected when
explicit — an unset nested callout inherits its ancestor's semantic colour context without
materializing a `variant` attribute. Explicitly writing even the same-default `brand` materializes
the attribute and pins the local brand palette; removing the attribute restores contextual
inheritance),
`appearance: 'accent'|'filled'|'outlined'|'plain'|'filled-outlined'` (reflected, with no explicit
default — when set, controls how much of the active variant palette is spent on fill, border, and
text; leaving it unset preserves the established quiet-fill/loud-edge treatment),
`size: LyraSize = 'm'` (reflected when explicit — **new in 8.0.0**; visual density on the library's shared ladder,
accepting both spellings of the aliased tiers (`s`/`small`, `m`/`medium`, `l`/`large`) so markup migrated
from `wa-callout` needs no attribute rewrite. An unset nested callout inherits its ancestor's size
context; standalone fallback is `m`. Explicitly writing even the same-default `m` pins the local
medium mapping, and removing the attribute restores contextual inheritance), `heading: string = ''`,
`headingLevel: LyraHeadingLevel = '3'` (attribute `heading-level`, reflected; `1`–`6` expose the
property and rich-slot heading wrapper at that semantic level, invalid untyped values retain level
3, and `none` is the visual-only opt-out),
`closable: boolean = false` (reflected), `inline: boolean = false` (reflected),
`announce: boolean = false` (reflected — opts the callout into announcing the content it already
carries when it first mounts; see the announcement paragraph that closes this section),
`open: boolean = true`
(reflected as a presence attribute — `open="false"` is accepted in plain markup; `false` removes the
semantic content and hides the host surface), and `accessibleLabel: string = ''`
(`accessible-label`; used only when the host has no `aria-label` attribute). A host `aria-label`
has highest precedence by presence, including an explicitly empty value.

Every reflected closed set normalizes identically from markup and untyped JavaScript writes:
unsupported `variant`, `size`, and `heading-level` values become reflected `brand`, `m`, and `3`,
while an unsupported `appearance` becomes the omitted state.

**Events:** cancelable `lr-close` (no detail); the callout sets `open = false` after the event
unless a listener calls `preventDefault()`.
When accepted close or a direct `open = false` write removes the focused close action, focus moves
to the nearest available composed action. Vetoed close and newer external focus are preserved.

**Slots:** default message, `heading` (rendered alongside the `heading` property inside the
configured semantic heading wrapper), `icon`.

**CSS parts:** `base` (the transparent grid wrapper inside the host-owned surface), `icon`
(hidden while the `icon` slot is empty), `content`, `heading`,
`message` (wrapper around the default slot), `close-button` (the close control, always
at least `--lr-icon-button-size` in both the panel and `inline` treatments), `close-button__control`,
`close-icon` (the visible "×" glyph inside it — this is what shrinks under `inline`, so the hit
target never does).

As of 16.0.0 the close control is a composed `<lr-icon-button>`: `close-button` names that host —
it still owns the grid placement, the accessible name and the click/focus API — while
`close-button__control` is its own native `<button>`, where the background, radius, hover/press
fill, focus ring and hit-area floor are painted. A rule that sets `background`, `border`,
`padding` or `outline` through `::part(close-button)` must move to `::part(close-button__control)`
or, better, to the `--lr-icon-button-*` tokens, which reach it the same way they reach a standalone
icon button. Layout-only rules (`margin`, `grid-column`, `order`, `display`) keep working on
`close-button` untouched.

The surface chrome lives on the custom-element host, not inside `base`. Ordinary host
`background`, `border`, `border-radius`, `color`, `padding`, and `margin` declarations therefore
work directly and take normal author precedence. `inline` removes the host's border, background,
and padding.

**Migrating a pre-16.0.0 `::part()` rule.** This component's icon-only action is a composed
`<lr-icon-button>`, so the part naming that action now names the composed child's HOST, which
paints nothing. A `border`, `background` or `border-radius` set on it is silently dead — only
`color` still appears to work, because it inherits, which makes such a rule look half-alive rather
than broken. Set `--lr-icon-button-background`/`-color`/`-border`/`-radius` (and their
`-hover`/`-active` variants) on this element or an ancestor instead: the composed control reads
those public tokens ahead of any default this component supplies. For SIZE use
`--lr-theme-icon-button-size`, not `--lr-icon-button-size` — every `LyraElement` re-declares the
latter on its own `:host`, so it never reaches a composed child (see `llms/tokens.md`).

**Themeable custom properties:** `--lr-callout-background`, `--lr-callout-color`, and
`--lr-callout-border` read the inherited generic semantic quiet/loud slots, with brand quiet/loud
as their standalone fallback. An explicit `variant` maps all generic slots locally; leaving it
unset preserves an ancestor's mapping. Explicit `appearance` works with either source and uses the
same brand fallback when there is no surrounding context. Public callout hooks are consumed at use
sites through private defaults, so a value inherited from a theme ancestor has the same authority
as one set directly on the callout. `--lr-callout-close-hover-bg`
(default `var(--lr-color-brand-quiet)`) — the close button's `:hover` background, deliberately
decoupled from `--lr-callout-background` (which every explicit `variant`, including `neutral`,
retargets for the panel itself) so a consumer can retint the hover fill — e.g. to keep it visibly distinct from a
`variant="brand"` panel, which shares the same default token — without a collateral effect on the
panel background, and vice versa.

Three more, all new in 8.0.0: `--lr-callout-font-size` (private default
`var(--lr-form-control-font-size, var(--lr-font-size-m))` — the callout's text size; each explicit
`size` tier maps that private default from the shared ladder), `--lr-callout-padding` (private
default `var(--lr-form-control-padding-inline, var(--lr-space-m))` — the panel's padding on _both_
axes; each `size` tier changes that private default from the ladder's inline-padding knob, because
a panel's block rhythm is generous like a control's inline padding rather than tight like its block
padding, which only exists to fit text inside a fixed control height). The padding hook applies to
the panel treatment; `inline` intentionally forces host padding to zero. Inherited or direct public
font-size values remain authoritative in either treatment, while inherited/direct padding is
authoritative only for the panel.
`--lr-callout-gap` (default `var(--lr-space-s)` — the space between the icon, the
content and the close action. It deliberately does _not_ vary by `size`: it separates three adjacent
boxes rather than setting the panel's density, and shrinking it at the small tiers only crowds
them).

Initial content and initially distributed slots are silent. After that first render/slot
distribution settles, heading-property changes and direct or nested default/heading-slot additions,
removals, and text changes are appended to Lyra's shared light-DOM polite sink (`assertive` for
`danger`). An unset callout resolves that urgency from the nearest composed ancestor carrying an
explicit semantic `variant`, matching the palette it inherits without inspecting computed RGB.
Ancestor changes, removal, reconnect, and adoption are resolved live; an explicit local variant
pins both presentation and urgency. Announcement text is whitespace-normalized, honors meaningful `aria-label` values, and
excludes the icon/close chrome plus content hidden by `hidden`, `inert`, or `aria-hidden="true"` at
any nested level. `display:none`/`content-visibility:hidden` prune a branch; a
`visibility:hidden|collapse` wrapper suppresses its own text while a descendant that restores
`visibility:visible` remains exposed. Updates while the host or a composed ancestor is hidden also
stay silent. Nested forwarding slots expose their flattened assigned text instead of fallback
content, and later assignment plus assigned-node text/style/visibility mutations are observed.
Mutations that leave that accessible text unchanged are deduplicated. A nonempty host
`aria-label` (or `accessible-label` fallback) prefixes visible update text as context, with an
equality check preventing duplicate copy. The complete localized
`calloutAnnouncementWithContext: '{context}: {content}'` message owns both fields, their order, and
punctuation; override that key through `strings` rather than prejoining either field. An explicitly
empty host label still leaves visible heading/message text live.
`[part="base"]` is an ordinary wrapper, upgraded to a non-live `role="group"` only when it has an
accessible label. Initial connection, reconnection, adoption, and detached changes that settle
during staging stay silent; each connection acquires its owning document's shared sink.

Set `announce` when the callout is created in response to something the user just did — a save
failure, a validation result — and nothing else would read the message aloud. It announces the
content the callout already carries the first time it mounts, through the same path a later update
takes: assertive for `danger` (including a `danger` inherited from a composed ancestor) and polite
otherwise, the same `hidden`/`inert`/`aria-hidden`/CSS-hidden and hidden-ancestor exclusions, the
same flattened text through nested forwarding slots, and the same complete localized
`calloutAnnouncementWithContext` composition when the host carries a nonempty label. A closed
callout (`open="false"`) announces nothing. `announce` is read once, when the callout first mounts:
a later reconnection or adoption stages the existing content again rather than replaying the
announcement, and later content updates are announced whether or not it is set. Leave it unset for
a callout that is simply part of the page a user is arriving on — that text is already read in
document order, and announcing it again is noise. If a consumer previously hand-added a host
`role="status"`/`role="alert"` to work around the missing initial announcement, remove it once
`announce` is set: leaving both in place announces the same initial text twice, once through the
native role and once through the shared sink.
