RTL
How Gaudi UI patterns support right-to-left languages such as Arabic and Hebrew, and how to set up your app for them.
Gaudi UI patterns work in right-to-left languages such as Arabic and Hebrew. Set the direction once on your app, and every pattern lays itself out to match.
How it works
The browser decides where the start and end are from the dir attribute of the element and its
ancestors. Every class in a pattern that refers to a side uses the start or end version, so the
whole layout follows dir:
These are the classes patterns use in place of left and right:
Flex and grid layouts already follow dir, so a row of items reverses with no extra classes. The
registry build rejects a pattern that uses any of the left or right classes, so every pattern
ships with RTL support. When you edit a pattern in your project, keep to the start and end
classes.
Setup
Set the document direction
Put dir and lang on <html>. dir sets the layout direction for the page. lang tells the
browser which language the text is in, which it uses to pick fonts and screen readers use to pick
a voice.
app/layout.tsx
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="ar" dir="rtl">
<body>
<div className="root">{children}</div>
</body>
</html>
);
}Add Base UI's DirectionProvider
dir covers the CSS. Base UI components also need to know the direction for their behavior, and
they read it from DirectionProvider. It
sets which arrow key moves forward in Tabs, Menu, Radio Group and Toolbar, which way a Slider
fills, and which side a submenu opens on.
app/layout.tsx
import { DirectionProvider } from '@base-ui/react/direction-provider';
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="ar" dir="rtl">
<body>
<DirectionProvider direction="rtl">
<div className="root">{children}</div>
</DirectionProvider>
</body>
</html>
);
}Gaudi UI has no direction component of its own. Patterns use Base UI's provider as it is.
Switching language
If your app supports several languages, derive the direction from the current one and pass the
same value to dir and direction, so the CSS and Base UI never disagree:
app/layout.tsx
import { DirectionProvider } from '@base-ui/react/direction-provider';
const rtlLanguages = new Set(['ar', 'he', 'fa', 'ur']);
export default function RootLayout({
lang,
children,
}: {
lang: string;
children: React.ReactNode;
}) {
const direction = rtlLanguages.has(lang) ? 'rtl' : 'ltr';
return (
<html lang={lang} dir={direction}>
<body>
<DirectionProvider direction={direction}>
<div className="root">{children}</div>
</DirectionProvider>
</body>
</html>
);
}Icons
Most icons look the same in both directions. Icons that point somewhere, such as arrows, chevrons
and carets, need to point the other way in a right-to-left layout. Patterns mirror them with
rtl:-scale-x-100:
<ArrowUpRightIcon aria-hidden="true" className="rtl:-scale-x-100" />Mirroring flips the icon horizontally. Rotating it with rtl:rotate-180 also works for a straight
arrow, but turns a diagonal one upside down:
Leave icons that don't point anywhere as they are: check marks, plus signs, search, download, spinners, and media controls such as play.
Text direction
Some text reads left to right in every language: email addresses, URLs, code and one-time codes.
Other text, such as a name, can be in either language. An input follows the page direction unless
you give it its own dir:
The same applies to values shown inside a sentence. A name or other user-provided value in a
right-to-left sentence can pull nearby numbers and punctuation into its own direction. Wrap the
value in <bdi> to keep it separate:
Read both from the right. Without <bdi>, the count comes first and the name second. With it, the
sentence reads name, count, then "comments", as written.
export function CommentCount({ name, count }: { name: string; count: number }) {
return (
<p>
<bdi>{name}</bdi>: {count} تعليقات
</p>
);
}Mixed directions
A page can contain a region in the other direction, such as an English support panel inside an
Arabic app. Set dir and lang on the region and nest another provider:
import { DirectionProvider } from '@base-ui/react/direction-provider';
export function SupportSection() {
return (
<DirectionProvider direction="ltr">
<section dir="ltr" lang="en">
<SupportPanel />
</section>
</DirectionProvider>
);
}Popups need one more step. Base UI renders them at the end of <body>, outside the region, so
they get their placement and keyboard behavior from the region but their layout from the page. To
match the region, read its direction with useDirection and set it on the popup:
import { useDirection } from '@base-ui/react/direction-provider';
import { Popover } from '@base-ui/react/popover';
export function HelpPopover() {
const direction = useDirection();
return (
<Popover.Root>
<Popover.Trigger>Help</Popover.Trigger>
<Popover.Portal>
<Popover.Positioner dir={direction}>
<Popover.Popup>…</Popover.Popup>
</Popover.Positioner>
</Popover.Portal>
</Popover.Root>
);
}Both buttons below sit in a right-to-left region on this left-to-right page. Open each one to compare:
You only need this for regions. When the direction is set on <html>, popups already match it.
Fonts
Inter, the theme's font, has no Arabic or Hebrew letters. Install a font for your language and add
it to --font-sans after Inter. The browser uses Inter for Latin text and falls back to the next
font for the rest. Fontsource splits each font by script, so the Arabic file only loads on pages
that have Arabic text.
styles/theme.css
@import '@fontsource-variable/noto-sans-arabic';
@theme inline {
--font-sans:
'Inter Variable', 'Noto Sans Arabic Variable', Inter, ui-sans-serif, system-ui, sans-serif;
}For Hebrew, use @fontsource-variable/noto-sans-hebrew and 'Noto Sans Hebrew Variable'.
Previews
Every pattern on this site can be viewed right to left. The direction switch in the bottom-right corner, next to the theme toggle, sets the direction of all pattern previews and is kept across pages. The docs text and code stay left to right.