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

# `lr-tab`

- **Import** `import '@aceshooting/lyra-ui/components/lr-tab.js';` (stable tag alias; registers the tag)
- **Class** `LyraTab`, also available unregistered from `@aceshooting/lyra-ui/components/layout/tab-group/tab.class.js`
- **Family** `components/layout/` — see `llms/index.md` for its siblings
- **Status** `stable` since `8.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** 4 parts, 0 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-tab`

One tab in a `<lr-tab-group>`'s strip. Mirrors `wa-tab` / `sl-tab`.

A **declarative descriptor, not the interactive control**: the group renders the real `role="tab"`
button and projects this element's content into it, so the whole ARIA and roving-tabindex contract
stays in one place. The host is `display: contents`, contributing no box of its own inside that
button. Direct default-slot element roots in the visual label are inert while projected, while their flattened
accessibility-exposed text explicitly names the real tab button; author-hidden, inert, and CSS-hidden
branches do not contribute. Use text/glyph markup rather than a second action.

**Properties:** `panel: string = ''` (reflected) — the `name` of the `<lr-tab-panel>` this tab
reveals; `disabled: boolean = false` (reflected) — removes the tab from keyboard navigation and
prevents activation; `active: boolean = false` (reflected) — SSR selection hint, synchronized by
the owning group after hydration; `closable: boolean = false` (reflected) — shows the mapped close
affordance.

**Events:** `lr-close` (no detail) — the Lyra-convention mapping of Shoelace's `sl-close`, emitted
when the close affordance is clicked or Delete is pressed on the focused owning tab. It bubbles, is
composed and noncancelable. A disabled tab never emits it. The tab never removes itself or its
panel; the consumer handles the request. The owning group separately emits
`lr-tab-show`/`lr-tab-hide`. **Slots:** default (the tab's visual label content; direct default-slot element roots
are inert while projected, and its accessibility-exposed flattened text names the real tab button).
**CSS parts:** `base`
and `tab` are aliases on the same projected-content slot; `close-button` and
`close-button__base` are aliases on the same non-focusable visual close affordance. Style the
group's `tab` part for the real interactive tab button.

**Themeable custom properties:** none of its own, and the group's are not settable here. The button
this tab is projected into lives in `<lr-tab-group>`'s shadow root, so it inherits
`--lr-tab-group-selected-color`, `--lr-tab-group-indicator-color` and `--lr-tab-group-hover-color`
from the group host or an ancestor of it. Declaring one on the `<lr-tab>` itself does nothing: this
element is _inside_ that button in the flattened tree, and inheritance only runs the other way.
`<lr-tab>`'s own host is `color: inherit; font: inherit;` for exactly this reason — it makes the
projected element (and therefore its visible label) pick up the real tab button's own computed
color and font, so `--lr-tab-group-selected-color`/`--lr-tab-group-hover-color` reach the rendered
tab text rather than being shadowed by the library's own default text color.

Before group hydration, an unassigned tab places itself in the public `nav` slot. The group then
writes its internal per-tab `slot` attribute itself. A labeled tab with no `panel` gets a stable
synthetic name from its position; the allocator avoids every authored tab and panel name, so mixed
paired/unpaired markup cannot collapse two tabs into one key. An unpaneled descriptor with no
accessibility-exposed label is omitted instead of exposing the synthetic key as its spoken name.

The visual close affordance is non-focusable because `<lr-tab-group>` projects this descriptor
inside the real `role="tab"` button. Rendering another focusable button there would create a
nested-interactive accessibility violation and break the APG's one-stop roving-tabindex model. The
glyph carries a localized `title`, stays out of the accessible name, and stops propagation so
closing an inactive tab never selects it first. Keyboard users focus the same real tab button and
press Delete, advertised through `aria-keyshortcuts`. Remove the corresponding `<lr-tab>` and
`<lr-tab-panel>` in an `lr-close` listener; the group then reconciles selection automatically.

```html
<lr-tab-group id="documents" aria-label="Open documents">
  <lr-tab panel="overview" active>Overview</lr-tab>
  <lr-tab panel="notes" closable>Notes</lr-tab>
  <lr-tab-panel name="overview" active>Overview content</lr-tab-panel>
  <lr-tab-panel name="notes">Notes content</lr-tab-panel>
</lr-tab-group>
```

```js
const group = document.querySelector("#documents");
group.addEventListener("lr-close", (event) => {
  const tab = event.target;
  const name = tab.panel;
  tab.remove();
  group.querySelector(`lr-tab-panel[name="${CSS.escape(name)}"]`)?.remove();
});
```

---
