RTL support
Krysalicss is bidirectional by default. Setting dir="rtl" on
<html>, <body>, or any subtree
mirrors the framework-emitted layout without a recompile, an alternate
stylesheet, or a per-component override.
This page explains how the framework gets there, what consumers are still on the hook for in their own markup, and the recommended way to verify a page works in both directions.
Logical properties, not physical
Every framework rule that controls inline-axis layout uses logical
properties (margin-inline-start, padding-inline-end,
border-inline-start, inset-inline-start, text-align: start) rather
than physical ones (margin-left, padding-right, text-align: left).
A logical property names the role of the edge, not its visual side. The
browser maps it to a visual side based on the writing mode and direction
of the element. In dir="ltr", inline-start is the left edge; in
dir="rtl", it's the right edge. The same compiled rule produces the
correct visual layout in both directions: no [dir="rtl"] overrides, no
double-stylesheet, no compile-time direction switch.
This applies across the framework: the container's gutters, the flex grid's offset utilities, the alert's accent border, the navbar's mobile toggle alignment, the dropdown's anchor position, the file input's button gap, the select's caret padding, the modal's centered fallback: every one of them is direction-agnostic.
Where physical stays
A few cases are unavoidably physical because CSS does not yet expose a logical equivalent:
background-positionhas no logical-axis keyword. The select's caret usesright $padding centerand a:dir(rtl)companion rule that flips toleft $padding center. The padding switches direction the same way.transform: translateX()is physical by definition. The switch component's thumb anchors viainset-inline-startand translates withtranslateXtoward the inline-end edge; a:dir(rtl)companion rule negates the translation so the thumb still travels visually to the end-edge in both directions.
These are the only places the framework writes a direction-conditional
rule, and they do so via the :dir() pseudo-class: supported in every
browser within our last 2 years baseline.
What consumers verify
The framework guarantees its own rules. Consumers writing markup on top still have a few responsibilities:
- Set
dircorrectly. Default todir="ltr"on<html>. For RTL pages, setdir="rtl"on<html>(or on a subtree if mixing). Do not rely onlangalone: most browsers don't infer direction from language. - Author your own CSS with logical properties. Project styles that
use
margin-left,text-align: left,border-right, etc. will not flip even though the framework's rules do. Match the framework's convention in your own code. - Mirror directional iconography. Chevrons, arrows, breadcrumb
separators, and similar marks need to flip. Use SVG transforms scoped
to
:dir(rtl), or distinct icon sets per direction. - Numeric content and code stay LTR. Wrap code blocks, phone
numbers, currency strings, and the like in
<bdo dir="ltr">or containers with explicitdir="ltr": RTL contexts shouldn't reverse numbers or code.
How to test
The recommended approach is parallel rendering. Build the page once in
dir="ltr", capture screenshots, then toggle <html dir="rtl"> and
capture again. Compare the two:
- Padding and margins should swap inline-axis sides.
- Text alignment should flip (left → right) wherever
text-align: startis in effect. - Borders that the framework places on the inline-start (alert accent, for example) should appear on the visually opposite side.
- Caret positions, dropdown anchors, and switch thumbs should all travel toward the inline-end edge.
The Krysalicss visual regression suite includes an RTL spec that does
exactly this: tests/visual/rtl.spec.ts mirrors a representative slice
of pages under dir="rtl" and asserts no regressions. Consumer projects
adopting the same pattern catch direction-specific bugs the same way
they catch theme regressions.
What to do when something doesn't flip
If you find a framework rule that doesn't flip cleanly under dir="rtl",
that is a bug: file it. The contract is that every framework-emitted
rule is direction-agnostic. The escape hatches above (background-position,
transform) are documented exceptions; new ones should not appear
silently.