@scope (list-ui) {
  :where(:scope) {
    /* ── Colors ── */
    --list-divider-color: var(--a-border-subtle);
    --list-bg-selected:   var(--a-accent-muted);
    --list-fg-selected:   var(--a-accent-strong);
    --list-border-selected: var(--a-accent-strong);
  }

  :scope {
    box-sizing: border-box;
    display: block;
    margin: 0;
    padding: 0;
    list-style: none;
  }

  /* ── Divider ── */
  :scope[divider] list-item-ui + list-item-ui {
    border-top: 1px solid var(--list-divider-color);
  }

  /* ── Item padding / margin (controlled by list-ui attributes) ──
     [divider]              — items get block padding only (dividers span full width)
     [divider][contained]   — items get block + inline padding (content inset on all sides)
     [contained] (no divider) — items use margin instead of padding
     (neither)              — items are neutral, no wrapper spacing */
  :scope[divider] > list-item-ui {
    padding-block: var(--list-item-inset);
    padding-inline: 0;
  }

  :scope[divider][contained] > list-item-ui {
    padding: var(--list-item-inset);
  }

  :scope[contained]:not([divider]) > list-item-ui {
    margin: var(--list-item-inset);
  }

  /* Flush the first/last items to the list edges when the list is not
     contained — avoids extra vertical gap at the top/bottom of a list
     that sits inside a card or bordered surface. */
  :scope:not([contained]) > list-item-ui:first-child {
    padding-block-start: 0;
    margin-block-start: 0;
  }

  :scope:not([contained]) > list-item-ui:last-child {
    padding-block-end: 0;
    margin-block-end: 0;
  }

  /* ── Selectable ── */
  :scope[selectable] > [role="listitem"] {
    cursor: pointer;
    border-inline-start: 2px solid transparent;
    padding-inline-start: var(--a-space-2);
    outline: none;
    transition:
      background   var(--a-duration-fast) var(--a-easing),
      color        var(--a-duration-fast) var(--a-easing),
      border-color var(--a-duration-fast) var(--a-easing);
  }

  :scope[selectable] > [role="listitem"]:hover {
    background: var(--a-bg-hover);
  }

  :scope[selectable] > [role="listitem"][aria-selected="true"] {
    background: var(--list-bg-selected);
    color: var(--list-fg-selected);
    border-inline-start-color: var(--list-border-selected);
  }

  :scope[selectable] > [role="listitem"]:focus-visible {
    outline: 2px solid var(--a-accent-strong);
    outline-offset: -2px;
  }

  /* ── Spacing variants ──
     Modulate the item inset used by [divider] / [contained] rules. */
  :scope[spacing="sm"] > list-item-ui {
    --list-item-inset: var(--a-space-2);
  }

  :scope[spacing="lg"] > list-item-ui {
    --list-item-inset: var(--a-space-5);
  }
}

@scope (list-item-ui) {
  :where(:scope) {
    /* ── Layout ── */
    --list-item-inset:      var(--a-inset);
    --list-item-gap-column: var(--a-space-3);
    --list-item-gap-row:    var(--a-space-1);

    /* ── Colors ── */
    --list-item-text-color: var(--a-fg);
    --list-item-desc-color: var(--a-fg-muted);
    --list-item-icon-color: var(--a-fg-muted);

    /* ── Typography ── */
    --list-item-font-size:      var(--a-ui-size);
    --list-item-desc-font-size: var(--a-ui-sm);
  }

  /* Anatomy (header-row model):
     row 1 = icon (col 1) · text/title (col 2) · action (col 3), all aligned
     on the title row. row 2 = description, spanning col 2 / -1 (under the
     action) so multi-line supporting text uses the full content width rather
     than being boxed into col 2 beside the action. For icon+text-only items
     there is a single row, so `grid-row: 1` is identical to the old `1 / -1`.
     `[slot="content"]` (used when consumer provides custom rendering)
     spans all columns. */
  :scope {
    box-sizing: border-box;
    display: grid;
    grid-template-columns: auto 1fr;
    column-gap: var(--list-item-gap-column);
    row-gap: var(--list-item-gap-row);
    font-size: var(--list-item-font-size);
    color: var(--list-item-text-color);
    line-height: 1.4;
    /* Defensive floor: empty list-item-ui (no text/icon/children) collapses
       to 0 height and disappears. Keep a visible row instead. */
    min-height: var(--a-space-6);
  }

  /* Slot positioning uses descendant (not direct-child >) because the
     template engine wraps `${...}` conditional renders in
     <span style="display:contents"> housekeeping nodes — so slot="…"
     children land as grandchildren in template-rendered hosts.
     display:contents makes the wrapper layout-transparent (the grid
     places the inner element), but CSS selectors still see the wrapper
     as a real node. Descendant selectors handle both shapes (direct
     authored children + template-engine-wrapped conditionals). */
  :scope [slot="icon"] {
    grid-column: 1;
    grid-row: 1;
    align-self: center;
    color: var(--list-item-icon-color);
  }

  :scope [slot="text"] {
    grid-column: 2;
    grid-row: 1;
    /* Center the title within row 1 so it aligns with the (also-centered) icon +
       action. Without this, a full-height action button (e.g. onboarding's "Open")
       makes row 1 taller than the title and the title top-aligns above the controls. */
    align-self: center;
  }

  :scope [slot="description"] {
    grid-column: 2 / -1;
    grid-row: 2;
    color: var(--list-item-desc-color);
    font-size: var(--list-item-desc-font-size);
    line-height: 1.3;
  }

  /* When a [slot="action"] child is present, the grid becomes 3-column:
     icon | content | action. The :has() selector promotes the template
     only when an action exists, so 2-column layouts stay unaffected.
     Action is right-aligned on the title row (row 1) so a multi-line
     description below can span the full width (col 2 / -1) beneath it.
     Used by onboarding-checklist-ui item rows + any consumer that needs an
     end-aligned action button on a list-item row.
     NOTE: NOT using `> [slot="action"]` (direct-child) — the template
     engine wraps conditional `${...}` renders in <span style="display:
     contents"> housekeeping nodes, so slot="action" lands as a grandchild
     in template-rendered hosts. display:contents makes the wrapper a
     transparent grid pass-through at layout time, but CSS selectors still
     see it as a node. Descendant combinator handles both shapes. */
  :scope:has([slot="action"]) {
    grid-template-columns: auto 1fr auto;
  }
  :scope [slot="action"] {
    grid-column: 3;
    grid-row: 1;
    align-self: center;
    justify-self: end;
  }

  /* Custom-content escape hatch — consumer authored a [slot="content"]
     child, the auto-stamp early-returned, and the consumer owns the
     full body. Span all columns so the consumer's layout isn't boxed
     into the content column. */
  :scope [slot="content"] {
    grid-column: 1 / -1;
  }

  /* ── Active state ──
     `[data-active]` is the consumer-driven "current item" hook used by
     keyboard-nav-style demos and any host that owns its own active-index
     model (separate from list-ui[selectable] / aria-selected). Reuses
     the accent-muted/accent-strong pair so the two mechanisms paint
     consistently. No border-radius on the row itself — dividers (when
     present) span full-width and rounded corners would clash. */
  :scope[data-active] {
    background: var(--a-accent-muted);
    color: var(--a-accent-strong);
    box-shadow: inset 2px 0 0 var(--a-accent-strong);
  }

  :scope[data-active] [slot="icon"],
  :scope[data-active] [slot="description"] {
    color: var(--a-accent-strong);
  }
}
