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.

Step 2 of 3

Notifications

Choose where we email you, and how often.

Emails per day
3

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:

Changes saved.

Left to rightborder-s-4 ps-3

تم حفظ التغييرات.

Right to left, physicalborder-l-4 pl-3

تم حفظ التغييرات.

Right to left, logicalborder-s-4 ps-3

These are the classes patterns use in place of left and right:

Instead ofPatterns use
pl-* / pr-*ps-* / pe-*
ml-* / mr-*ms-* / me-*
left-* / right-*start-* / end-*
text-left / text-righttext-start / text-end
border-l / border-rborder-s / border-e
rounded-l / rounded-rrounded-s / rounded-e

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:

Left to rightno class
Right to leftno class
Rotatedrtl:rotate-180
Mirroredrtl:-scale-x-100

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:

dirUse it forWhat happens
"ltr"Email, URL, password, code, one-time codeAlways left to right
"auto"Names, search, messages: either languageFollows the first letter of the value
noneText that is always in the page's languageFollows the page
Emaildir="ltr"
City, always in Arabicno dir
Name, typed in Englishdir="auto"
Name, typed in Arabicdir="auto"

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:

Sara: 3 تعليقات

Without bdi{name}: 3 تعليقات

Sara: 3 تعليقات

With bdi<bdi>{name}</bdi>: 3 تعليقات

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:

Popup without dir<Popover.Positioner>
Popup with the region's dirdir={useDirection()}

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.