Amends 0303 (decision 7, the props
vocabulary). 0303 carries a note; its body is unchanged.
Context
0303 decision 7 gave every wave 1 component one props vocabulary: a concept has one prop name across the kit, and the runtime’s own name for it is left out of the wrapper’s type. Wave 2 adds Surface, Card, Separator, Chip, StatusBadge and Badge, then Avatar, Spinner and Skeleton, and the design cross-check found that they need new words and three rules that the vocabulary table cannot hold: how spec identifiers are numbered when several specs run side by side, how a wrapper merges a class of its own with the caller’s, and how a role colour reaches a graphic that has no class. An Accepted ADR’s body changes only for typos and Enforcement, so the additions are recorded here.Decision
-
The wave 2 vocabulary. Decision 7 of 0303 stands; wave 2 adds or extends these rows:
The existing
variant,size,toneandlabelrows extend to Avatar (variantisAvatarVariant:defaultorsoft;sizeisSize;toneisTone;labelnames the image, and without it the avatar is hidden) and to Spinner (sizeisSize;toneisTextTone, the text rolesdefault,muted,accent,success,warninganddanger;labelmakes it one named status, and without it it is hidden).TextToneis the type 0303 gave Text, Heading and Icon, and Spinner reuses it. Later components add their rows to this table with the PR that adds the component. -
Spec ids run on. Where several specs of one feature are written side by side, their
FR-,P-andSC-ids continue one numbering, so a bare id is unique across them and every id is used once. Each test names its spec with thesta-<n>:prefix of@covers. Wave 1 numbered each spec from 001; wave 2 and later do not. -
One class merger. A wrapper that adds a class of its own merges it with the caller’s through HeroUI’s
cn(@heroui/reacton web,heroui-nativeon native), with the caller’s class last, so a conflicting caller utility wins. The wrapper never concatenates class strings by hand. -
A role colour on a graphic is an inline
var(--<role>). A status dot, an icon or a spinner that must draw a theme role takes it as an inline style on web (colororbackgroundColorset to the theme’s CSS variable) anduseThemeColoron native, never a utility class. It needs no@sourceline, a test can read it in jsdom, and it follows the theme.
Consequences
- The props of Surface, Card, Separator, Chip, StatusBadge and Badge read the same on web and native, and the word
orientation, the logicalplacementand the compound-part names are shared by every later component that needs them. - A logical
placementkeeps a badge on the reading-end corner in Arabic, which HeroUI’s physical corners do not. - Chip and StatusBadge are display only: interaction is a tag group or a button, so no chip takes a press handler.
- The spec for a feature that spans several components stays in one place, and a test id is never ambiguous.
- Native Separator loses HeroUI Native’s
thinandthickvariants from its type;thicknessstays.
Enforcement
- planned — the
@stackra/uitype tests of each component assert that the runtime props extend the contract and refuse the raw HeroUI names this vocabulary replaces; wave 2’s tests already do (color,onClick,onPress, HeroUI’s physicalplacement, native Separator’sthinandthick). tools/scripts/spec-trace.mjs(run bypnpm validate) refuses a spec id that no test covers with an@coverstag, so a dropped id is visible.- planned — a lint rule against hand-joined class strings in
@stackra/uiwrappers. - Until these exist, review (
architecture-guardian) checks this page.
Alternatives
- Rows added to 0303’s body. Not chosen: an Accepted ADR’s body changes only for typos and Enforcement.
- A shared
Placementenum for badges and overlays. Not chosen: React Aria’s overlay placements ("top start") mean a side of the trigger, not a corner of an anchor. - Physical
placementwith a note that it does not flip. Not chosen: every Arabic screen would need its own mapping. - Per-spec numbering from 001. Not chosen for wave 2: six specs in one wave collided on every bare id.