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

# `lr-empty`

- **Import** `import '@aceshooting/lyra-ui/components/lr-empty.js';` (stable tag alias; registers the tag)
- **Class** `LyraEmpty`, also available unregistered from `@aceshooting/lyra-ui/components/overlays/empty/empty.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** 5 parts, 4 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-empty`

First-party "no data" state (no Web Awesome equivalent).

Removing `heading` or `description` safely omits that text; explicit empty values remain empty and
later values restore the corresponding content.

**Properties:**

- `heading: string = ''`
- `headingLevel: LyraHeadingLevel = '3'` (attribute `heading-level`, reflected) — `1`–`6` expose
  either the string heading or rich `heading` slot at that semantic level; invalid untyped values
  retain level 3, while `none` keeps the visible text without heading semantics
- `description: string = ''`
- `compact: boolean = false` (reflected) — tighter, left-aligned rendering (less padding, a lighter
  heading weight) for use inside a constrained space like a widget body or table cell, instead of
  the centered/spacious full-page default
- `announce: boolean = false` (reflected) — announces the heading and description this empty state
  already carries when it first mounts, through the same shared light-DOM polite sink later changes
  use. Set it where the empty state replaces a result set the user just asked for; leave it unset
  for an empty state that is part of the page being loaded. See the first "Known gotchas" bullet

**Events:** none.

**Slots:** default (icon/illustration), `heading` (rich heading content, overrides the `heading`
attribute), `description` (rich description content, overrides the `description` attribute),
`actions` (buttons/links below the description)

**CSS parts:** `base`, `icon`, `heading`, `description`, `actions`

**Themeable custom properties:** `--lr-empty-compact-align` (compact mode only; defaults preserve
the existing `flex-start` cross-axis and `start` text alignment, and `center` centers both),
`--lr-empty-compact-padding` (default `--lr-space-xs` — padding used in compact mode),
`--lr-empty-compact-gap` (default `--lr-space-2xs` — gap between the icon, heading, and description
in compact mode; the non-compact layout's gap stays the plain shared `--lr-space-s` token, not
independently themeable), `--lr-empty-compact-font-size` (compact mode only; unset by default with
**no fallback value** — the compact heading keeps its ordinary inherited font size until a
consumer explicitly sets this token), plus shared tokens (`--lr-space-xs/-s/-l`,
`--lr-color-text-quiet/-border/-text`).

**Optional peer deps:** none.

```html
<lr-empty
  heading="No results"
  heading-level="2"
  description="Try a different search."
>
  <svg slot="" ...></svg>
  <!-- default slot: any icon/illustration -->
  <div slot="actions"><button>Clear filters</button></div>
</lr-empty>
<lr-empty
  compact
  heading="No results"
  description="Try a different search."
  style="--lr-empty-compact-align: center"
></lr-empty>
```

**Known gotchas:**

- Initial content and reconnect state—including property changes made while detached—stay silent
  unless `announce` is set, which announces the heading/description present at first mount once,
  through the same sink and the same exclusions as a later change. `announce` is read once, so a
  later reconnection or adoption stages the existing content again rather than replaying it.
  Later meaningful heading and description changes are appended to Lyra's shared light-DOM polite
  announcement sink. Default-slot illustrations, action-slot controls, nested hidden/inert/
  `aria-hidden="true"` content, updates under a hidden/CSS-hidden composed ancestor, and mutations
  that leave the accessible text unchanged are excluded;
  a `visibility:hidden|collapse` wrapper suppresses its own text but not a descendant that restores
  `visibility:visible`;
  nested forwarding slots contribute their flattened assigned heading/description text instead of
  fallback content, and later assignment plus assigned-node text/style/visibility changes are
  observed without turning initial distribution into a live update;
  a host `aria-label` names the host but does not replace that visible update text;
  the shadow `[part="base"]` remains ordinary visible content rather than a shadow-root live region.
  Remove any host `role="status"`/`role="alert"` hand-added before `announce` existed once it is
  set — otherwise the initial text is announced twice, through the native role and again through
  the shared sink.
- Note: correctly works around the classic `:empty`-pseudo-class trap (a wrapper with a `<slot>`
  inside can never match `:empty`) by tracking real flattened slot content in JS, including a bare
  non-whitespace text node such as an emoji as default icon content —
  `lr-table` reuses this component for its own empty-rows state, and `lr-stat` (below) now uses
  the same JS-tracked-slot-state pattern for its own icon/caption wrappers.

---
