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

# `lr-reorder-list`

- **Import** `import '@aceshooting/lyra-ui/components/lr-reorder-list.js';` (stable tag alias; registers the tag)
- **Class** `LyraReorderList`, also available unregistered from `@aceshooting/lyra-ui/components/layout/reorder-list/reorder-list.class.js`
- **Family** `components/layout/` — see `llms/index.md` for its siblings
- **Status** `stable` since `6.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** 1 part, 1 custom property — see this component's own `@csspart`/`@cssprop` list below
- **Documented with** `lr-reorder-item` (same section below)
- **Library-wide behavior** (events, form association, `locale`/`strings`, tokens, TS types): `llms/shared.md`

---

## `lr-reorder-list` / `lr-reorder-item`

A generic flat-list reorder primitive: per-row move-up/move-down buttons (always available), plus
Ctrl/Cmd+ArrowUp/ArrowDown from focus anywhere inside a row — the same modifier convention
`<lr-tree>`'s `reorderable` and `<lr-dashboard-grid>`'s `cells-draggable` already establish. By
default this list physically moves its own slotted `<lr-reorder-item>` light-DOM nodes itself
(there is no `data` array prop to reconcile against), and emits `lr-reorder` with the full new
order so the host can persist it without hand-rolling its own splice/resort logic. Setting
`controlled` opts into `<lr-tree>`'s controlled `reorderable` contract instead: the list stops
moving anything itself, and waits for the host to reorder its own backing data and re-render the
slotted items to match — reconciled by each item's `value` rather than by element reference, so a
non-keyed host re-render that recreates the moved row (or merely rewrites `value` on the elements
already at each position) still completes the move once the resulting order matches. `lr-reorder`
is cancelable — a listener calling `preventDefault()` holds the move open (mirroring
`lr-confirm-bar`'s cancelable approve/deny pattern) until the host calls
`finalizePendingMove()`/`revertPendingMove()`; `controlled` changes what "applying" the move
means (host re-render instead of a physical DOM move) but not this cancelable contract.

Direct item `value` property or attribute edits refresh the owning list's valid identities and
movement boundaries. Correcting a missing or duplicate identity re-enables the corresponding
controls; making it invalid disables them. Identity edits do not emit a reorder event. Removing the
value attribute from a standalone item renders it as absent while preserving `null` readback; an
explicit empty string remains supplied and later valid values recover.

### `lr-reorder-list`

**Properties:**

- `label: string = ''` — accessible-name fallback for the internal `role="list"`. A present host
  `aria-label` always wins over `label`, including when the host value is explicitly empty.
- `disabled: boolean = false` (reflected) — disables every item's move buttons and the Ctrl/Cmd+
  Arrow shortcut, without mutating any item's own `disabled` attribute.
- `controlled: boolean = false` (reflected) — opts into the controlled mode described above:
  an accepted move waits for a matching host re-render (reconciled by `value`) instead of moving
  the DOM itself. Every move action stays disabled and `aria-busy="true"`/`:state(busy)` apply
  list-wide for the whole wait, the same as a `preventDefault()`-held move. A host re-render that
  never reaches the exact emitted `order` leaves the move pending indefinitely; one that drops the
  moved `value` entirely (removes or renames that row) cancels it silently, with no announcement.
  Toggling this off while a reconciliation is pending drops it rather than leaving the list stuck
  busy.

**Events:** `lr-reorder`
(`detail: LyraReorderDetail { readonly order: readonly string[], readonly fromIndex: number,
readonly toIndex: number }`, cancelable) — fired before a move is applied; `order` is an immutable
snapshot of every valid item's stable `value` in the order the move WOULD produce. Uncanceled, the
move applies synchronously only if the exact mover, target, membership, order, identities, and
availability remain valid after dispatch — or, while `controlled`, starts waiting for the host's
own re-render to reach that `order` instead. `preventDefault()` holds the move instead: the
internal list exposes `aria-busy="true"`, every move action is disabled, the affected item exposes
`:state(pending)`, and no other move can start until the host resolves it — see **Methods** below.
Synchronous finalize/revert calls from the canceling listener are supported.

**Methods:** `finalizePendingMove()` — applies a move held via `preventDefault()` (or, while
`controlled`, starts waiting for the host's own re-render instead of moving the DOM itself).
`revertPendingMove(options?: { silent?: boolean })` — discards a held move, restoring the prior
order; pass `{ silent: true }` to suppress the built-in `reorderMoveCancelled` announcement, e.g.
when a host is deferring the decision to a flow of its own (a confirmation dialog, say) that will
communicate the outcome itself. Both methods no-op when nothing is pending.

**Slots:** default — `<lr-reorder-item>` elements.

**CSS parts:** `base` — the internal `role="list"` wrapper.

**Themeable custom properties:** `--lr-reorder-list-gap` (default `var(--lr-space-2xs)`) — gap
between rows.

```html
<lr-reorder-list label="Form fields">
  <lr-reorder-item value="name">Name</lr-reorder-item>
  <lr-reorder-item value="email">Email</lr-reorder-item>
  <lr-reorder-item value="phone">Phone</lr-reorder-item>
</lr-reorder-list>
<script type="module">
  document
    .querySelector("lr-reorder-list")
    .addEventListener("lr-reorder", (e) => console.log(e.detail.order));
</script>
```

**Known gotchas:**

- Boundary-disabled state (`atStart`/`atEnd`), `listDisabled`, and `pending` are readonly effective
  state computed by the owning list and exposed through item custom states.
- Every item requires a unique, nonempty stable `value`. Missing, whitespace-only, and later
  duplicate identities remain visible but their move actions are unavailable until corrected.
- Ctrl/Cmd+Arrow is consumed only for a valid owned move. A boundary/no-op gesture or one from a
  nested input, select, link, button, editable region, or custom control retains its native action.
- No pointer drag-and-drop; move-up/move-down buttons and the keyboard shortcut only.
- `controlled` reconciliation is keyed by `value`, never by element reference: a host re-render
  that recreates the moved row as a brand-new element, or one that leaves every node in place and
  just rewrites `value` at each position (the common outcome of a non-keyed `Array.map()` into the
  default slot), both complete the move once the resulting order matches.

---

### `lr-reorder-item`

**Properties:**

- `value: string = ''` — required unique, nonempty stable identifier included in the parent's
  `lr-reorder` order array.
- `accessibleLabel?: string` (attribute `accessible-label`) — explicit row identity appended to
  each repeated move action's accessible name; otherwise the item derives a bounded accessible
  text projection from its row content.
- `disabled: boolean = false` (reflected) — disables this row's own move buttons only; does not
  hide its slotted content.
- `atStart: boolean`, `atEnd: boolean`, `listDisabled: boolean`, `pending: boolean` (readonly) —
  effective owner state. Corresponding custom states include `:state(at-start)`, `:state(at-end)`,
  `:state(list-disabled)`, `:state(pending)`, and `:state(busy)`.

**Events:** `lr-move-request` (`detail: { direction: 'up' | 'down' }` — a move button was activated
while not disabled; handled by the parent `<lr-reorder-list>`, which performs the actual move)

**Methods:** `focusMoveButton(direction: 'up' | 'down'): boolean` — moves focus to the requested
move control and returns whether it did; a no-op returning `false` when that control is disabled
for any reason (this item's own `disabled`, the owning list's `listDisabled`, a move held or a
controlled reconciliation pending anywhere in the list, an invalid identity, or that direction
already being this item's boundary). The owning `<lr-reorder-list>` uses it for its own post-move
focus restore, including onto a freshly recreated element instance while `controlled`; a host can
call it the same way.

**Slots:** default — arbitrary row content.

**CSS parts:** `base` (row wrapper), `move-up-button`, `move-down-button`,
`move-up-button__control` / `move-down-button__control` (each move control's own native `<button>` —
as of 16.0.0 the move controls are composed `<lr-icon-button>`s: the old part names keep placement,
rotation and activation, while background, radius, hover/press mixes, focus ring and hit-area floor
come from `--lr-icon-button-*`, and the component's own `--lr-reorder-item-move-button-*` hooks still
win over those defaults), `content` (default-slot wrapper).

**Border reaches the composed move controls the same way background/color/radius do.** This
component paints no resting border on either move control, so it relays no
`--_lr-icon-button-border-default` into their private fallback tier — but that absence is not a
gap. The public `--lr-icon-button-border` (and its `-hover`/`-active` variants) is the FIRST arm
of the token chain, resolved by ordinary custom-property inheritance regardless of whether this
component relays a default for that same property, so setting it on either move control or an
ancestor reaches it exactly as the background/color/radius tokens do. A component with no resting
border simply has no default to relay, which is different from border theming being broken. Size
remains the one exception that does not cross this way: use `--lr-theme-icon-button-size`, never
`--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-reorder-item-gap` (default `var(--lr-space-xs)`) — gap
between the move buttons and the row content. The move-button interaction paints are independent,
inherited inline fallbacks: `--lr-reorder-item-move-button-hover-bg` (default
`var(--lr-color-brand-quiet)`), `--lr-reorder-item-move-button-hover-color` (default
`var(--lr-color-brand)`), `--lr-reorder-item-move-button-active-bg` (default `color-mix(in oklab,
var(--lr-color-brand-quiet), var(--lr-color-mix-partner) var(--lr-color-mix-active))`), and
`--lr-reorder-item-move-button-active-color` (default `var(--lr-color-brand)`). Set them on an item
or any ancestor to retheme only the hover or pressed move affordance.

---
