Docs
Right-to-left (RTL)
Set the page direction and every component, block and template mirrors: layout, icons, keyboard arrows, sliders, menus, sheets. There is a toggle on every preview to see it.
Turn it on
Set dir and lang on <html>, and wrap the app in DirectionProvider so the Radix-based components (tabs, sliders, menus, radio groups) know the direction too:
import { DirectionProvider } from "@/lib/ballmac/direction"
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="ar" dir="rtl">
<body>
<DirectionProvider dir="rtl">{children}</DirectionProvider>
</body>
</html>
)
}npx shadcn@latest add @ballmac/directionItems that need the direction in code (Tabs, Slider, Calendar, Carousel, Sidebar and others) add it for you. They also read <html dir> on their own, so an app that sets only the attribute still works; the provider adds the Radix side. A region can have its own direction with <Dir dir="ltr">.
What mirrors
- Layout. Spacing, borders, positions and alignment use CSS logical properties (
ms-,me-,ps-,pe-,start-,end-,text-start,border-s,rounded-e), which followdirwith no JavaScript. - Icons with a direction. Arrows, chevrons and back and forward icons carry
rtl:rotate-180orrtl:-scale-x-100. Disclosure chevrons still point down when open. - Keyboard. In RTL, ArrowLeft goes to the next tab, raises a slider, expands a tree node, moves a card to the next column and pages a carousel forward. Radix handles its primitives; the custom handlers read
useDirection. - Sides.
SheetandSidebartakeside="start"or"end"(the new default), which flip."left"and"right"stay fixed edges. - Dates and numbers use the locale (see i18n), including Arabic-Indic digits when you ask for them.
What stays left-to-right, on purpose
- Code and data. Code blocks, terminals, logs, JSON, diffs,
.envrows, API keys and keyboard shortcuts aredir="ltr"inside, as in every editor. The surrounding chrome still mirrors. - Charts keep their axes: time runs left to right, as Recharts draws it. Legends and text around them mirror.
- Motion with a named direction. Props such as
direction="left"on marquees and blur-fade describe a physical direction and are not flipped. - Hardware frames and two decorative effects keep a fixed layout; the list is below. What you put inside a device frame follows the page direction. Mac windows, the menu bar and the Finder mirror, as Safari does in Arabic; the red, yellow and green lights keep their order.
Fixed-layout items
- Light Rays: Rays fan out from a point centred with left-1/2 and a negative margin; both are physical and symmetric, so the picture is the same in either direction.
- Sparkles Text: Sparkles are placed by random percentages from the left edge and centred with a negative margin; decoration with no reading direction.
- Android Frame: Hardware buttons and bezels sit on fixed physical edges of the device, so the frame keeps its shape; the screen content you place inside follows the page direction.
- Laptop Frame: Hardware buttons and bezels sit on fixed physical edges of the device, so the frame keeps its shape; the screen content you place inside follows the page direction.
- Phone Frame: Hardware buttons and bezels sit on fixed physical edges of the device, so the frame keeps its shape; the screen content you place inside follows the page direction.
- Tablet Frame: Hardware buttons and bezels sit on fixed physical edges of the device, so the frame keeps its shape; the screen content you place inside follows the page direction.
- Watch Frame: Hardware buttons and bezels sit on fixed physical edges of the device, so the frame keeps its shape; the screen content you place inside follows the page direction.
Writing RTL-safe code
The repository checks this on every build (pnpm check runs scripts/rtl.ts):
- Physical classes are rejected:
ml-mr-pl-pr-becomems-me-ps-pe-;left-andright-becomestart-andend-;text-leftbecomestext-start;border-lbecomesborder-s;rounded-lbecomesrounded-s. Centering withleft-1/2 -translate-x-1/2is symmetric and allowed. - Directional icons from lucide-react must have an
rtl:class.npx tsx scripts/rtl.ts --fixrewrites the safe cases. - Anything that is physical on purpose carries a comment containing
rtl-fixedand a reason, or is listed inscripts/rtl-exceptions.json. - A
translate-xnudge needs its mirror:group-hover:translate-x-1 rtl:group-hover:-translate-x-1.
Testing
Every component and block preview has a right-to-left toggle in its toolbar. Any preview opens right-to-left with /preview/<name>?dir=rtl. pnpm rtl:sweep loads every preview in both directions and reports any whose layout is not the mirror image of the other, or that overflows only in RTL.
Known limits
- Templates mirror, but their sample copy is English and written into the files, so translate it as you would any content.
- Recharts draws SVG in its own coordinates: axes do not mirror. Pass
reversedto an axis yourself if your design needs it. - The marquee and blur-fade
directionprops are physical. Choose the value you want for your reading direction.