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

# `lr-segmented`

- **Import** `import '@aceshooting/lyra-ui/components/lr-segmented.js';` (stable tag alias; registers the tag)
- **Class** `LyraSegmented`, also available unregistered from `@aceshooting/lyra-ui/components/layout/segmented/segmented.class.js`
- **Family** `components/layout/` — 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** 4 parts, 19 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-segmented`

A single-select button row with the WAI-ARIA APG `radiogroup` contract built in:
`role="radiogroup"`/`role="radio"`, roving tabindex, automatic activation (click or arrow-key move
both select immediately, like a native radio group), cyclic Arrow/Home/End navigation among
non-disabled items. First-party invention (no `wa-*`/`sl-*` counterpart) — "choose exactly one of N
labeled options, rendered as a button row" is ubiquitous settings/filter-panel UI.
Navigation starts from the segment that actually received the keyboard event, even when a
controlled `value` write changed the selected or remembered roving item first.

**Properties:**

- `items: readonly LyraSegmentedItem[] = []` (attribute: false) — `LyraSegmentedItem { value:
string; label: string; icon?: unknown; disabled?: boolean }`; `icon` renders as an inert,
  `aria-hidden` decorative leading
  visual inside `segment-icon`. It does not replace the required text label or provide an independent
  action or accessible name. Input is read through a realm-neutral bounded schema snapshot (at most
  256 positions); malformed/hostile entries are skipped, later duplicate values use
  first-valid-value-wins, and the frozen returned array/records never alias caller-owned objects.
- `value: string = ''` — the currently selected item's `value`.
- `label: string = ''` — accessible-name fallback copied to the internal `role="radiogroup"`. A
  host-level `aria-label` wins by attribute presence, including an explicitly empty value.
- `size: '2xs' | 'xs' | 's' | 'm' | 'l' | 'xl' | 'small' | 'medium' | 'large' = 'm'` (reflected) —
  visual size on the library's **shared** ladder, the same `--lr-form-control-*` scale
  `lr-input`/`lr-select`/`lr-combobox`/`lr-button` resolve, so a row of mixed controls set to one
  `size` lines up at a matching height. Both spellings of every tier are accepted (`s`/`small`,
  `m`/`medium`, `l`/`large`), so migrating from either upstream is a tag rename with no attribute
  rewrite. Before 8.0.0 this component carried its own six-tier scale that had drifted from that
  one; `m` is still the default, but the tiers now resolve to the shared control heights, paddings
  and font sizes rather than to this component's former private values.

**Events:**

- `lr-change` (`detail: { value }`) — fired when the selected value changes via click or keyboard.
- `lr-activate` (`detail: { value }`) — fired on **every** activation of a non-disabled
  segment (a click, or an Arrow/Home/End key that lands on one), whether or not the selection
  actually moved. Bubbling, composed, not cancelable — it reports that the user picked a segment
  and gates nothing. Use it for the repeat pick `lr-change` deliberately stays silent for: "run
  that report again", reopening a panel, re-fetching the same range. A `click` listener only
  half-covers that case, because keyboard activation produces no click — pressing Home on an
  already-first selection, End on an already-last one, or an arrow key in a one-item row activates
  a segment and fires no click at all. When an activation _does_ move the selection, `lr-change` is
  emitted first and `lr-activate` second, so either listener reads the settled `value`. A
  disabled segment fires neither event.

**Methods:**

- `scrollToValue(value: string): void` — scroll the segment with the given `value` into view within
  the (possibly overflowing) track, without changing the selection. Honors
  `prefers-reduced-motion` (falls back to `behavior: 'auto'`). This runs automatically when `value`
  is changed programmatically (keyboard navigation already reveals the focused segment on its own),
  so you only need to call it for the "reveal without selecting" case.

**Slots:** none.

**CSS parts:** `base` (the `role="radiogroup"` root), `segment` (a single `role="radio"` button),
`segment-icon` (an optional inert, `aria-hidden` decorative leading icon), `segment-label` (the
segment's label text).

**Themeable custom properties:** `--lr-scroll-fade-size` (default `2rem`) — width of the mask fade
at each horizontal scroll edge of the track, painted only while the track actually overflows (a row
that fits is never dimmed). Forced-colors mode removes the decorative mask while preserving the
native horizontal scroll owner. `--lr-segmented-track-min-height` (default
`var(--lr-form-control-height)`), `--lr-segmented-segment-padding` (default
`var(--lr-form-control-padding-block) var(--lr-form-control-padding-inline)`), and
`--lr-segmented-font-size` (default `var(--lr-form-control-font-size)`) are the three knobs the
`size` tier moves — each points at the shared ladder rather than carrying a per-tier value of its
own, so retuning one tier for this component alone is a one-line override instead of a fork. Their
private defaults follow the tier; a public value inherited from an ancestor or set directly on the
element remains authoritative in every tier.

`--lr-segmented-track-height` pins the `base` track's exact height at every `size` tier (it sets
both `block-size` and `min-block-size`), for a row that has to sit flush beside a hard-sized toolbar
control. It is **genuinely undeclared by default** — not `auto` — and that is load-bearing: an
exact-height hatch only works as an undeclared sentinel, because `auto` is itself a valid value that
would always win and would silently turn every tier's `--lr-segmented-track-min-height` floor into
dead code. While it is unset, each tier keeps its own floor and the track grows with its content.
The floor at the two compact tiers is the ladder's own (20px at `2xs`, 24px at `xs`), but every
`2xs`/`xs` _segment_ separately carries a 24×24px minimum box, so the tappable target holds even
when a label is a single character and the track ends up taller than its nominal floor.

`--lr-segmented-selected-bg` (default `var(--lr-color-surface)`), `--lr-segmented-selected-color`
(default `var(--lr-color-text)`), `--lr-segmented-selected-font-weight` (default
`var(--lr-font-weight-semibold)`) and `--lr-segmented-selected-shadow` (default
`var(--lr-shadow-xs)` — the shallowest step in the elevation scale, since the checked segment is a
thumb lifted a hair off its own track) style the checked segment's pill;
`--lr-segmented-hover-color` (default `var(--lr-color-text)`) styles a hovered segment that is
neither checked nor disabled, independently of the four above — so recoloring the checked pill never
bleeds onto hover. `--lr-segmented-hover-bg` (default `transparent`) and `--lr-segmented-hover-shadow`
(default `none`) style that same hovered segment's background and box shadow; both are undeclared by
default, so they fall back to the segment's own resting values and change nothing about today's
hover paint until set. These seven state hooks are inline `var()` fallbacks at the
point of use rather than `:host` declarations, so each can be set on the element _or on any
ancestor_; unset, each falls back to the token its rule used before. They exist because
`::part(segment)[aria-checked='true']` is invalid CSS — Shadow Parts forbids an attribute selector
after `::part()` — which previously left hijacking the library-wide
`--lr-color-surface`/`--lr-color-text` tokens as the only way to restyle a selected segment,
repainting every other element that read them.

`--lr-segmented-active-bg` (default `color-mix(in oklab, transparent,
var(--lr-color-mix-partner) var(--lr-color-mix-active))`) and
`--lr-segmented-active-color` (default
`var(--lr-segmented-hover-color, var(--lr-color-text))`) style a pressed segment that is neither
checked nor disabled. They use the same inline-fallback inheritance, leaving checked and merely
hovered siblings independent.

Otherwise shared tokens — `--lr-color-border`/`-surface`/`-text`/
`-text-quiet`, `--lr-radius`, `--lr-font-weight-semibold`, `--lr-shadow-xs`,
`--lr-opacity-disabled`, `--lr-focus-ring-*`, and the `--lr-form-control-*` knobs the `size` tier
resolves.

**Optional peer deps:** none.

```html
<lr-segmented></lr-segmented>
<script type="module">
  const seg = document.querySelector("lr-segmented");
  seg.items = [
    { value: "day", label: "Day", icon: "☀" },
    { value: "week", label: "Week", icon: "▦" },
    { value: "month", label: "Month" },
  ];
  seg.value = "week";
  seg.addEventListener("lr-change", (e) => console.log(e.detail.value));
  seg.addEventListener("lr-activate", (e) => console.log("activated", e.detail.value));
</script>
```

**Known gotchas:**

- arrow-key navigation cycles (past the last non-disabled item wraps to the first, and vice versa)
  rather than clamping at the first/last item, unlike `lr-stepper`'s clamped Left/Right.
- this component self-selects on navigation: clicking or arrow-navigating to an item immediately
  updates `value` and fires `lr-change` — there's no separate "commit" step the way, e.g.,
  `lr-select`'s popup has. `lr-change` is change-only, so re-picking the segment that is already
  selected fires nothing on it; listen for `lr-activate` if a repeat pick is meaningful to
  your application.
- the semantic `radiogroup` lives inside shadow DOM. Set `label` (preferred for reactive code) or a
  host `aria-label`; a present host attribute wins, including an explicit empty value, and the
  component deliberately forwards the resulting name to that internal role.

**Additional API surface:**

- `--lr-segmented-track-gap` — Gap between segments. Default: `var(--lr-size-0-125rem)`.
- `--lr-segmented-track-radius` — Track corner radius. Default: `var(--lr-radius)`.
- `--lr-segmented-track-padding` — Track inset padding. Default: `var(--lr-size-0-125rem)`.
- `--lr-segmented-track-bg` — Background of the `base` track. Undeclared by default (transparent),
  matching its own current absence of a background.
- `--lr-segmented-track-border-color` — Border color of the `base` track, which previously read
  `--lr-color-border` as a literal with no override hook. Default: `var(--lr-color-border)`.

---
