Theme studio

Design a theme, preview it live, then export it. Saved in this browser.

Quick picks #10b981
Generated scale
50100200300400500600700800900
Some text is below AA
Engineering

RTL is not a mirror

Flipping a layout for Arabic or Hebrew is the easy half. Nexera UI also decides what must stay left-to-right: codes, phone numbers, chart axes.

NEngineering· 5 min read

Setting dir="rtl" on a page and flipping it horizontally gets you most of the way to an Arabic or Hebrew interface. The rest is where the bugs live. A verification code typed into mirrored cells reads backwards, a chart with a reversed time axis tells the wrong story, and a back arrow that points forward sends people the wrong way. This post covers the rules Nexera UI follows for right-to-left layouts, and the places where it deliberately does not mirror.

Direction comes from the locale

Right-to-left support starts at the provider:

app.tsx
import { NexeraProvider } from "@nexera-ui/react";

<NexeraProvider locale="ar">{children}</NexeraProvider>;

locale goes to React Aria's I18nProvider, which formats numbers and dates and derives the text direction. The provider then puts dir="rtl" on <html>, or on its wrapper element when it is nested.

There is also a direction prop, and it is easy to misuse. Setting it only changes the dir attribute. React Aria derives keyboard and interaction direction from the locale, so direction="rtl" with an English locale gives you a mirrored layout whose arrow keys still behave left-to-right. In development, the provider logs a warning when direction and the locale disagree, and suggests passing an RTL locale such as ar.

A nested provider renders a wrapper with its own dir, and overlays opened inside it (popovers, menus, modals, toasts) render inside that wrapper. A right-to-left region of an otherwise left-to-right page keeps its direction in its menus too.

Logical properties everywhere

The first rule in our conventions for RTL is short: logical properties only. Components use ms-, me-, ps-, pe-, start- and end- instead of left and right, so spacing, padding and positioning follow the reading direction without a second stylesheet. A label's icon sits at the start of the line in both directions, and a close button sits at the end.

Most of the mirroring in the library comes from this rule alone. It costs nothing at runtime, and it means a component written in a left-to-right story is usually right in RTL before anyone looks at it.

Icons that point somewhere

Some icons carry direction: chevrons, arrows, back and forward, undo and redo. Those must flip. Others must not: a search glass, a bell, a check mark.

Nexera wraps the first kind in DirectionalIcon, which adds rtl:-scale-x-100 and hides the glyph from assistive technology. Breadcrumb separators, Pagination arrows, the back button in Drawer and TopAppBar, and the chevrons in tree and menu items all use it. You can use it in your own components too:

app.tsx
import { Button, DirectionalIcon } from "@nexera-ui/react";
import { LuArrowRight } from "react-icons/lu";

<Button
  trailingIcon={
    <DirectionalIcon>
      <LuArrowRight />
    </DirectionalIcon>
  }
>
  Continue
</Button>;

The Breadcrumb tests check that the separators sit between levels only and mirror in right-to-left, and an axe test checks the collapsed trail in right-to-left with its menu open.

What stays left-to-right

Here is where a mirror goes wrong. Arabic is written right to left, but numbers inside Arabic text are read left to right. So are codes, phone numbers, card numbers and source code. Our rule says it directly: numbers, IBANs, card numbers and code stay LTR.

In practice, about twenty components contain a left-to-right island marked with dir="ltr". Some examples:

  • OTPInput keeps its cells left-to-right. The first digit you type lands in the leftmost cell, and ArrowLeft moves to the previous digit. A test renders the field with locale: "ar", checks that the cells sit inside a dir="ltr" element, and presses ArrowLeft to confirm the caret moves back one cell.
  • InputPhone keeps the dial code and the number left-to-right, in the field and in the country list.
  • PaymentCard keeps the card number and the CVC left-to-right.
  • CodeBlock and InlineCode keep source code left-to-right.

The same idea applies to input. OTPInput accepts digits typed on Arabic-Indic and Persian keyboards, as well as Devanagari, Bengali and full-width digits, and stores them as ASCII. Someone who types ١٢٣٤ gets a valid code without switching keyboards.

Charts keep their axes

Charts are the clearest case for a partial mirror. A reversed time axis makes a rising line look like a falling one, so the library keeps time and value axes in their usual direction.

The plot area of every Nexera chart stays left-to-right: the shared ChartPlot group sets direction="ltr", and a test checks the attribute, including in the server-rendered HTML. Everything around the plot follows the page: the card, the header, the legend and the data table mirror. Digits on the axes come from Intl.NumberFormat with the chart's locale. HeatmapChart goes one step further: month and weekday names follow the locale while the grid of cells keeps its order.

Physical sides when you mean them

Sometimes you do mean a physical side. Drawer takes position="start" | "end" | "left" | "right". The default, end, opens from the right in left-to-right layouts and from the left in right-to-left ones. left and right stay on that side in both directions, for the cases where the side is tied to something on screen.

app.tsx
import { Button, Drawer } from "@nexera-ui/react";

<Drawer title="Filters" position="end" trigger={<Button variant="secondary">Filters</Button>}>
  <FilterForm />
</Drawer>;

How we test it

The unit tests have a renderUI(ui, { locale: "ar" }) helper, and more than a hundred test files use it for components where direction matters. Many of them press keys as well as check attributes, because a component can look right in RTL and still move focus the wrong way.

The browser run adds a third pass to every story. Each one is checked with axe in Light, Dark and RTL, so a right-to-left regression shows up in the same report as a contrast problem.

None of this replaces a review by someone who reads the language. Translations, the order of words in your labels and whether a screen makes sense to an Arabic reader are still yours to check. The Breadcrumb and OTP input pages are good places to start.

Keep reading

All posts →

One email when we publish.

New posts and releases, about twice a month. Or follow the RSS feed.