Arabic (RTL)
Read when: the app runs in Arabic, something sits on the wrong side, or you are about to type
left, right, ml- or pr-.
Owns: turning RTL on, where Reka reads direction from, what you write, dir="ltr" on values,
and the registry's RTL gaps with their fixes.
TL;DR
- Generate the registry with RTL on (
"rtl": true), set<html lang dir>, and wrap every layout in Reka'sConfigProvider :dir. - Write logical classes only;
rtl:variants for what has no logical form. dir="ltr"goes on an element that shows a Latin value, not on a plain input.- RTL mode cannot rewrite transforms and offsets. The fixes live unlayered in the stylesheet.
Turning it on
- The registry in RTL mode (checked 2026-11-03 against shadcn-vue.com/docs/rtl):
- new project:
npx shadcn-vue@latest init --rtl(orcreate --rtl); - existing project:
npx shadcn-vue@latest migrate rtlrewrites every file in theuifolder and sets"rtl": true. A path or glob limits it:migrate rtl "resources/js/components/ui/**". Readgit diffafter. Withrtlon,addwrites logical classes (left-*→start-*), flips the supported icons withrtl:rotate-180, and makes slide animations logical (slide-in-from-right→slide-in-from-end).
- new project:
<html lang dir>set by the app, and kept in sync when the language changes without a reload (an Inertia visit, a Nuxt route change). Why: adirwritten once on first load outlives a switch of language.<ConfigProvider :dir :locale>fromreka-uiat the root of every layout. Reka reads direction from it, not from CSS. Without it every popper is placed and every arrow key resolved as if the page were LTR: the dropdown opens off-screen and nothing in the CSS explains why.
<script setup lang="ts">
import { ConfigProvider } from 'reka-ui';
defineProps<{ dir: 'ltr' | 'rtl'; locale: string }>();
</script>
<template>
<ConfigProvider :dir="dir" :locale="locale">
<slot />
</ConfigProvider>
</template>
- Portalled content that slides in (
PopoverContent,TooltipContent) may need:dirpassed directly, because the logical slide utilities intw-animate-cssread direction from the element itself (per the docs, checked 2026-11-03).
What you write
- Logical classes only:
ms-/me-,ps-/pe-,start-/end-,text-start/text-end,border-s/border-e,rounded-s-*. Neverml-,pr-,left-,text-left. - A glyph that points:
rtl:rotate-180when it is symmetrical (chevrons, plain arrows),rtl:-scale-x-100when it is not (an arrow entering a door). Clocks, checkmarks and logos never flip. A typed→cannot flip at all, so use an icon. - Prefer
rtl:/ltr:variants to branching on the direction in script.
dir="ltr" is for display, not for typing
- A Latin value shown inside Arabic text (URL, host, email, code, key) sits in an element with
dir="ltr", so bidi does not reorder it around dots and slashes. - Never on a plain input. It moves the caret to the far side and puts an Arabic placeholder on
the wrong edge. An input that holds Latin takes
dir="auto"only once it has a value::dir="value ? 'auto' : undefined". Why:dir="auto"reads the value, and an empty box falls back to LTR. - An
InputGroupwhose affix is part of the value (a.example.comsuffix, a+1dial code, a prefix such asinv_) is LTR as a whole. One whoseInputGroupAddonholds only an icon stays in the page direction.
Registry gaps and the fixes
RTL mode rewrites properties that have logical twins. A transform, a shadow offset or a background position has none, so the component looks right in English and broken in Arabic. The fixes are one unlayered block at the foot of the stylesheet, already in ../assets/app.css. Unlayered because Tailwind's utilities are layered and would otherwise win.
Found with shadcn-vue 2.8.x (checked 2026-11-03); re-check after upgrading:
SheetContentstays physical. It positions onleft-0/right-0whileSidebartreatssideas start/end, so in Arabic the mobile sidebar sat on the wrong edge and slid in from the wrong one. The fix flips the edge and the enter/exit translate variables. It keys on twodata-slotvalues: the sidebar's mobile branch overwrites the sheet's slot name with"sidebar", so a selector onsheet-contentalone matches nothing.AlertDialogHeaderaligns with a physicalsm:text-left. The fix restates it astext-align: startinside the same breakpoint, leaving the small-screen centring alone.Switch's thumb travels ontranslate. In Arabic the checked state pushed the thumb out of the track. The fix mirrors the checked travel only; unchecked is0both ways.- Hardcoded English inside a component. Override at the call site (a slot,
aria-label).
The docs list Calendar, Pagination and Sidebar as not migrated automatically; check them in Arabic when you add them. Still physical in that build, fix them the moment you use one:
| Component | What is physical |
|---|---|
ToggleGroupItem |
first:rounded-l-* / last:rounded-r-*, border-l-0, inline paddings |
SidebarRail |
group-data-[side=right]:left-0, group-data-[side=left]:-right-4 |
TabsTrigger |
group-data-vertical/tabs:after:-right-1 (vertical tabs only) |
Checking it
Switch the language and look at the same screen. What breaks first: poppers (wrong side), drawers (wrong edge), icons that imply direction, and anything that animates or offsets. When adding a component that animates or offsets, toggle it once in Arabic before believing it.