Context
0303 decision 7 gave every component one props vocabulary, and 0304 and 0311 extended it for waves 2 and 3. Wave 4 adds the overlays (Modal, Drawer, AlertDialog, Popover, Menu, Tooltip), the confirmation pieces built on them (useConfirmation, LeaveConfirmation, ActionMenu), the messages (Toast,
Alert) and the progress indicators (ProgressBar, ProgressCircle). The overlays all open, close and dismiss, three of them
anchor to a trigger, and the two messages carry a tone. The two runtimes disagree on each word: HeroUI Native calls the
starting state isDefaultOpen, its sides left and right, and a dialog’s backdrop dismissal isCloseOnPress; HeroUI
for the web calls an Alert’s tone status and a Toast’s variant, and takes physical or logical sides as one
space-separated string. An Accepted ADR’s body changes only for typos and Enforcement, so the additions are recorded here.
Decision
-
The wave 4 vocabulary. Decision 7 of 0303 stands; wave 4 adds these rows:
-
Sides follow the reading direction.
placementisPlacement, never a physical side, andalignruns along the edge that side leaves open (on astartorendside,startis the top). The web Popover, Tooltip and Menu turn the pair into React Aria’s placement string. The native ones turnstartandendinto physical sides by text direction. The web Drawer does not: HeroUI lays itsleftandrightedges out with flexbox, so they already mean start and end, and a direction-based mapping would flip twice in right-to-left. It mapsstarttoleftandendtorightas a fixed table. Each is one pure function incore/shared by the whole group. Among the overlays the wordplacementis reserved forPlacement: the Drawer’s edge takes it, defaulting toend, and the two other things HeroUI callsplacement, the Modal’s vertical place (auto,top,center,bottom) and the Toast region’s side (top,bottom), take the wordpositionin their own contracts. -
One tone table for messages. Alert, AlertDialog and Toast take the five
Tonevalues as they are. A tone has one default icon and one announcement role inFEEDBACK_BY_TONE:alertforwarninganddanger, which interrupt assistive technology, andstatusfor the rest, which wait. A message never chooses its own role. -
The shared contracts live in
src/contracts.OpenStateContractProps,OverlayContractProps(the open state plus dismissal),PlacementContractPropsandFeedbackContractPropsare exported from@stackra/ui/contracts, and a component’s own contract extends the ones it needs. Menu and Tooltip, which dismiss themselves, take the open state and the placement but not the dismissal props.
Consequences
- An app drives any overlay with the same three props and places any anchored one with the same two, in English and in Arabic, on web and native.
- A tone reads the same on an Alert, an AlertDialog and a Toast, and a screen reader hears a failure at once and a success politely.
- A native overlay has no Escape key, so
isKeyboardDismissDisabledis unread there unless a component’s own page maps it to the Android back button, which the page then states. - Each component reads a prop it does not use (an Alert has no
isDismissable) as absent from its type, so a misuse is a type error, not a silent no-op.
Enforcement
packages/design-system/ui/src/core/functions/__tests__proves both placement functions, andpackages/design-system/ui/src/core/constants/__tests__proves the tone table.- Each component’s type test asserts that the runtime props extend the contract and refuse the raw HeroUI names this
vocabulary replaces (
isDefaultOpen,status,varianton a message,left,right). tools/scripts/spec-trace.mjs(run bypnpm validate) refuses a spec id that no test covers.- Until a lint rule exists, review (
architecture-guardian) checks this page.
Alternatives
- One name for the message tone as HeroUI has it,
statuson Alert andvarianton Toast. Not chosen: two words for one concept, andvariantalready means emphasis in 0303. - Physical
leftandrightsides. Not chosen: they would put a tooltip on the wrong side in Arabic, against the reading-direction rule of 0060. - A role chosen per message by the caller. Not chosen: a status message read as an alert interrupts the user, and the failure that needs it is the one a caller forgets to mark.