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-position has no logical-axis keyword. The select's caret uses right $padding center and a :dir(rtl) companion rule that flips to left $padding center. The padding switches direction the same way.
  • transform: translateX() is physical by definition. The switch component's thumb anchors via inset-inline-start and translates with translateX toward 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 dir correctly. Default to dir="ltr" on <html>. For RTL pages, set dir="rtl" on <html> (or on a subtree if mixing). Do not rely on lang alone: 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 explicit dir="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: start is 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.