---ci--- project: atelier phase: 6 milestone: v0.3 status: complete requirements: covered: [ATELIER-60..91] partial: [] ---/ci---
6.2 KiB
RTL and Bidi — Derived Rules
Derives from
domains/i18n/first-principles.md. Covers P6 (Text Direction is a Layout Primitive) and P7 (Layout Accommodates Expansion). Referenced bytesting-i18n.md(RTL coverage is an e2e tier). Grounded in W3C i18n bidi authoring, UAX #9, anddomains/uiux/accessibility.md.
Text Direction is a Layout Primitive (P6 Text Direction is a Layout Primitive)
-
RTL and bidi are first-class layout concerns, not a CSS afterthought. The layout is designed for both directions from the first commit, not retrofitted when an RTL locale ships.
-
Logical CSS properties over physical properties, always. The browser resolves logical → physical from the
dirattribute; the code never has to.Physical (LTR-only) Logical (dir-aware) Resolves to in RTL margin-leftmargin-inline-startmargin-rightmargin-rightmargin-inline-endmargin-leftpadding-leftpadding-inline-startpadding-rightleft: 0inset-inline-start: 0right: 0text-align: lefttext-align: starttext-align: rightfloat: leftuse flexbox/grid + inline-startwhere supportedmirrored -
The
dirattribute is set on the document root (<html dir="rtl">) and on subtrees whose direction differs from the document (<span dir="ltr">for an embedded Latin run).diris the contract the bidi algorithm (UAX #9) reads; do not fake direction withtext-alignalone.
The Bidi Algorithm (UAX #9)
- The Unicode bidi algorithm resolves inline reordering of mixed- direction runs. The browser applies it; the author's job is to mark direction correctly, not to reorder by hand.
- A string like
"The price is 15 USD"in an RTL context renders with the Latin run"15 USD"in LTR within the RTL line — the algorithm handles it if the container'sdiris set. Withoutdir, numbers and Latin fragments drift to the wrong edge. dir="auto"on a container infers direction from the first strong directional character of its content — useful for user-generated content whose direction is unknown.dir="auto"is not a replacement fordir="rtl"on a known-RTL document.
Mirroring (Icons, Controls, Diagrams)
- Direction-aware icons mirror in RTL: a "back" arrow pointing left in LTR points right in RTL. A "refresh" circular arrow does not mirror. The rule: icons that imply direction mirror; icons that imply time or rotation do not.
- Use
[dir="rtl"]selectors or logical icon variants — nevertransform: scaleX(-1)as a one-off hack scattered across components. Centralize the mirroring rule (a token, a component prop) so it is auditable. - Numbers do not mirror.
"15 USD"in an RTL line is still"15 USD"left-to-right inside the bidi run; mirroring it to"DSU 51"is a correctness violation. - Diagrams and flowcharts: a left-to-right process flow in LTR is a right-to-left flow in RTL. Decide per diagram whether the flow mirrors (most do) or is direction-neutral (some scientific schematics).
Layout Accommodates Expansion (P7 Layout Accommodates Expansion)
- Translated text expands. German is ~30% longer than English;
Japanese is often shorter but taller; RTL mirroring shifts every
visual anchor. Layouts are flexible:
- No fixed pixel widths on translatable text containers.
- No
white-space: nowrapon translatable strings. - No
text-overflow: ellipsiswithout atitlecarrying the full string. - Buttons sized to fit their longest locale variant, not the source.
- A layout that breaks at +30% width is a layout that is wrong for most of the world's locales. Designing for the worst case up front is cheaper than reworking every screen when the first long-form locale ships.
Common Pitfalls
| Pitfall | Why it breaks | Fix |
|---|---|---|
margin-left everywhere |
In RTL the start is the right; margin-left leaves the right side unstyled |
margin-inline-start |
text-align: left for "default" alignment |
In RTL the default is right; left pins content to the wrong edge |
text-align: start |
| Icons hardcoded to LTR orientation | "Back" arrow points the wrong way in RTL | Mirror direction-implying icons via [dir="rtl"] |
| Numbers mirrored with the layout | Numbers are LTR inside RTL; mirroring produces garbage | Leave number runs LTR; the bidi algorithm handles embedding |
Fixed width: 120px on a button |
German button label overflows and truncates | min-width + max-width + flex; let content size |
position: absolute; left: 0 |
Pins to the physical left in both directions | inset-inline-start: 0 |
Fake direction with text-align only |
The bidi algorithm reads dir, not text-align; mixed runs reorder wrong |
Set dir on the container |
What Violates RTL/Bidi Discipline
| Violation | Principle |
|---|---|
A layout with no dir attribute, assuming LTR |
P6 Text Direction is a Layout Primitive |
margin-left / left: 0 / text-align: left throughout |
P6 Text Direction is a Layout Primitive |
| A "back" arrow that points left in the RTL build | P6 Text Direction is a Layout Primitive |
| Numbers mirrored to read right-to-left | P6 Text Direction is a Layout Primitive |
width: 100px on a text container that overflows in German |
P7 Layout Accommodates Expansion |
white-space: nowrap on a translated label |
P7 Layout Accommodates Expansion |
dir faked with text-align and no dir attribute |
P6 Text Direction is a Layout Primitive |
| No RTL build until the first RTL locale ships | P6 Text Direction is a Layout Primitive |
Relationship to Other Domains
domains/uiux/accessibility.md— RTL support is an accessibility concern for non-Latin-script users; WCAG 2.1 AA requires that direction be set correctly.domains/uiux/components.md— components are built with logical properties so they survive direction and expansion without per- locale overrides.domains/i18n/testing-i18n.md— RTL coverage is an e2e-tier test; pseudo-locale mirroring surfaces direction bugs early.domains/i18n/locale-resources.md— thediris part of the locale's metadata, carried alongside the resource bundle.