Build right-to-left interfaces with Proper UI. Arabic, Hebrew, Persian, and other RTL languages are supported through CSS logical properties, not a separate theme.
Right-to-left support
Proper UI supports right-to-left languages — Arabic, Hebrew, Persian, Urdu — without a separate theme or component fork. Direction-aware behavior (arrow-key navigation, popover placement, date-picker field order) comes from React Aria and is complete; the styling layer gets there with CSS logical properties, and is mid-migration.
Status. React Aria's direction handling is complete. The style layer is not: across
packages/ui/src/components, roughly 281 logical spacing utilities (ms-*,me-*,ps-*,pe-*,start-*,end-*) are in place against about 365 remaining physical ones (ml-*,mr-*,pl-*,pr-*,left-*,right-*), and 34text-start/text-endagainst 36text-left/text-right. Coverage is strongest in base components and weakest in marketing sections and page examples. If a component doesn't mirror correctly, that's a bug — open an issue.
Try it live — this toggle flips only the panel below between dir="ltr" and dir="rtl", nothing else on the page moves, because every class inside it is logical rather than physical:
Sana Al-Rashid
ps-3 avatar gap · text-start alignment
This card only uses
ms-*,
ps-*,
and
text-start
. Flip the switch above — nothing here needed a rewrite.
How it works
Logical properties describe layout relative to text direction (start / end) instead of a physical side (left / right). In a dir="ltr" document margin-inline-start resolves to the left; in dir="rtl" it resolves to the right — the same class, no conditional styling. Proper UI's components are migrating to these Tailwind utilities everywhere spacing, alignment, or borders would otherwise assume a direction — see the status note above for how far that migration has gotten:
| Physical | Logical | Resolves to (LTR) | Resolves to (RTL) |
|---|---|---|---|
ml-4 | ms-4 | margin-left: 1rem | margin-right: 1rem |
mr-4 | me-4 | margin-right: 1rem | margin-left: 1rem |
pl-4 | ps-4 | padding-left: 1rem | padding-right: 1rem |
pr-4 | pe-4 | padding-right: 1rem | padding-left: 1rem |
left-0 | start-0 | left: 0 | right: 0 |
right-0 | end-0 | right: 0 | left: 0 |
text-left | text-start | left-aligned | right-aligned |
Symmetrical utilities — px-4, mx-4, p-4, m-4, gap-4 — apply to both sides already and need no logical equivalent.
Setting up RTL
Add dir="rtl" to the <html> element (or any container) and wrap your app in React Aria's I18nProvider so every interactive component — dialogs, menus, date pickers, sliders — picks up the direction automatically:
import { I18nProvider } from "react-aria-components";
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="ar" dir="rtl">
<body>
<I18nProvider locale="ar-AE">{children}</I18nProvider>
</body>
</html>
);
}
I18nProvider's locale drives number, date, and collation formatting; the dir attribute drives layout. Set them independently — an app can be RTL-laid-out while formatting numbers for a specific locale, or vice versa.
Migrating an LTR codebase
There's no CLI codemod for this yet — converting physical-direction classes to their logical equivalents is a manual, project-wide find-and-replace: ml-* → ms-*, mr-* → me-*, pl-* → ps-*, pr-* → pe-*, left-* → start-*, right-* → end-*, text-left → text-start, text-right → text-end, rounded-l-* → rounded-s-*, rounded-r-* → rounded-e-*, border-l → border-s, border-r → border-e.
It's safe to do incrementally: ms-4 compiles to exactly margin-left: 1rem under dir="ltr", so migrating a file at a time changes nothing until you actually flip a container to dir="rtl".
What has no logical equivalent
Some CSS has no logical equivalent — transform: translateX(), transform: scaleX(), keyframe-based slide animations. For those, add an rtl: variant alongside the original class rather than replacing it:
- <div className="slide-in-from-right-4" />
+ <div className="slide-in-from-right-4 rtl:slide-in-from-left-4" />
Directional icons
Icons that visually encode a direction — arrows, chevrons pointing to "next", the icon in a "back" button — need to flip in RTL. Icons that don't imply direction — a checkmark, a bell, a trash can — must not.
Setup
Proper UI marks direction-sensitive icons in @properui/icons with a naming convention (ArrowRight, ChevronRight, ArrowNarrowRight, and their Left counterparts) so you know which ones to flip. Apply the flip with the rtl: variant directly on the icon:
import { ArrowRight } from "@properui/icons";
<ArrowRight className="size-5 rtl:-scale-x-100" aria-hidden="true" />;
Usage
Because Button's iconTrailing prop accepts a component reference, wrap the flip once in a small helper and reuse it everywhere a directional icon appears as a button icon:
import { ArrowRight } from "@properui/icons";
import { cx } from "@/utils/cx";
const DirectionalArrow = (props: React.ComponentProps<typeof ArrowRight>) => <ArrowRight {...props} className={cx("rtl:-scale-x-100", props.className)} />;
<Button iconTrailing={DirectionalArrow}>Continue</Button>;
Using a different icon library
If you swap in your own icon set, apply the same rule: mirror any icon whose meaning depends on direction (arrows, chevrons that mean "forward"/"back", the icon in a breadcrumb separator) with rtl:-scale-x-100, and leave everything else untouched.
When NOT to flip
Don't mirror icons whose meaning is independent of reading direction — a play button, a search glyph, a checkmark, a trash icon, brand logos, flags, or avatars. Flipping these makes them look wrong or, for a flag, factually incorrect. Text-containing icons (icons with embedded letters, like a "PDF" file badge) should also never be mirrored.
Testing
Storybook currently only ships a light/dark theme toggle (withThemeByClassName in .storybook/preview.tsx) — there's no direction toolbar item yet, so the fastest way to check RTL while building is to toggle dir on <html> directly in devtools.
Pay particular attention to sliders, breadcrumbs, carousels, date pickers, and drawers, where direction affects both layout and gesture direction. The project's accessibility tests (pnpm test) also catch the structural problems that tend to accompany a mis-mirrored layout.