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

# `lr-random-content`

- **Import** `import '@aceshooting/lyra-ui/components/lr-random-content.js';` (stable tag alias; registers the tag)
- **Class** `LyraRandomContent`, also available unregistered from `@aceshooting/lyra-ui/components/utility/random-content/random-content.class.js`
- **Family** `components/utility/` — 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** 2 parts, 11 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-random-content`

Shows a chosen subset of its slotted children and hides the rest — A/B copy testing, testimonial
rotation, varying marketing copy per render or interval — with no custom JS beyond slotting the
candidates. Selection is applied by setting `hidden` + `aria-hidden` directly on the light-DOM
children; nothing is moved or cloned.

**Properties:**

- `items: number = 1` — how many children are shown **simultaneously**; a count, not the pool.
  Normalized to a finite integer clamped to `[1, poolSize]`
- `mode: 'unique' | 'random' | 'sequence' = 'unique'` — `'random'` re-rolls freely (repeats
  possible); `'unique'` retries up to 10 times to avoid re-picking the previous selection (only
  when the pool is larger than `items`); `'sequence'` walks the pool in order with a wrapping cursor
- `animation: 'none' | 'fade' | 'fade-up' | 'fade-down' | 'fade-left' | 'fade-right' = 'none'`
  (reflected) — entrance effect applied to a child the instant it becomes shown
- `autoplay: boolean = false` (reflected)
- `paused: boolean = false` (reflected) — suppresses autoplay. The built-in pause/resume action
  toggles this state; a programmatic assignment remains silent.
- `autoplayInterval: number = 3000` (attribute `autoplay-interval`) — clamped to a 1000 ms floor

**Methods:** `randomize(): readonly Element[]` — re-selects using the current `mode`, applies
`hidden`/`aria-hidden`, emits `lr-content-change`, appends the exposed selection text to the shared
polite announcement sink (even when `autoplay` is enabled), and returns the elements now shown.
Does **not** reset or restart the autoplay timer.

**Events:** `lr-content-change` (`detail: { readonly items: readonly Element[] }` — a frozen
snapshot of the exact elements now shown, in display order). Fires on first render, on
`randomize()`, on a real slot-content change, and on
each autoplay tick; never when the eligible pool is empty. `lr-pause-change`
(`detail: { paused: boolean }`) fires only when the built-in pause/resume button toggles `paused`, so a
host mirroring or persisting that state stays in sync; a programmatic `paused` write stays silent,
so a controlled binding can't echo itself. Same event name and payload shape as `<lr-poll-status>`'s
identical affordance, so one handler serves both.

**Slots:** default — the candidate pool. Direct **element** children, including SVG elements, are
eligible. When a wrapper
places a forwarding `<slot>` directly in the pool, its flattened projected elements become the
candidates; an arbitrary nested custom-element subtree remains one opaque direct candidate.

**CSS parts:** `base` — the ordinary wrapper around the default slot; a host `aria-label` gives it
a non-live `role="group"` and is included as announcement context. Selection changes after mount
are appended to Lyra's shared light-DOM polite announcement sink; nested `hidden`, `inert`,
`aria-hidden="true"`, `display:none`, and `content-visibility:hidden` branches are omitted. A
`visibility:hidden|collapse` wrapper suppresses its own text but not a descendant that restores
`visibility:visible`. Timer-driven autoplay ticks stay silent to avoid spam,
but a direct `randomize()` call still announces while `autoplay` is enabled. Initial connection and
reconnection are also silent, including a detached reactive selection change whose update settles
during reattachment; changes while the component or a composed ancestor is accessibility-hidden
stay silent too. A nested forwarding slot contributes flattened assigned content rather than its
fallback; later assignment and assigned-node text/style/visibility changes announce only when they
change the currently exposed selection, while initial distribution remains silent. `pause-button`
— the localized autoplay pause/resume action, rendered
only while `autoplay` is enabled and exposed as a toggle with `aria-pressed`.

**Themeable custom properties:** Web Awesome aliases `--animation-duration` (default `300ms`),
`--animation-easing` (default `ease`), and `--animation-translate` (default
`--lr-size-0-5em` — travel distance for the four directional `fade-*` effects) feed the mapped
`--lr-animation-duration`, `--lr-animation-easing`, and `--lr-animation-translate` names. Existing
`--lr-random-content-animation-duration`, `--lr-random-content-animation-easing`, and
`--lr-random-content-animation-translate` names remain fallbacks. The base owns a wrapping flex
layout for simultaneous selections; `--lr-random-content-item-gap` (default `--lr-space-s`) sets
the row/column gap and `--lr-random-content-item-alignment` (default `flex-start`) sets cross-axis
alignment. Slotted candidates establish transformable inline-block boxes, so directional entrance
animations retain their travel for ordinary inline elements.

**Web Awesome migration note:** `migrate-wa` performs the tag rename and reports only the behavior
groups exercised by each use: host layout (`display: contents` versus Lyra's block host),
multi-item layout ownership, the bounded retry used by `mode="unique"`, direct forwarding-slot
candidates, and autoplay semantics (reduced-motion suppression, silent timer ticks, focus-only
pause, and Lyra's visible localized pause/resume control). An empty element is rewritten without a
behavior warning; direct SVG candidates and the documented animation vocabulary are parity cases.

**Known gotchas:**

- There is no next/previous/shuffle action; the only built-in control is the autoplay pause/resume
  button. Selection changes via autoplay or `randomize()`.
- Autoplay is suppressed entirely under `prefers-reduced-motion: reduce`, and whenever the eligible
  pool has fewer than 2 children. The preference is re-observed live, not just read once.
- `fade-left`/`fade-right` are physical-direction transforms (upstream naming), deliberately **not**
  mirrored under `:dir(rtl)` — they are not previous/next navigational semantics.
- The entrance animation targets `::slotted(*)`, which the library-wide reduced-motion rule cannot
  reach (it only covers the shadow tree), so this component guards it with its own media query.
- The host is `display: block`. For an inline text-fragment swap inside a sentence, override
  `lr-random-content { display: inline; }` from outside; `display: contents` is deliberately not
  used (a11y-tree inconsistencies across engines).
- Slot/focus microtasks and autoplay never queue new selection work while detached; reconnecting
  starts again from current state rather than replaying stale work.
- Non-selected candidates are hidden by the component's own `::slotted([hidden])` rule, not by the
  UA's. `::slotted(*)` sets `display: inline-block` on every candidate, and an author-origin
  declaration always beats the user-agent `[hidden] { display: none }` — so the rule that restores
  the native meaning of `hidden` is load-bearing, and an author overriding `display` on
  `::slotted` content from outside must keep the hidden case at `display: none`.
  `hidden="until-found"` is exempted, exactly as the UA rule exempts it.
- **Before script runs, the first candidate is the one that shows.** Selection happens after the
  first client render and works by setting `hidden`/`aria-hidden` on light-DOM siblings, which Lit
  hydration never compares, so a server renderer can never produce it. Every candidate after the
  first is therefore hidden by CSS until a selection has been applied: a server-rendered page — or
  one whose script never runs — paints one candidate rather than the whole pool. Randomness itself
  cannot survive SSR (there is no shared seed), so order the pool if a specific candidate should be
  the pre-hydration one. With `items > 1` the pre-hydration paint is still a single candidate.
