:dir() and unicode-bidi: Controlling Multilingual Layouts Without JavaScript
AI generated
{ }
@
CSS · Internationalization · RTL · Accessibility
:dir() and unicode-bidi
RTL and LTR dependent styling directly in CSS, with no JavaScript direction detection at all

Once a shop offers a store view with Arabic or Hebrew, the layout has to adapt automatically to the writing direction, right to left instead of left to right. The :dir() pseudo-class reacts directly to an element's actually computed text direction, unicode-bidi controls behavior for mixed text, and logical properties handle the rest, with no JavaScript snippet ever needing to detect the direction.

14 min read :dir() · unicode-bidi · logical properties Chrome · Firefox · Safari (current)

1. Why multilingual shops need more than a dir attribute on html

The obvious first step toward an RTL capable layout is the dir attribute on the html element, which sets the base text direction for the whole page. But that alone is not always enough, because individual elements can have a different direction, for example an embedded product code field that should always read left to right even in an Arabic store view, or a user generated comment whose language, and therefore direction, differs from the rest of the page.

For exactly those cases, a single, global dir attribute is not enough. CSS needs a way to target the actually effective text direction of a single element, regardless of whether that direction was inherited from the ancestor html element, set via a dir attribute on the element itself, or automatically detected by the browser from the text content. That is exactly what the :dir() pseudo-class does.

2. The :dir() pseudo-class: targeting the computed direction on purpose

:dir(rtl) and :dir(ltr) target an element's actually computed text direction, not just an explicitly set dir attribute. That is the decisive difference from the simpler attribute selector [dir="rtl"]: an element with dir="auto", whose direction the browser derives from its contained text, matches :dir() correctly according to the detected direction, while [dir="rtl"] would not match at all in that case, because the markup literally only says auto.

In practice that means an icon that sits to the left of text in an LTR context and should sit to the right in an RTL context can be reliably controlled with two :dir() rules, with no JavaScript ever needing to determine the actual direction at runtime and write it back as a class on the element. The pseudo-class also works on any arbitrary element, not just html or body, which makes it usable for individual, mixed language components within a single page too.


/* Icon alignment depending on the computed text direction */
.search-input-wrapper .icon {
  position: absolute;
  inset-inline-start: 0.75rem;
}

/* :dir() also reacts to dir="auto", not only an explicit dir="rtl" */
.product-badge:dir(rtl) {
  transform: scaleX(-1);
}

3. unicode-bidi in detail: controlling the bidirectional text algorithm

By default, the browser arranges mixed text, for example an Arabic sentence with an embedded Latin product number, through the Unicode Bidirectional Algorithm, which determines each character's direction from its Unicode properties. In most cases that algorithm produces a correct result, but for technical content like product codes, IBAN numbers, or URL fragments inside an RTL sentence, the automatic arrangement can produce a reading order that confuses native speakers.

unicode-bidi controls how strongly an element intervenes in that algorithm. The value isolate encapsulates an element's content against its surroundings, so an embedded number or Latin term is not influenced by the surrounding RTL direction and vice versa. plaintext goes further and lets the browser derive the element's direction entirely from its own content, independent of the parent element's direction, which fits particularly well for user generated content with an unknown language.


/* SKU stays readable even when embedded inside an RTL sentence */
.sku-inline {
  unicode-bidi: isolate;
  direction: ltr;
}

/* User generated comment: derive direction entirely from content */
.user-comment {
  unicode-bidi: plaintext;
}

4. Interplay with logical properties: margin, padding and alignment

Classic physical properties like margin-left or text-align: left describe a fixed screen side and ignore text direction entirely, which is why a layout built for LTR simply looks wrong in an RTL store view without further adjustment: spacing sits on the wrong side, text aligns to the wrong edge. Logical properties like margin-inline-start, padding-inline-end, or text-align: start solve this problem structurally, by always aligning relative to the current writing direction instead of referring to a fixed screen side.

In practice that means a layout built once with logical properties adapts automatically whenever the text direction changes, whether that change comes from the dir attribute, a :dir() rule, or unicode-bidi: plaintext. :dir() is still necessary for anything logical properties don't cover, for example mirroring an icon or a completely different transform rule depending on direction.


/* Works automatically in LTR and RTL, with no :dir() rule */
.card {
  padding-inline: 1.5rem;
  margin-inline-start: 1rem;
  text-align: start;
  border-inline-start: 3px solid var(--accent);
}

5. Mirroring icons and arrows: where :dir() stays indispensable

Logical properties solve spacing, alignment and borders, but not every visual element adapts automatically to a direction change. An arrow icon that points right in LTR to mean next or next page has to point left in RTL to carry the same meaning, because in RTL the intended reading direction, and therefore the intuitive meaning of forward, reverses as well. An SVG icon itself does not know its meaning, which is why that mirroring has to happen explicitly in CSS.

:dir(rtl) combined with a transform: scaleX(-1) rule is the most reliable approach for that, because it is directly tied to the actually effective direction and applies automatically regardless of whether the direction was inherited from the html element or overridden locally. It matters here to only mirror icons that actually carry directional meaning, like arrows or chevrons, but not icons like a cart symbol or a clock icon whose meaning is direction independent.


/* Only mirror directionally meaningful icons, not neutral symbols */
.icon-arrow-next:dir(rtl),
.icon-chevron:dir(rtl) {
  transform: scaleX(-1);
}

6. Forms and input fields in mixed language layouts

Input fields for structured data like email addresses, phone numbers, or IBAN numbers should always keep direction: ltr regardless of the page direction, because these formats themselves follow fixed international conventions and a mirrored display makes entry harder rather than easier. At the same time, the label next to the field should keep following the page direction, so the whole form stays visually consistent with the rest of the RTL page.

unicode-bidi: isolate combined with direction: ltr on the input field itself achieves exactly that: the field stays internally LTR and isolated from its surroundings, while the label, error message, and layout around it follow the inherited or :dir() set direction normally. This combination matters especially for checkout forms, where an incorrectly displayed IBAN or phone number can directly lead to input errors and abandoned orders.

7. Testing an RTL layout without switching the browser language

For a quick visual test, it is enough to set dir="rtl" temporarily right in the DevTools element inspector on the html element, without actually switching the system language or the store view. That quickly surfaces layout breaks like misplaced icons, physical instead of logical spacing, or accidentally mirrored, direction neutral symbols, before a real Arabic or Hebrew store view goes live.

A common mistake when testing is checking only the rough text direction while missing detail components like tooltips, toast notifications, or breadcrumb separators, which are often built with fixed, physical CSS values and therefore show up in the wrong place under RTL. A systematic pass through every reusable component of a design system uncovers such gaps far more reliably than a single glance at the homepage.

8. Accessibility: why correct bidi handling is also a WCAG concern

An incorrectly arranged or visually confusing text direction is not just a cosmetic problem, it directly affects readability and therefore a page's accessibility. Screen reader software reads DOM content in markup order, independent of the visual CSS presentation, which means an order that is only mirrored visually through direction but not adjusted in the markup can produce an inconsistent experience that differs between sighted and blind users.

The robust solution is to set the dir attribute correctly in HTML instead of forcing direction exclusively through CSS, because screen readers and other assistive technologies read the dir attribute directly, while a purely visual CSS direction change without a matching attribute gets ignored. :dir() and unicode-bidi complement a correctly set dir attribute with the matching visual presentation, they do not replace it.

9. Practical use in a shop: multilingual store views with Arabic or Hebrew

For a Magento shop with an Arabic or Hebrew store view, it pays off to consistently switch the Hyvä theme to logical properties as the foundation, complemented by targeted :dir() rules for icon mirroring and unicode-bidi: isolate for every structured input field. That combination covers the vast majority of RTL requirements, without needing a separate, mirrored stylesheet maintained for every store view.

The long term advantage is that a theme built this way stays RTL capable automatically, even when another RTL language like Persian or Urdu gets added later, because the logic is tied not to a single language but to the actually computed text direction. A theme built with physical properties after the fact would instead need manual reworking for every new direction.

Technique Reacts to Typical use Replaces JavaScript?
:dir(rtl) / :dir(ltr) Computed element direction Icon mirroring, direction dependent rules Yes
[dir="rtl"] Only an explicit dir attribute Simple, static RTL switching Partially
unicode-bidi: isolate Bidi algorithm per element Embedded numbers, IBAN, SKU Yes
Logical properties Current writing direction Spacing, alignment, borders Yes

Mironsoft

Modern CSS, layout architecture and rendering performance

CSS that stays maintainable instead of breaking with every change?

We review existing stylesheets for specificity chaos and layout thrashing, then build a CSS architecture with cascade layers, custom properties and modern layout primitives that still makes sense after the tenth feature.

CSS Audit

Systematically uncovering specificity issues, cascade conflicts and unused selectors.

Architecture Refactoring

Introducing cascade layers, custom properties and design tokens cleanly.

Performance Tuning

Fixing layout thrashing, expensive selectors and rendering bottlenecks.

10. Summary

:dir() and unicode-bidi: The Essentials at a Glance

Core idea

:dir() reacts to an element's actually computed text direction, even with dir="auto", replacing JavaScript based direction detection.

unicode-bidi

isolate encapsulates embedded content against its surroundings, plaintext derives direction entirely from its own content, important for numbers and user generated text.

Logical properties

margin-inline-start, padding-inline-end and text-align: start adapt automatically to the writing direction, with no custom :dir() rules needed.

Icon mirroring

:dir(rtl) with transform: scaleX(-1) stays necessary for directionally meaningful icons like arrows, direction neutral symbols should stay unchanged.

11. FAQ: :dir() and unicode-bidi: The Essentials at a Glance

1What is the difference between :dir() and [dir="rtl"]?
[dir="rtl"] only matches a literally set dir attribute. :dir(rtl) reacts to the actually computed direction, even when it comes from dir="auto" or inheritance.
2When should I use unicode-bidi: isolate?
Whenever an element like a product code, IBAN, or URL is embedded inside text with a different writing direction and should not be influenced by the surrounding direction.
3What does unicode-bidi: plaintext do differently from isolate?
plaintext derives an element's direction entirely from its own content, independent of the parent element. That fits particularly well for user generated comments with an unknown language.
4Isn't dir="rtl" on the html element enough?
For the rough page direction, yes, but individual elements like product codes or multilingual comments often need their own, different direction, which can only be targeted through :dir() or unicode-bidi.
5Do I need to mirror all icons in RTL?
No, only icons with actual directional meaning like arrows or chevrons. Direction neutral symbols like a cart or clock icon should stay unchanged.
6What are logical properties and why do I need them for RTL?
Logical properties like margin-inline-start align relative to the current writing direction instead of a fixed screen side, which makes a layout look correct automatically in both LTR and RTL.
7How do I test an RTL layout without switching the language?
Setting dir="rtl" temporarily in the DevTools element inspector on the html element shows the mirrored layout immediately, with no need to switch the system language or store view.
8Should IBAN numbers be shown mirrored in an RTL form?
No. Structured formats like IBAN or phone numbers should always stay in their fixed international notation with direction: ltr and unicode-bidi: isolate, regardless of the page direction.
9Does :dir() work on any arbitrary element?
Yes, the pseudo-class can be applied to any element, not just html or body, which makes it usable for individual, mixed language components within a single page as well.
10What happens if I use neither :dir() nor logical properties?
Physical properties like margin-left ignore text direction completely, which means an RTL layout simply looks wrong without adjustment: spacing and alignment consistently sit on the wrong side.