Right-to-left (RTL)
Yugen UI supports right-to-left languages (Arabic, Hebrew, Persian…) with one dir prop and a rule to use only logical utilities.
Enable RTL
One dir prop on YugenProvider: the wrapper (or <html> with applyToDocument) gets dir, and Radix's DirectionProvider passes the direction to every component.
// Whole app: sets dir="rtl" on <html> and passes the direction to Radix
<YugenProvider applyToDocument dir="rtl" theme="light">
<App />
</YugenProvider>
// Or a single region (for example, to preview a translation)
<YugenProvider dir="rtl" lang="ar" className="rounded-xlarge p-5">
<Breadcrumb items={[{ label: "الرئيسية" }, { label: "المستندات" }]} />
</YugenProvider><!-- Set it in the HTML so the page does not switch direction while it loads -->
<html lang="ar" dir="rtl" data-theme="light">- CSS: the
dirattribute reverses flex, grid, the default text-align and every logical utility (ms-*,ps-*,start-*…). - Behavior: Tabs, Menu (submenus open to the left), RadioGroup, Slider, ToggleGroup, ScrollArea, Toolbar, Tree, List, Carousel, TagPicker, InputOTP and Rating read the direction from
DirectionProviderto reverse arrow keys and drag direction. - Overlays: Popover, Tooltip, HoverCard, Dialog, Drawer, Menu and listbox put the provider's
diron their surface even though they render in a portal. The Toaster fromwithToasteruses the logical positionbottom-end, so it moves to the left corner in RTL.
Live example
Switch the text direction of the frame below, then Tab in and use the arrow keys on Tabs and Slider: ← / → swap roles in RTL.
مرحبا بك في يوغن
<YugenProvider dir="rtl" lang="ar" className="flex flex-col gap-4 rounded-2xlarge border border-stroke-2 p-6">
<Breadcrumb items={[{ label: "الرئيسية" }, { label: "المستندات" }, { label: "التقارير" }]} />
<Input aria-label="ابحث" placeholder="ابحث…" contentBefore={<Search20Regular />} rootClassName="w-64" />
<Switch label="الإشعارات" defaultChecked />
<Slider label="مستوى الصوت" showValue defaultValue={[30]} rootClassName="max-w-sm" />
<Pagination pageCount={10} defaultPage={3} />
<Button appearance="primary" icon={<ArrowRight20Regular className="rtl:-scale-x-100" />} iconPosition="after">
التالي
</Button>
</YugenProvider>Logical utilities
A rule for component authors: no physical directions in layout. Logical utilities follow dir on their own, without an rtl: variant.
| Don't use (physical) | Use (logical) | CSS |
|---|---|---|
ml-* / mr-* | ms-* / me-* | margin-inline-start / end |
pl-* / pr-* | ps-* / pe-* | padding-inline-start / end |
left-* / right-* | start-* / end-* | inset-inline-start / end |
border-l / border-r | border-s / border-e | border-inline-start / end |
rounded-l-* / rounded-r-* | rounded-s-* / rounded-e-* | border-start-start-radius… |
rounded-tl-* / rounded-tr-* | rounded-ss-* / rounded-se-* | border-start-start / start-end-radius |
text-left / text-right | text-start / text-end | text-align: start / end |
space-x-* with manual margins | gap-* | gap has no direction |
Valid exceptions: centering with left-1/2 -translate-x-1/2 (it is symmetric), and content that is always LTR, such as source code, phone numbers and URLs. Wrap it in <bdi> or dir="ltr" so the bidi algorithm does not reorder its characters.
اتصل بنا: +84 912 345 678
الرمز: npm run dev
Flipping icons & transforms
Icons that show a direction (forward, back, send, indent) flip in RTL. Icons that show an object or a concept stay as they are.
dir="ltr"
- Forward / back arrowflip
- Navigation chevronflip
- Sendflip
- Searchkeep
- Checkmarkkeep
- Play mediakeep
dir="rtl"
- Forward / back arrowflip
- Navigation chevronflip
- Sendflip
- Searchkeep
- Checkmarkkeep
- Play mediakeep
{/* Directional icons: flip with the reading direction */}
<ChevronRight16Regular className="rtl:-scale-x-100" />
<Button icon={<ArrowLeft20Regular className="rtl:-scale-x-100" />}>Back</Button>
{/* Physical transforms: write both branches */}
<span className="translate-x-5.5 rtl:-translate-x-5.5" />
{/* Open / close chevron: flip first, then rotate the other way in RTL */}
<ChevronRight20Regular className="rtl:-scale-x-100 group-data-[state=open]:rotate-90 rtl:group-data-[state=open]:-rotate-90" />Tailwind's rtl: variant matches elements inside any ancestor with dir="rtl", so it also works with nested YugenProviders. Use it for every physical value that has no logical counterpart: translate-x, rotate, scale-x, angled gradients, background-position.
Arrow keys
Radix components and the Yugen components with horizontal navigation already swap ← / →. In your own components, read the direction with useDirection().
import { useDirection } from "@radix-ui/react-direction";
/** ← / → follow the reading direction: in RTL, ArrowLeft means "forward". */
export function useInlineArrowKeys(onPrev: () => void, onNext: () => void) {
const dir = useDirection(); // "ltr" | "rtl" — from YugenProvider
return (event: React.KeyboardEvent) => {
const nextKey = dir === "rtl" ? "ArrowLeft" : "ArrowRight";
const prevKey = dir === "rtl" ? "ArrowRight" : "ArrowLeft";
if (event.key === nextKey) {
event.preventDefault();
onNext();
} else if (event.key === prevKey) {
event.preventDefault();
onPrev();
}
};
}useYugen().dir gives the same value, but useDirection() from @radix-ui/react-direction also works outside Yugen (under any DirectionProvider). ↑ / ↓ and Home / End do not depend on the text direction.
Testing
Broken RTL usually shows up in spacing, icons and keys, so check each of them.
- Switch LTR / RTL in the top bar of the docs site and go through your component's page.
- Compare spacing on both sides: icons in buttons, input padding and the selection bar of Nav / Tabs must switch sides.
- Use the keyboard: ← / → on Tabs, Slider, Toolbar, submenus and Carousel must follow the reading direction.
- Open overlays (Menu, Popover, Tooltip, Drawer start / end) and toasts. They render in portals.
- Search the source for leftover physical utilities:
# List physical classes to review (ignore left-1/2 centering matches)
rg -n "\b(ml|mr|pl|pr|left|right|border-l|border-r|rounded-l|rounded-r|text-left|text-right)-" src/components- Use ms/me/ps/pe/start/end/border-s/border-e/rounded-s/rounded-e/text-start.
- Flip directional icons with rtl:-scale-x-100.
- Set lang along with dir so screen readers use the right language.
- Flipping logos, checkmarks, magnifying glasses or media icons.
- Writing dir === 'rtl' branches in JS for what logical CSS already handles.
- Assuming ArrowRight always means “next”.