How to support right-to-left (RTL) languages in React Router v7
Mirror layouts for Arabic and Hebrew
Problem
Most design systems assume text flows from left to right. Navigation starts on the left, sidebars anchor to the left, and content reads from left to right. But Arabic and Hebrew read from right to left, and their layouts should mirror accordingly—what appears on the left for English should appear on the right for Arabic. Without this mirroring, the entire interface feels backward. Visual flow contradicts reading direction, creating a disorienting experience where users must fight against the layout to read naturally.
The challenge extends beyond simple text alignment. Margins, padding, borders, and positioning all need to adapt. A button with left margin in English should have right margin in Arabic. Icons that point right should point left. The entire spatial logic of the interface must flip to match the reading direction.
Solution
Set the dir attribute on the document's <html> element to specify text direction for the current language. Use ltr for left-to-right languages like English and rtl for right-to-left languages like Arabic and Hebrew. This single attribute causes browsers to automatically mirror many layout behaviors.
Design layouts using CSS logical properties like margin-inline-start instead of physical properties like margin-left, so spacing adapts automatically when text direction changes. Logical properties are direction-agnostic—they define spacing relative to the flow of text rather than fixed screen positions. When the document direction is RTL, margin-inline-start becomes margin-right, and the layout mirrors itself without additional code.
Steps
1. Detect the current locale's text direction
React Router 7 requires a root route at app/root.tsx that renders the HTML document. Create a helper function that maps locale codes to their text direction.
const locales = {
en: { dir: "ltr" },
ar: { dir: "rtl" },
he: { dir: "rtl" },
es: { dir: "ltr" },
};
function getTextDirection(locale: string): "ltr" | "rtl" {
return locales[locale as keyof typeof locales]?.dir || "ltr";
}
This function returns the appropriate direction for each supported locale, defaulting to left-to-right for unknown locales.
2. Set the dir attribute on the html element
In your root layout, retrieve the current locale and apply the corresponding direction to the document.
import { Links, Meta, Outlet, Scripts, ScrollRestoration } from "react-router";
export function Layout({ children }: { children: React.ReactNode }) {
return (
<html lang="en" dir="ltr">
<head>
<meta charSet="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<Meta />
<Links />
</head>
<body>
{children}
<ScrollRestoration />
<Scripts />
</body>
</html>
);
}
export default function Root() {
return <Outlet />;
}
Replace the hardcoded dir="ltr" with a dynamic value based on your locale detection mechanism. If your app stores the current locale in a loader or context, read it here and pass it to getTextDirection.
3. Use logical properties for spacing
Replace physical CSS properties with their logical equivalents in your components and stylesheets.
export default function Card({
title,
children,
}: {
title: string;
children: React.ReactNode;
}) {
return (
<div
style={{
paddingInlineStart: "1rem",
paddingInlineEnd: "1rem",
marginInlineStart: "auto",
borderInlineStart: "4px solid blue",
}}
>
<h2>{title}</h2>
{children}
</div>
);
}
When dir="rtl" is set, paddingInlineStart applies to the right side and paddingInlineEnd to the left, automatically mirroring the layout. The same component works correctly in both LTR and RTL contexts without conditional logic.
4. Apply logical properties to layout containers
Use logical properties for common layout patterns like navigation bars and content grids.
export default function Navigation() {
return (
<nav
style={{
display: "flex",
gap: "1rem",
paddingInline: "2rem",
borderBlockEnd: "1px solid #ccc",
}}
>
<a href="/" style={{ marginInlineEnd: "auto" }}>
Home
</a>
<a href="/about">About</a>
<a href="/contact">Contact</a>
</nav>
);
}
paddingInline sets padding on both inline sides, and marginInlineEnd: "auto" pushes content to the inline-start edge, which flips from left to right when direction changes. The navigation layout mirrors automatically for RTL languages.
5. Handle icons and directional graphics
For icons that represent direction or flow, conditionally flip them based on text direction.
function BackButton() {
const dir = document.documentElement.dir;
const iconStyle = {
transform: dir === "rtl" ? "scaleX(-1)" : "none",
marginInlineEnd: "0.5rem",
};
return (
<button>
<span style={iconStyle}>←</span>
Back
</button>
);
}
This flips the arrow icon horizontally in RTL mode while keeping the text and spacing direction-aware through logical properties. Not all icons need flipping—only those that indicate direction or movement.