- Date: 2026-06-06
- Status: Accepted
- Phase / Feature: Foundation v1 · B2 (icons) RTL handling
- Closes: ADR 0036 Q5 — "RTL icon opt-out (logos shouldn't flip)"
- Builds on: ADR 0036 §4 (icons, RTL-aware), T7.4 (per-icon exports + sprite)
Context
ADR 0036 §4 promised RTL-aware icons: directional icons (arrows, chevrons, back/forward) auto-flip when the document direction is rtl. The naïve implementation would flip every icon, breaking logos, brand marks, math signs, and content icons whose meaning is direction-independent. ADR 0036 Q5 parked the opt-out shape.
T7.4 just shipped per-icon exports and the sprite. This ADR settles which icons flip, which stay put, and how a downstream consumer drives the direction.
Decision
§1 — Default = keep (do not flip)
Most icons are content (check, x, person, info, error, alert). Defaulting to flip would silently invert logos and brand marks during RTL rollout — a hard-to-spot regression. Defaulting to keep makes adding rtl: 'flip' to a directional icon an explicit, reviewable opt-in.
§2 — Per-icon rtl flag
Each per-icon component (T7.4 surface) carries a static metadata flag:
CheckIcon.rtl = "keep"; // content
ChevronDownIcon.rtl = "keep"; // top-down chevron is direction-independent
// ChevronLeft/Right (future) — `rtl: "flip"` — actual directional intentImplementation: add rtl: 'flip' | 'keep' to BaseProps. IconBase looks the flag up and applies transform: scaleX(-1) when the current direction is rtl AND the flag is flip. Component author sets it once per icon at definition time.
For the v1 essentials (check, x, chevron-down, person, alert, info, error) every icon stays keep. None of them carries directional intent. Chevron-down is the closest call: in some patterns it suggests "expand below". The downside of flipping is a designer in Arabic seeing chevron arrows reversed from the rest of the chrome — net wrong. Keep.
§3 — Direction source
Three sources, in priority order:
- Explicit prop:
<CheckIcon dir="rtl" />. Highest priority; useful for one-off rendering inside a fixed-direction widget. - React context
<FoundationDirectionProvider value="rtl">. Wraps app routes / regions. Higher than CSS because some renderers (e.g. the ARNO canvas) compose icons outside a real DOM tree wheredirattribute doesn't propagate. - CSS
:dir(rtl)(DOM-only). Last resort; reads from the closest ancestor'sdirattribute via the standard CSS selector. TheIconBaseincludes a:dir(rtl)rule conditional on the flag so consumers that pass nothing get the standard behavior.
Per-instance opt-out is also available: <ChevronLeftIcon dir="ltr" /> forces no flip even when the surrounding direction is rtl.
§4 — Sprite
<symbol id="chevron-left-line"> ships with data-rtl="flip". Consumers using <use href="#chevron-left-line"/> apply the flip themselves (one CSS rule keyed on :dir(rtl) [data-rtl="flip"]) — the sprite is direction-agnostic markup.
The build script build-icons.mjs writes data-rtl on each <symbol> from the same metadata table the per-icon components consume.
§5 — Designer-facing surface
The Storybook icon catalog grows a "Direction" toggle. Flipping it re-renders every icon in the catalog under rtl. Icons that flip do so visibly; icons that stay put surface their keep status in a small badge so a designer reviewing icon coverage knows which decisions were made.
This decision is per-icon, not per-project. Brand exceptions (a logo that should flip in RTL despite being a brand mark — rare) ride the future custom-icon mechanism Q3 settles.
Anti-patterns explicit
- Do not flip by default. The cost of a missed
keep → flipflag is one wrong-direction chevron in an Arabic UI. The cost of a missedflip → keepflag is a backwards logo. Asymmetric cost wins the default choice. - Do not put per-instance overrides everywhere. The point of the metadata is "design pass settled this once." Per-instance is for the edge case (a chevron used as a row collapse indicator inside a numeric table that should not flip), not the norm.
- Do not rely on CSS-only resolution. The ARNO canvas renders icons outside the host DOM; the React context is the substrate that survives.
- Do not ship a sprite that has the
transform: scaleX(-1)already baked in forflipicons. The sprite must be direction-agnostic; the host applies the rule.
Open questions / parking
- Future 100-icon set classification. When T7.2/T7.3 ship the remaining 93 + 14 RTL counterparts, the per-icon flag must be set as part of the design pass. Reviewer checklist: "is this icon's meaning direction-dependent?". Document in T7.2 spec.
- User-added custom icons (Q3). The custom-icon ingestion flow must collect the
rtlflag as a required field at upload time; no implicit default. - Bi-directional logos. A logo that has a left-pointing arrow as a brand mark might want to flip. Rare; for now manual override per instance.