Components
A provider component that sets the text direction for your application.
Playground
useDirection() → "rtl"
The dir attribute mirrors the layout; the provider is what Radix primitives — the Select here — read. Turn the provider off and the popup keeps aligning as if the app were LTR.
DirectionProvider renders no markup — it publishes "ltr" or "rtl" on a React context that every Radix primitive reads. Mount it once, high in the tree, alongside the dir attribute on <html>: the attribute drives CSS, the provider drives behavior.import { DirectionProvider } from "@workspace/ui/components/direction"
<html dir="rtl">
<body>
<DirectionProvider dir="rtl">
{/* Your app content */}
</DirectionProvider>
</body>
</html>dir="rtl" wrapper flips the layout because the browser resolves logical properties from the DOM — but Radix never reads the attribute, so without the provider its primitives still behave as LTR: popups align from the wrong edge, and arrow keys move against the reading order. Both panels below carry the attribute; only the second one is wrapped in the provider. Open each Select to see the difference.dir="rtl"
ltrdir + provider
rtl{/* layout mirrors, Radix context stays "ltr" */}
<div dir="rtl">
<Select>{/* … */}</Select>
</div>
{/* layout mirrors and Radix context becomes "rtl" */}
<DirectionProvider dir="rtl">
<div dir="rtl">
<Select>{/* … */}</Select>
</div>
</DirectionProvider>rtl: true in components.json) — components use logical utilities rather than physical ones: ms-* / me-* for margins, ps-* / pe-* for padding, border-s / border-e for edges. They mirror on their own. Directional glyphs do not — flip those explicitly with the rtl: variant.LTR
ms-autoborder-s-2 ps-3
RTL
ms-autoborder-s-2 ps-3
<div className="flex items-center"> <p className="text-sm font-medium">Label</p> <Badge variant="outline" className="ms-auto font-mono">ms-auto</Badge> </div> <p className="border-s-2 ps-3 text-sm text-muted-foreground">border-s-2 ps-3</p> <Button variant="outline" size="sm"> Next <CaretRightIcon className="rtl:rotate-180" /> </Button>
DirectionProvider dir="ltr" with a matching dir attribute, while its label keeps the surrounding direction.<DirectionProvider dir="rtl">
<div dir="rtl">
<Label htmlFor="name">الاسم الكامل</Label>
<Input id="name" placeholder="أدخل اسمك" />
<DirectionProvider dir="ltr">
<div dir="ltr">
<Label htmlFor="account" dir="rtl">معرّف الحساب</Label>
<Input id="account" className="font-mono" defaultValue="acct_4821-KX90" />
</div>
</DirectionProvider>
</div>
</DirectionProvider>useDirection(). It returns the nearest provider value, falling back to "ltr" when there is none — useful when the decision is not expressible in CSS, such as picking a caret or swapping a copy string. The first button below sits outside any provider, the second inside an RTL one.import { useDirection } from "@workspace/ui/components/direction"
function NextButton() {
const direction = useDirection()
return (
<Button variant="outline">
{direction === "rtl" ? "التالي" : "Next"}
<CaretRightIcon className={direction === "rtl" ? "rotate-180" : undefined} />
</Button>
)
}dir on <html> together with lang, so screen readers pick the right voice and bidirectional text resolves correctly; the provider then keeps keyboard behavior aligned with what is on screen. Arrow keys in Tabs, Select, Menubar and the other roving-focus primitives follow the reading order only when the context says "rtl", so a page that mirrors visually but not in context is navigable backwards.// app/layout.tsx — lang and dir belong on the document element
<html lang="ar" dir="rtl">
<body>
<DirectionProvider dir="rtl">{children}</DirectionProvider>
</body>
</html>Direction.DirectionProvider. It renders no element and consumes no design token — the tokens listed below come from the components used in the examples on this page. The upstream docs show a direction prop; it exists as an alias and wins when both are set, but dir is the one the type requires, so pass dir.| Export | Signature | Default |
|---|---|---|
| DirectionProvider | dir: "ltr" | "rtl" | required |
| DirectionProvider | direction?: "ltr" | "rtl" | — |
| DirectionProvider | children?: React.ReactNode | — |
| useDirection | (localDir?: "ltr" | "rtl") => "ltr" | "rtl" | "ltr" |
Tokens used
8 design tokens consumed by the Direction examples.
--border
Border
--card
Card surface
--input
Input border
--ring
Focus ring
--primary
Primary
--muted-foreground
Muted foreground
--radius
Radius
--spacing
Spacing unit (4px)