Build layouts that work for Arabic, Hebrew, and other RTL scripts using CSS logical properties, the dir attribute, and correct font stacks for non-Latin writing systems.
If your application serves users who read Arabic, Hebrew, Persian, or Urdu, your typography must flip direction — and so must your layout. CSS logical properties replace direction-specific physical values with direction-aware logical ones, making RTL support achievable without duplicate CSS. This lesson covers everything from the HTML dir attribute to font stacks for Arabic and Hebrew scripts.
Physical CSS properties are anchored to the screen: left, right, top, bottom. Logical properties are anchored to text flow: inline-start (left in LTR, right in RTL), inline-end (right in LTR, left in RTL), block-start (top in horizontal writing), block-end (bottom in horizontal writing). For RTL support, replacing every physical property with its logical counterpart is the single most impactful change. The mapping is direct: margin-left → margin-inline-start, padding-right → padding-inline-end, border-left → border-inline-start, text-align: left → text-align: start, float: left → float: inline-start. Browser support for CSS logical properties is now excellent (Chrome 87+, Firefox 41+, Safari 15+). The only notable exception is the shorthand margin-inline: auto (centering) — this is universally supported and should be preferred over margin: 0 auto.
1/* Physical properties — break in RTL */
2.card-physical {
3 margin-left: 1rem; /* Always pushes from left */
4 padding-right: 1.5rem; /* Always adds space on right */
5 border-left: 3px solid blue; /* Always on left side */
6 text-align: left; /* Always aligns to left */
7 float: left; /* Always floats left */
8}
9
10/* Logical properties — correct in both LTR and RTL */
11.card-logical {
12 margin-inline-start: 1rem; /* Start edge: left in LTR, right in RTL */
13 padding-inline-end: 1.5rem; /* End edge: right in LTR, left in RTL */
14 border-inline-start: 3px solid blue; /* Flips with direction */
15 text-align: start; /* Aligns to reading start */
16 float: inline-start; /* Floats to reading start */
17}The dir attribute on the HTML element sets the base text direction for the entire document. dir='ltr' is the default for Latin scripts. dir='rtl' is required for Arabic, Hebrew, Persian, Urdu, and other RTL scripts. When dir='rtl' is set, the browser: renders text right-to-left, reverses the horizontal order of block-level elements, flips text-align: start to the right, and reverses margin/padding logical properties. The CSS direction property mirrors this but should only be used for inline styling or components — the HTML attribute is the correct document-level mechanism. For multilingual content where some paragraphs are LTR and some RTL, use the dir attribute on individual elements or the unicode-bidi CSS property.
1<!-- Arabic document: dir on html element -->
2<html lang="ar" dir="rtl">
3 <head>
4 <meta charset="UTF-8" />
5 </head>
6 <body>
7 <!-- All layout is now RTL by default -->
8 <p>مرحبا بالعالم</p> <!-- Arabic: right-to-left -->
9
10 <!-- Inline LTR content within RTL document -->
11 <p dir="ltr">English paragraph in an RTL document</p>
12
13 <!-- Bidirectional text: let Unicode algorithm handle it -->
14 <p>مرحبا <span dir="ltr">Hello</span> مرحبا</p>
15 </body>
16</html>1/* CSS direction — use for components, not documents */
2.rtl-component {
3 direction: rtl;
4 unicode-bidi: embed; /* Required when using CSS direction */
5}Non-Latin scripts require fonts that include those character sets. System fonts on major platforms include Arabic and Hebrew support. For custom Arabic web fonts, popular choices are Cairo, Tajawal, and Noto Sans Arabic (all available via Google Fonts). For Hebrew: Heebo, Rubik, and Frank Ruhl Libre. For CJK (Chinese, Japanese, Korean): Noto Sans SC/TC/JP/KR. Font stacks for multilingual projects should include script-specific fonts before generic fallbacks. The CSS unicode-range descriptor in @font-face tells the browser which ranges a font covers, so Latin text uses the Latin font and Arabic text uses the Arabic font — the browser handles the split automatically when both @font-face declarations are present.
1/* Arabic font stack */
2@font-face {
3 font-family: 'Cairo';
4 src: url('/fonts/cairo-var.woff2') format('woff2');
5 font-weight: 200 900;
6 font-style: normal;
7 font-display: swap;
8 /* Covers Arabic Unicode block */
9 unicode-range: U+0600-06FF, U+200C-200E, U+2010-2011,
10 U+204F, U+2E41, U+FB50-FDFF, U+FE80-FEFC;
11}
12
13/* Latin font (loaded separately for Latin characters) */
14@font-face {
15 font-family: 'Cairo';
16 src: url('/fonts/cairo-latin.woff2') format('woff2');
17 font-weight: 200 900;
18 unicode-range: U+0000-00FF, U+0131, U+0152-0153;
19}
20
21/* Application: font-family is the same — browser picks correct file */
22body {
23 font-family: 'Cairo', 'Segoe UI', Tahoma, Arial, sans-serif;
24}Arabic script is cursive — letters connect to each other and change shape based on position in a word (initial, medial, final, isolated forms). Never use letter-spacing on Arabic text — it breaks the cursive connections and makes the text unreadable. Arabic text typically requires a slightly larger font-size and more line-height than Latin text at the same nominal size — Arabic has a larger body and different vertical metrics. For mixed Arabic/Latin documents, the Arabic font-size is often 10–15% larger than the Latin font-size to appear visually equal. Hebrew is non-cursive (letters do not connect) but still RTL. Hebrew requires generous line-height (1.7–1.8) due to its vertical metrics and the presence of niqqud (vowel diacritics) that add height.
1/* RTL-specific typography adjustments */
2:lang(ar) {
3 font-family: 'Cairo', 'Noto Sans Arabic', Tahoma, Arial, sans-serif;
4 /* Arabic needs more size to appear equal to Latin at same px */
5 font-size: 1.1em; /* 10% larger than Latin ancestor */
6 line-height: 1.8; /* More generous for Arabic vertical metrics */
7 letter-spacing: 0; /* NEVER use letter-spacing on Arabic */
8 word-spacing: 0; /* Word spacing also disrupts Arabic */
9}
10
11:lang(he) {
12 font-family: 'Heebo', 'Rubik', Arial, sans-serif;
13 line-height: 1.75; /* Hebrew needs generous leading for diacritics */
14 letter-spacing: 0; /* No tracking — disrupts Hebrew as well */
15}
16
17/* Adjust heading sizes for Arabic */
18:lang(ar) h1 { font-weight: 700; } /* Arabic 700 renders differently */Test RTL layouts with real content — do not test with placeholder reversed text. Use actual Arabic or Hebrew sample paragraphs. Chrome DevTools can emulate locale and dir but cannot test font rendering for non-Latin scripts on a machine without those fonts installed. Key things to check: (1) Does the layout mirror correctly — navigation, sidebar, form fields, icons all flip? (2) Do logical properties work — padding, margin, and borders all on the correct side? (3) Does mixed bidirectional text (Arabic sentence with an English URL embedded) render correctly using the Unicode bidirectional algorithm? (4) Do punctuation marks appear on the correct side — Arabic question mark (؟) is at the end of the line, which is the left side. (5) Are numbers (Western Arabic numerals) still LTR within an RTL paragraph?
1/* Full logical property audit: replace all physical properties */
2
3/* Search your CSS for these and replace: */
4/* margin-left → margin-inline-start */
5/* margin-right → margin-inline-end */
6/* padding-left → padding-inline-start */
7/* padding-right → padding-inline-end */
8/* border-left → border-inline-start */
9/* border-right → border-inline-end */
10/* left: X → inset-inline-start: X */
11/* right: X → inset-inline-end: X */
12/* text-align: left → text-align: start */
13
14/* Quick RTL test: add dir=rtl to html and check if layout mirrors */
15/* Any visual breakage points to a physical property that needs fixing */A complete bilingual card component using logical properties that renders correctly in both LTR and RTL.
Next lesson covers performance-first font strategy — the complete decision framework for choosing between system fonts, variable fonts, and subsetting for any project type.
Your progress is saved in this browser, on this device only.