Status: Canonical (D5 v1.0) Last updated: August 10, 2026
Taxonomy placement: D5. Stroke & Separation (part of the 7-dimensional CEM token framework)
Companion specs:
cem-colors.md) — defines
--cem-separator-color and focus colors; D5 defines thickness and geometrycem-dimension.md) — inset/spacing rules that keep strokes readable without crowdingcem-coupling.md) — density modes that affect when thin strokes must become strongercem-shape.md) — corner radii and rounding rules that strokes must followcem-layering.md) — when to prefer shadow separation vs stroke separationcem-timing.md) — animation timing for focus/selection transitionsD5 defines the geometry of separation:
D5 does not define:
Canvas, CanvasText, SelectedItem, emotional palettes, etc.).Tokens must express what the stroke means to the consumer, not how it is implemented:
Stroke systems can explode into per-component and per-state width tokens. CEM avoids that by defining:
Changing border width on focus is a common source of layout shift (e.g., label jumps in outlined text fields). Focus and selection indicators should be drawn using outline or box-shadow, or on a pseudo-element, rather than changing border-box geometry.
D5 must support modern focus-indicator requirements (including minimum visible area/thickness and sufficient contrast in high-contrast themes). See §8.
CEM D5 uses a 4-step stroke basis that covers almost all UI needs without a large token surface.
These tokens are foundational. Semantic endpoints map to them.
:root {
/* Basis: keep small and stable */
--cem-stroke-none: 0px; /* no stroke */
--cem-stroke-hair: 1px; /* hairline separators, subtle boundaries */
--cem-stroke-standard: 2px; /* focus/selection indicators; high legibility */
--cem-stroke-strong: 3px; /* high-emphasis boundaries; contrast themes */
}
Notes
1px remains device-independent (CSS px), and is appropriate for dividers and subtle component boundaries.
2px is the default for focus visibility and state distinction.
3px exists primarily for high contrast and “hard separation” contexts.
Material Design component specs frequently use 1–2dp stroke thickness for outlines and focus indicators; in web CEM this maps cleanly onto 1px hairlines and 2px indicators (see References for representative component spec links and issue discussions).
| Token | Value | Description | tier |
|---|---|---|---|
--cem-stroke-none | 0px | No stroke | required |
--cem-stroke-hair | 1px | Hairline separators / subtle boundaries | required |
--cem-stroke-standard | 2px | Focus / selection / target indicator thickness | required |
--cem-stroke-strong | 3px | High-emphasis boundaries / contrast themes | required |
Stroke thickness is allowed to vary by density mode as a non-breaking adjustment, as long as semantics stay intact:
These are the canonical semantic endpoints that components consume.
:root {
/* Canonical boundaries */
--cem-stroke-boundary: var(--cem-stroke-hair);
/* Canonical separators */
--cem-stroke-divider: var(--cem-stroke-hair);
/* Canonical indicators */
--cem-stroke-focus: var(--cem-stroke-strong);
--cem-stroke-selected: var(--cem-stroke-standard);
--cem-stroke-target: var(--cem-stroke-standard);
--cem-stroke-pending: var(--cem-stroke-standard);
/* Placement */
--cem-stroke-indicator-offset: 2px; /* distance outside the edge, not thickness */
/* Optional convenience endpoints (aliases, not required by contract) */
--cem-stroke-grid: var(--cem-stroke-divider); /* tables / data grids */
--cem-stroke-boundary-strong: var(--cem-stroke-standard); /* dialogs / sheets when needed */
}
| Token | Value | Description | tier |
|---|---|---|---|
--cem-stroke-boundary | var(--cem-stroke-hair) | Default control container edge (text fields, chips, cards) | required |
--cem-stroke-boundary-strong | var(--cem-stroke-standard) | Strong boundary when elevation/shadow cannot carry separation | recommended |
--cem-stroke-divider | var(--cem-stroke-hair) | Sibling separators (list rows, table rows, sections) | required |
--cem-stroke-grid | var(--cem-stroke-divider) | Tables / data-grid divider alias | recommended |
--cem-stroke-focus | var(--cem-stroke-strong) | Keyboard focus indicator thickness | required |
--cem-stroke-selected | var(--cem-stroke-standard) | Selection indicator thickness | required |
--cem-stroke-target | var(--cem-stroke-standard) | Deep-link target indicator thickness | required |
--cem-stroke-pending | var(--cem-stroke-standard) | Persistent pending-state boundary/indicator thickness | recommended |
--cem-stroke-indicator-offset | 2px | External ring/outline offset (distance, not thickness) | required |
--cem-stroke-boundary is the default edge for control containers (text fields, chips, cards, tiles).--cem-stroke-divider separates siblings (list rows, table rows, sections).--cem-stroke-focus is for keyboard focus (and must meet §8).--cem-stroke-selected marks selection where background does not carry the whole burden of state (CEM preference).--cem-stroke-target marks a deep-linked / navigated-to target (e.g., URL fragment target, “jump to result”).--cem-stroke-pending strengthens a persistent pending/loading boundary so the state does not rely on color
alone. It does not imply focus, selection, disabled, or progress semantics.--cem-stroke-indicator-offset prevents rings/indicators from visually merging with the component edge.Optional convenience endpoints (aliases):
--cem-stroke-grid is a readability alias for “divider inside structured content” (tables / data grids).--cem-stroke-boundary-strong is a stronger boundary used only when elevation/shadow is unavailable or insufficient.The D5 contract intentionally avoids per-component thickness tokens (e.g., --cem-input-outline). Instead:
Rule of thumb: color is D0 (
--cem-separator-color), thickness is D5, corner curvature is D3, spacing/insets is D1, and the interactive guard floor is D2.
| Component family | Primary D5 endpoint(s) | Typical stroke usage | Notes |
|---|---|---|---|
| Dividers (horizontal/vertical) | --cem-stroke-divider + D0 --cem-separator-color | Hairline inside a spaced track | Package the line with D1 relationship margins, floor interactive separation at the D2 guard, and prefer inset patterns for lists. |
| List item separators | --cem-stroke-divider (or --cem-stroke-grid) | Row separators | Treat dense “data-list” layouts as a grid for readability. |
| Table borders / grid lines | --cem-stroke-grid | Cell/row separators | Use grid to distinguish “structured separators” from generic dividers; same thickness by default. |
| Text field outline (outlined pattern) | --cem-stroke-boundary | Control container edge | Focus indication should be a ring (--cem-stroke-focus) rather than mutating border width. |
| Text field underline (filled pattern) | --cem-stroke-boundary or --cem-stroke-divider | Baseline boundary | Underline is a boundary of the control, not a sibling divider; map to divider only when visually used as “row separation”. |
| Pending input boundary | --cem-stroke-pending | Persistent thicker anchor | Pair with a pending color and programmatic busy state; do not infer pending from focus, validation, or value changes. |
| Outlined button border | --cem-stroke-boundary | Variant boundary | Use boundary-strong only for “hard separation” themes (e.g., low-elevation UIs). |
| Card / tile border | --cem-stroke-boundary (often --cem-stroke-none) | Optional edge | Prefer D4 elevation or D0 surface contrast; use boundary only when needed for scannability. |
| Checkbox / radio container border | --cem-stroke-boundary | Control boundary | The checkmark/dot is not a stroke token concern; it is icon geometry (D3) + color (D0). |
| Switch track outline (if any) | --cem-stroke-boundary | Track boundary | Track height is D2/D1, not D5; D5 only provides line weight if outlined. |
| Chip / badge border (bordered variant) | --cem-stroke-boundary | Subtle outline | In dense chips, consider mapping boundary → standard via D2 coupling if needed for separation. |
| Tabs / segmented control indicator | --cem-stroke-selected | Active underline/bar thickness | Avoid using divider for active indicators; indicators communicate state, not separation. |
| Focus ring (all controls) | --cem-stroke-focus + --cem-stroke-indicator-offset | External ring/outline | Must satisfy WCAG 2.2 focus appearance (§8.1). |
| Target highlight (deep link / “jump to”) | --cem-stroke-target | Temporary outline/ring | Distinct from focus: target is navigational, focus is interactive. |
| Menus / dropdowns / popovers (edge) | --cem-stroke-boundary (often none) | Optional border | Prefer D4 shadow separation; use boundary/boundary-strong in forced-colors or shadow-suppressed contexts. |
| Progress/slider tracks | (usually none) / --cem-stroke-boundary | Outline only if required | Track thickness (e.g., 8px) is a dimension; treat that as D2/D1, not D5. |
CEM uses an outline-driven indicator model for states that must remain visible across backgrounds:
:target)theme-data.xhtml)The current theme implementation uses a zebra ring composed of stacked outside strokes using box-shadow and zebra color variables:
--cem-zebra-strip-size--cem-zebra-color-0 … --cem-zebra-color-3--cem-action-box-shadow (used by .action components)In normal themes, a 3-stripe ring is used; in contrast themes, a 4-stripe ring is used (intent + focus + target + selected).
Ownership split (R-D5-1 resolved): --cem-zebra-strip-size and --cem-zebra-color-{0..3} are owned by D0
(cem-colors.md) and emitted by cem-colors.html because they ship with full theme-mode coverage
(.cem-theme-{light,dark,contrast-light,contrast-dark,native}). D5 owns:
--cem-stroke-{none,hair,standard,strong})--cem-stroke-{boundary,divider,focus,selected,target,…} and --cem-stroke-indicator-offset)--cem-ring-zebra-3, --cem-ring-zebra-4)--cem-zebra-angle (gradient-mode geometry only)D5 ring recipes reference D0-owned zebra tokens via var(); D5 does NOT redeclare them.
D5 treats these as canonical indicator-pattern tokens:
:root {
--cem-zebra-strip-size: 2px; /* thickness per stripe */
--cem-zebra-angle: 45deg; /* if stripes are rendered as gradients */
--cem-zebra-color-0: Canvas; /* intent / base stripe (contrast themes) */
--cem-zebra-color-1: CanvasText; /* focus stripe */
--cem-zebra-color-2: Mark; /* target stripe */
--cem-zebra-color-3: SelectedItem; /* selected stripe (or themed alternative) */
}
--cem-zebra-strip-size and --cem-zebra-color-{0..3} are owned by D0 (cem-colors.md) and emitted by
cem-colors.html. D5 only declares --cem-zebra-angle and the ring composition recipes below.
| Token | Value | Description | tier |
|---|---|---|---|
--cem-zebra-angle | 45deg | Stripe angle for gradient-mode zebra | recommended |
Pending background stripes are separate from zebra focus/selection rings. The square tile and diagonal five-stop gradient follow the D0 pending recipe. The canonical angle is 45 degrees; arbitrary angles need a matching tile recipe to retain seamless edges.
| Token | Value | Description | tier |
|---|---|---|---|
--cem-pending-angle | 45deg | Pending background gradient tilt | recommended |
--cem-pending-tile-size | 2rem | Square background tile and distance traveled per cycle | recommended |
/* 3-stripe ring: focus/target/selected (normal themes) */
--cem-ring-zebra-3:
0 0 0 calc(1 * var(--cem-zebra-strip-size)) var(--cem-zebra-color-1),
0 0 0 calc(2 * var(--cem-zebra-strip-size)) var(--cem-zebra-color-2),
0 0 0 calc(3 * var(--cem-zebra-strip-size)) var(--cem-zebra-color-3);
/* 4-stripe ring: intent + focus + target + selected (contrast themes) */
--cem-ring-zebra-4:
0 0 0 calc(1 * var(--cem-zebra-strip-size)) var(--cem-zebra-color-0),
0 0 0 calc(2 * var(--cem-zebra-strip-size)) var(--cem-zebra-color-1),
0 0 0 calc(3 * var(--cem-zebra-strip-size)) var(--cem-zebra-color-2),
0 0 0 calc(4 * var(--cem-zebra-strip-size)) var(--cem-zebra-color-3);
Why box-shadow?
outline in forced-colors contexts (§8.2).| Token | Value | Description | tier |
|---|---|---|---|
--cem-ring-zebra-3 | 0 0 0 calc(1 * var(--cem-zebra-strip-size)) var(--cem-zebra-color-1), 0 0 0 calc(2 * var(--cem-zebra-strip-size)) var(--cem-zebra-color-2), 0 0 0 calc(3 * var(--cem-zebra-strip-size)) var(--cem-zebra-color-3) | 3-stripe focus/target/selected ring | recommended |
--cem-ring-zebra-4 | 0 0 0 calc(1 * var(--cem-zebra-strip-size)) var(--cem-zebra-color-0), 0 0 0 calc(2 * var(--cem-zebra-strip-size)) var(--cem-zebra-color-1), 0 0 0 calc(3 * var(--cem-zebra-strip-size)) var(--cem-zebra-color-2), 0 0 0 calc(4 * var(--cem-zebra-strip-size)) var(--cem-zebra-color-3) | 4-stripe ring (contrast themes) | recommended |
| Token | Value |
|---|---|
--cem-ring-zebra-3 | 0 0 0 var(--cem-stroke-focus) Highlight |
--cem-ring-zebra-4 | 0 0 0 var(--cem-stroke-focus) Highlight |
cem-stroke-rings-forced carries forced-colors fallback values for the rings (Principle P4). Generator-only — no
new tokens. The same ring names are redeclared inside @media (forced-colors: active) :root { … }.
Input indicators may reuse one locally composed stripe stack for both full-boundary and bottom-boundary appearances.
D5 represents the appearance as a unitless geometry selector rather than an inherited box-shadow recipe:
| Token | Value | Description | tier |
|---|---|---|---|
--cem-indicator-appearance-outline | 0 | Put cumulative stripe width in box-shadow spread, producing a full outline | recommended |
--cem-indicator-appearance-underline | 1 | Put cumulative stripe width in positive block-axis offset and zero the spread, producing an underline | recommended |
For appearance selector a and cumulative stripe width w, every shadow uses:
0 calc(a * w) 0 calc((1 - a) * w) <stripe-color>
At 0, the Y offset is zero and spread produces a concentric outline. At 1, spread is zero and positive cumulative
Y offsets leave only the bottom portions visible. Components MUST compose the shadow list locally so state-local stripe
colors and widths resolve at the paint target. A root-declared recipe containing nested var(--stripe-color) references
resolves those references at the declaration scope and can freeze descendant state overrides.
The transform does not fix a semantic-role count. Input components omit the shadow stack at rest. Invalid state activates an
anchor using --cem-stroke-boundary; pending uses --cem-stroke-pending. Focus
and selection activate independent stripes using --cem-zebra-strip-size.
Choice indicators paint the native input rather than its enclosing label.
Native radios use their built-in dot for selection and omit an additional selected
stripe. Their focus, invalid and pending outlines use a decorative ::before on the
input with --cem-bend-circle to follow the native circle; the native dot and
input appearance remain intact. Checkbox and switch selection stripes remain.
Inactive anchor width is zero; outside stripe changes do not affect layout. A component-friendly property such as
--cem-input-indicator-appearance is an adapter hook, not a canonical D5 token; its supported values are references to
the two tokens above.
box-shadow is not a forced-colors fallback. Components MUST remove the shadow stack in forced colors, preserve native
state semantics, and provide a system-color full outline for focus-visible. An underline selection in normal rendering
does not weaken that forced-colors focus requirement.
Prefer :focus-visible semantics. If the platform needs a polyfilled focus-ring behavior, align with heuristics similar to Material Web’s focus ring utilities (see references).
CEM recognizes three common divider roles (same thickness basis, different usage):
All three should normally map to --cem-stroke-divider (hairline). Promote to --cem-stroke-boundary-strong when:
Avoid creating many inset tokens. Use a single inset rule:
Spacing and inset should use D1 tokens. The canonical divider is a track, not a bare line:
--cem-gap-related, --cem-gap-group, --cem-gap-block, or
--cem-gap-section).--cem-coupling-guard-min.--cem-inset-container along the line's logical start edge for the standard inset pattern.margin-block: calc(
(max(var(--cem-gap-group), var(--cem-coupling-guard-min)) - var(--cem-stroke-divider)) / 2
);
border-block-start: var(--cem-stroke-divider) solid var(--cem-separator-color);
For a vertical divider, transpose block and inline axes. The line plus both cross-axis margins equals the divider
track. A bare --cem-stroke-divider line cannot stand in for either D1 relationship spacing or the D2 guard.
Indicator rings should follow component rounding:
border-radius: var(--cem-bend-control);
box-shadow: var(--cem-ring-zebra-3);
If the ring is outside, it may require a slightly larger radius:
border-radius: calc(var(--cem-bend-control) + var(--cem-stroke-indicator-offset));
Where indicators sit outside the edge, ensure surrounding layout provides enough breathing room. Prefer:
--cem-coupling-guard-min
and --cem-coupling-haloTreat --cem-coupling-guard-min as the default clearance budget between adjacent operable zones.
If you increase any of the following, you must also increase surrounding D1 spacing and/or D2 guard/halo
(or accept overlap):
3 * --cem-zebra-strip-size (normal) / 4 * --cem-zebra-strip-size (contrast)--cem-stroke-indicator-offset + --cem-stroke-focusSee the D2 compatibility rule in cem-coupling.md §4.1.1.
Elevation and stroke are substitutes:
The focus indicator must be large/visible enough to be reliably perceived. The default is a 3px perimeter (or equivalent area) around the focused component, which D5’s --cem-stroke-focus (aliased to --cem-stroke-strong) supports.
In forced-colors: active contexts:
outline (system colors) and avoid relying on subtle shadowsCanvasText / Highlight as appropriate in D0 mapping--cem-separator-color, which resolves to CanvasText; retain the D5 thickness and the
D1/D2 divider-track geometrySuggested baseline:
@media (forced-colors: active) {
.cem-focusable:focus-visible {
outline: var(--cem-stroke-focus) solid CanvasText;
outline-offset: var(--cem-stroke-indicator-offset);
box-shadow: none;
}
}
Avoid
/* Causes layout shift */
.control:focus { border-width: 2px; }
Prefer
.control:focus-visible {
outline: var(--cem-stroke-focus) solid currentColor;
outline-offset: var(--cem-stroke-indicator-offset);
}
or (for rounded geometry / zebra):
.control:focus-visible {
box-shadow: var(--cem-ring-zebra-3);
}
If multiple indicators can apply, compose in a stable order:
In zebra, this is naturally expressed as concentric stripes.
Current implementation uses:
.action { box-shadow: var(--cem-action-box-shadow); }
.action:hover { --cem-action-box-shadow: var(--cem-action-box-shadow-hover); }
.action:active { --cem-action-box-shadow: var(--cem-action-box-shadow-active); }
In contrast themes, --cem-action-box-shadow becomes the zebra ring.
This section is non-normative. It documents how to map framework tokens/variables into the CEM D5 semantic endpoints.
Common MDC variables (examples seen in Angular Material customization workflows):
--mdc-outlined-text-field-outline-width → --cem-stroke-boundary--mdc-outlined-text-field-focus-outline-width → --cem-stroke-focus (or --cem-stroke-boundary-strong)--mdc-filled-text-field-active-indicator-height → --cem-stroke-divider--mdc-filled-text-field-focus-active-indicator-height → --cem-stroke-focus--mdc-outlined-card-outline-width → --cem-stroke-boundaryAngular Material also exposes higher-level variables in some setups, e.g.:
--mat-form-field-outlined-focus-outline-width → --cem-stroke-focusGuidance: map these variables from CEM tokens; do not treat them as canonical tokens in CEM.
MUI System
sx={{ border: 1 }} expresses a border thickness via the theme’s borders scale.border: 1 (typical) to --cem-stroke-boundary (hairline).MUI Joy UI Joy exposes per-component CSS variables for focus ring geometry (examples):
--Input-focusedThickness → --cem-stroke-focus--Input-focusedInset → whether focus is inset or outset (maps to indicator placement policy)--Input-focusedOffset → --cem-stroke-indicator-offset| Token | Category | Required | Meaning |
|---|---|---|---|
--cem-stroke-none | Basis | ✓ | No stroke |
--cem-stroke-hair | Basis | ✓ | 1px hairline |
--cem-stroke-standard | Basis | ✓ | 2px standard indicator |
--cem-stroke-strong | Basis | ✓ | 3px strong separation |
--cem-stroke-boundary | Semantic | ✓ | Default component boundary |
--cem-stroke-boundary-strong | Semantic | Optional | Strong boundary for cases where elevation/shadow cannot carry separation |
--cem-stroke-divider | Semantic | ✓ | Default separator/divider |
--cem-stroke-grid | Semantic | Optional | Divider alias for structured content (tables/grids) |
--cem-stroke-focus | Semantic | ✓ | Focus-visible indicator width |
--cem-stroke-selected | Semantic | ✓ | Selection indicator width |
--cem-stroke-target | Semantic | ✓ | Target indicator width |
--cem-stroke-pending | Semantic | Optional | Persistent pending/loading indicator width |
--cem-stroke-indicator-offset | Placement | ✓ | Ring/outline offset distance |
--cem-zebra-strip-size | Pattern | Optional | Stripe thickness for zebra indicators |
--cem-zebra-angle | Pattern | Optional | Stripe angle for gradient zebra |
--cem-zebra-color-0..3 | Pattern | Optional | Concentric zebra stripe colors (intent/focus/target/selected) |
--cem-action-box-shadow | Adapter hook | Optional | Existing action indicator/shadow hook (implementation detail) |
D5 v1.0 became canonical after cross-dimension review on August 10, 2026. The review confirmed that D0 owns separator color, D1 owns relationship spacing and inset, D2 floors the complete divider track, and D5 owns the line and indicator geometry.
Treat as major (breaking) if you:
--cem-stroke-focus no longer representing focus thickness).Treat as minor/patch if you:
2px → 3px in contrast theme only).| Source table | Section | Description |
|---|---|---|
cem-stroke-pending-pattern | §5 | Pending gradient angle and tile geometry |
cem-stroke-basis | §3.1 | Stroke basis: --cem-stroke-{none,hair,standard,strong} |
cem-stroke-semantic | §4.1 | Semantic endpoints: --cem-stroke-{boundary,boundary-strong,divider,grid,focus,selected,target,pending,indicator-offset} |
cem-stroke-zebra-pattern | §5.2 | --cem-zebra-angle (gradient-mode geometry) |
cem-stroke-action-outline | Contrast action contours | Action contour display at ordinary/contrast theme boundaries |
cem-stroke-rings | §5.3 | Ring composition recipes: --cem-ring-zebra-3, --cem-ring-zebra-4 |
cem-stroke-rings-forced | §5.3 | Forced-colors fallback values for the rings (generator-only; no new tokens) |
cem-stroke-indicator-appearance | §5.4 | Unitless outline/underline geometry selectors |
Generator derivation rules:
cem-stroke-basis, cem-stroke-semantic, cem-stroke-zebra-pattern, cem-stroke-rings, cem-stroke-action-outline,
cem-stroke-indicator-appearance → token list (tier in last column).cem-stroke-rings-forced → override data emitted inside @media (forced-colors: active) :root { … }; no new
tokens.--cem-zebra-strip-size and --cem-zebra-color-{0..3} are owned by D0 (cem-colors.md) and NOT emitted here;
the ring recipes reference them via var() (R-D5-1 resolution).--cem-action-box-shadow is an existing implementation detail of the action stylesheet, not part of the D5
manifest contract; this generator does not emit it.W3C WCAG 2.2 Understanding: Focus Appearance (Minimum) — https://www.w3.org/WAI/WCAG22/Understanding/focus-appearance.html
MDN: :focus-visible — https://developer.mozilla.org/en-US/docs/Web/CSS/:focus-visible
Material Web focus ring documentation — https://github.com/material-components/material-web/blob/main/docs/components/focus-ring.md
Material Design 3 component specs (stroke/focus indicator values are listed per component):
Google issue tracker example referencing OutlinedTextField outline thickness in specs — https://issuetracker.google.com/issues/191543915
Angular Material customization guidance — https://v12.material.angular.io/guide/customizing-component-styles
Angular Material outlined form-field focus border width discussion (example) — https://github.com/angular/components/issues/28023
Angular Material customization examples showing outline width variables (examples):
MUI System borders utilities — https://mui.com/system/borders/
MUI Joy UI Input variables (focus thickness/offset) — https://mui.com/joy-ui/react-input/
| Token | Value | Contrast | Description | tier |
|---|---|---|---|---|
--cem-action-outline-display | none | block | Enable the action contour in contrast themes; reset in light/dark/native scopes | required |
This source table is part of the D5 token manifest. The generator emits Value at the root and every theme boundary, then Contrast at contrast-light/dark boundaries. Geometry uses existing stroke endpoints: boundary at rest, standard on enabled hover, strong during activation, and pending while an action is in progress. Disabled suppresses pointer state changes. The contour sits inside the painted surface, so changing thickness does not change the hit area or layout. Focus zebra stays outside the operable bounds. Pending animates D0's current-intent gradient, with a brighter stripe in light contrast mode and the ordinary dark endpoints in dark mode, using the existing D7 pending duration and D5 tile size; the center is masked out. Reduced motion freezes it, and forced colors replaces it with the native pending outline.
cem-action[selected] uses the D0 selected stripe (--cem-zebra-color-3) with
D5 zebra geometry even without keyboard focus. Focus is stripe 1, target is
stripe 2 and selected is stripe 3. Selection does not require a new fill or an
additional hit area. Hover, held press, disabled and pending do not erase it.
In forced colors, suppress shadows and retain an inset SelectedItem boundary
separate from the outer focus/pending outline; neither changes layout.