Accessibility
PendingValue, PendingValueAppearance, PendingValueLines, PendingValueWidth from @prepared911/ui/pending-value.
While pending, the host holds the label as real text. A screen reader that reaches the value (browsing a list, tabbing a card, reading a data list) reads "Location, Locating address", not a blank and not a bare placeholder glyph. With placeholder, the visible stand-in is aria-hidden and label is read as visually hidden text. In the skeleton appearance, Skeleton paints the line aria-hidden and reads label.
When the value arrives, the label layer gets aria-hidden and visibility: hidden, and the value layer is exposed. While pending the value layer is aria-hidden and inert, so a link or control inside it cannot be focused or read early.
role="status" or aria-live on the label. A live region announces when its content changes. A card with six pending fields would announce six labels as it renders, and six values as they land, which buries the call-taker's audio. The label does not need an announcement; it needs to be there when the value is reached, and as text it is.aria-busy on the host. aria-busy tells assistive technology to hold changes to a region until it is false. Some screen readers also drop the content of a busy subtree from browse mode, which would hide the very label that says the value is pending. Put aria-busy on the region that is being replaced as a whole (DataList does it for its own loading), not on one value.announcement reads one polite message through the shared announcer when the value arrives. Use it for a value the call-taker is waiting for; the default is silent. It is polite, never assertive, and is not read on the first render or when a value goes back to pending.role, aria-*, style or on* props, so a call site cannot hide the label or turn the host into a live region by accident.
The pending label binds --color-fg-subtle, which Chroma certifies at the label tier. index.browser.test.tsx measures it against the page in both themes. Default is available when the label must match the value. Muted is not allowed: it fails the gate below 16px. The arrived value takes the surrounding color.
The swap is a crossfade of opacity (and visibility) on --motion-glide-state. Nothing moves, so there is no movement to remove under reduced motion; the fade stays because it explains the state change. Under prefers-reduced-motion: reduce and data-motion="reduced" the end state is the same.
The label is text, so forced colors repaint it in system colors and pending is stated in words, not color. Sizes are rem space tokens and lh units of the type role, so they follow the user's text size. Skeleton keeps its own forced-colors fill.
PendingValue is not interactive and sets no tabIndex. It is not an empty state (use EmptyText) and not an error (use the component's error state): the label says the value is coming, not that it is absent or has failed.