Internationalizing a React application is not just a matter of translating strings. For Arabic, Hebrew, Persian, Urdu, and other languages written with right-to-left scripts, the interface itself must respect directionality: text flow, alignment, icons, spacing, table order, form behavior, and mixed-language content all need attention.
The W3C Internationalization guidance is clear on the foundation: use HTML directionality to establish the base direction. The dir attribute is the semantic mechanism browsers use to apply the Unicode Bidirectional Algorithm correctly, and W3C recommends adding dir="rtl" to the html element when the overall document direction is right-to-left.
Start with document direction
A common React anti-pattern is to scatter conditional class names across components:
<div className={locale === "ar" ? "text-right flex-row-reverse" : "text-left"}>
...
</div>
This is brittle and repetitive. Instead, set dir and lang at the document or app shell level:
const rtlLocales = new Set(["ar", "he", "fa", "ur"]);
export function AppShell({ locale, children }) {
const dir = rtlLocales.has(locale) ? "rtl" : "ltr";
return (
<html lang={locale} dir={dir}>
<body>{children}</body>
</html>
);
}
In client-rendered apps where you cannot render <html> directly, update it from your root layout:
import { useEffect } from "react";
const rtlLocales = new Set(["ar", "he", "fa", "ur"]);
export function DirectionProvider({ locale, children }) {
useEffect(() => {
document.documentElement.lang = locale;
document.documentElement.dir = rtlLocales.has(locale) ? "rtl" : "ltr";
}, [locale]);
return children;
}
Setting direction at the html level influences paragraph alignment, punctuation placement, table column progression, form-field behavior, overflow direction, and CSS mirroring when logical properties are used.
Replace left/right CSS with logical properties
Refactoring for RTL should not mean duplicating every stylesheet. The better approach is to remove physical-direction assumptions.
Instead of this:
.card {
margin-left: 1rem;
padding-right: 1.5rem;
border-left: 4px solid currentColor;
text-align: left;
}
Use logical properties:
.card {
margin-inline-start: 1rem;
padding-inline-end: 1.5rem;
border-inline-start: 4px solid currentColor;
text-align: start;
}
In LTR, inline-start maps to left. In RTL, it maps to right. That means the same component works in both directions.
Useful replacements:
/* Before */
margin-left: 16px;
margin-right: 8px;
padding-left: 12px;
border-right: 1px solid #ccc;
left: 0;
right: auto;
text-align: left;
/* After */
margin-inline-start: 16px;
margin-inline-end: 8px;
padding-inline-start: 12px;
border-inline-end: 1px solid #ccc;
inset-inline-start: 0;
inset-inline-end: auto;
text-align: start;
For React components using utility classes, create direction-safe abstractions:
function Card({ children }) {
return (
<section className="rounded-lg border p-4 text-start">
{children}
</section>
);
}
Prefer utilities such as text-start, text-end, ms-*, me-*, ps-*, and pe-* when your CSS framework supports them.
Refactor layout intent, not just visual direction
Not everything should mirror.
Navigation, chat bubbles, disclosure arrows, pagination, timelines, and media controls often have different rules. Some reflect text direction; others represent time, physical movement, or brand identity.
Ask this for every left/right decision:
Is this position relative to reading direction, or is it physically fixed?
Use dir="auto" for user-generated content
User-generated content is often multilingual. A Hebrew comment may appear in an English UI. An English product name may appear inside an Arabic page. You often do not know the direction ahead of time.
Use dir="auto" on content containers whose text comes from users, APIs, search results, product catalogs, or databases:
function Comment({ author, text }) {
return (
<article className="comment">
<strong dir="auto">{author}</strong>
<p dir="auto">{text}</p>
</article>
);
}
W3C recommends dir="auto" for forms and inserted text when the direction of runtime content is unknown. The browser determines direction from the first strong directional character.
Isolate inline bidirectional text
When you know the direction of an inline phrase, wrap it tightly and set dir:
<p>
The title is{" "}
<cite dir="rtl">
مدخل إلى <span dir="ltr">C++</span>
</cite>{" "}
in Arabic.
</p>
A small React helper can make this habit consistent:
export function BidiText({ as: Component = "span", children, dir = "auto" }) {
return <Component dir={dir}>{children}</Component>;
}
Usage:
<BidiText as="cite">{bookTitle}</BidiText>
<BidiText>{fileName}</BidiText>
<BidiText>{userDisplayName}</BidiText>
Fix forms deliberately
Use dir="auto" for fields that may contain either LTR or RTL content:
<label>
{t("bookTitle")}
<input name="title" dir="auto" />
</label>
For multi-paragraph text:
<textarea name="comment" dir="auto" />
For textarea, dir="auto" can assign direction paragraph by paragraph.
If your server needs to preserve the direction chosen or detected in a form field, HTML also supports dirname:
<input name="comment" dir="auto" dirname="comment.dir" />
That allows the submitted form data to include the computed direction.
Style by language when typography requires it
Some scripts need different fonts, line heights, or emphasis behavior. Do not solve this with locale-specific component forks. Use language-aware CSS.
:lang(ar) {
font-family: "Noto Naskh Arabic", serif;
line-height: 1.8;
}
:lang(he) {
font-family: "Noto Sans Hebrew", sans-serif;
}
W3C recommends the CSS :lang() selector for styling content by language, especially because it recognizes inherited language values rather than requiring every nested element to repeat a lang attribute.
Conclusion
That approach follows the web platform, and it produces React components that are easier to maintain, easier to localize, and much more reliable for the people who use RTL languages every day.
Top comments (0)