Yugen UI

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 dir attribute 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 DirectionProvider to reverse arrow keys and drag direction.
  • Overlays: Popover, Tooltip, HoverCard, Dialog, Drawer, Menu and listbox put the provider's dir on their surface even though they render in a portal. The Toaster from withToaster uses the logical position bottom-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.

مرحبا بك في يوغن

مستوى الصوت30
إعدادات الحساب العامة.
<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-rborder-s / border-eborder-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-righttext-start / text-endtext-align: start / end
space-x-* with manual marginsgap-*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.

  1. Switch LTR / RTL in the top bar of the docs site and go through your component's page.
  2. Compare spacing on both sides: icons in buttons, input padding and the selection bar of Nav / Tabs must switch sides.
  3. Use the keyboard: ← / → on Tabs, Slider, Toolbar, submenus and Carousel must follow the reading direction.
  4. Open overlays (Menu, Popover, Tooltip, Drawer start / end) and toasts. They render in portals.
  5. 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
Do
  • 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.
Don't
  • 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”.