Font Converter

Right-to-Left (RTL) Font Guide

Complete guide to Arabic, Hebrew, Persian, and Urdu web font implementation. Learn CSS direction properties, bidirectional text handling, Arabic contextual shaping via OpenType, and how to subset RTL fonts to under 100KB for fast loading.

TL;DR - Key Takeaways

  • • Set dir="rtl" on the HTML element and use CSS logical properties (margin-inline-start) instead of physical ones
  • • Arabic requires contextual shaping OpenType features (calt, init, medi, fina, isol) for proper letter joining
  • • Mixed LTR/RTL (bidi) text needs unicode-bidi: isolate on embedded directional spans
  • • Subset Arabic to Unicode block U+0600–U+06FF for 50–100KB WOFF2 files

Share this page to:

Right-to-left writing systems represent some of the world's most widely spoken languages. Arabic alone has over 400 million native speakers across 22 countries, while Hebrew, Persian (Farsi), and Urdu together add hundreds of millions more. Building web typography that correctly supports RTL scripts requires understanding a distinct set of CSS properties, OpenType font features, and Unicode text algorithms that are entirely separate from Latin typesetting.

Unlike Latin script, Arabic and Hebrew are cursive scripts where letterforms change shape depending on their position within a word. An Arabic letter can have up to four forms: isolated, initial, medial, and final. The rendering engine must apply a sequence of OpenType substitution lookups to produce the correct connected glyphs from the underlying Unicode code points. Without a properly authored font and correct OpenType feature activation, Arabic text renders as a disconnected sequence of isolated characters rather than flowing, legible script.

Hebrew is somewhat simpler in that letters do not join, but it carries its own complexities: vowel marks (niqqud) appear as diacritics above and below consonants, cantillation marks are used in liturgical texts, and right-to-left directionality must coexist correctly with embedded LTR content such as numbers, URLs, and Latin words.

This guide covers everything you need to implement RTL typography correctly on the web: CSS properties for layout direction, the Unicode Bidirectional Algorithm for mixed-direction content, font selection for Arabic and Hebrew scripts, OpenType feature requirements, subsetting strategies, and testing approaches that catch the most common RTL implementation mistakes.

RTL Writing Systems Overview

The four major RTL writing systems each have distinct typographic requirements. Understanding their unique characteristics informs font selection, subsetting decisions, and rendering configurations.

Arabic Script

The Arabic Unicode block (U+0600–U+06FF) contains 256 code points covering the Arabic script used for Arabic, Pashto, Sindhi, Uyghur, and more. The Arabic Supplement (U+0750–U+077F) adds characters for additional languages. Arabic script is fully cursive: every letter connects to adjacent letters, and letterform varies by position (initial, medial, final, isolated). Correct rendering requires full HarfBuzz or CoreText shaping with activated OpenType GSUB tables.

Unicode blocks for Arabic:

  • U+0600–U+06FF — Arabic (256 characters)
  • U+0750–U+077F — Arabic Supplement (48 characters)
  • U+FB50–U+FDFF — Arabic Presentation Forms-A
  • U+FE70–U+FEFF — Arabic Presentation Forms-B

Hebrew Script

Hebrew occupies U+0590–U+05FF (112 code points) plus U+FB1D–U+FB4E in the Alphabetic Presentation Forms block. Hebrew letters do not join like Arabic; each letter stands independently. The 22 consonants are augmented by vowel points (niqqud, U+05B0–U+05C7) and cantillation marks (teamim, U+0591–U+05AF) that appear as combining characters. Modern web content typically omits niqqud except in educational, liturgical, or children's text.

Unicode blocks for Hebrew:

  • U+0590–U+05FF — Hebrew (112 characters)
  • U+FB1D–U+FB4E — Alphabetic Presentation Forms

Persian (Farsi) and Urdu

Persian and Urdu are written using the Perso-Arabic script, an extension of Arabic with additional letters for sounds not in classical Arabic. Persian adds four letters: پ (pe), چ (che), ژ (zhe), گ (gaf). Urdu adds several more, including ٹ (te), ڈ (dal), ڑ (re), ڻ (nun). These extended characters are in the Arabic block (U+0600–U+06FF) and Arabic Extended-A (U+08A0–U+08FF). Both languages use Nastaliq calligraphic style for formal typography, which requires specialized fonts and advanced shaping beyond standard Arabic rendering.

CSS Properties for RTL Layout

RTL layout requires a combination of HTML attributes and CSS properties working together. The most important decision is whether to use physical properties (left, right) or logical properties (inline-start, inline-end). For any site serving RTL languages, logical properties are strongly recommended—they adapt automatically when direction changes.

HTML dir Attribute

Set dir="rtl" on the <html> element for fully RTL pages, or on individual containers for RTL sections within an LTR page. The dir attribute cascades to all descendants and informs both CSS and the Unicode Bidi Algorithm.

<!-- Full RTL page (Arabic, Hebrew, Persian, Urdu) -->
<html lang="ar" dir="rtl">
  <head>...</head>
  <body>...</body>
</html>

<!-- RTL section within an LTR page -->
<article lang="he" dir="rtl">
  <!-- Hebrew content here -->
</article>

<!-- Inline RTL within LTR sentence -->
<p>The word <span dir="rtl" lang="ar">مرحبا</span> means hello.</p>

CSS direction and unicode-bidi

The direction CSS property sets text direction for elements. It should always accompany the HTML dir attribute rather than replace it. The unicode-bidi property controls how the bidirectional algorithm handles an element. Use isolate to create a self-contained directionality context, preventing embedded content from affecting surrounding text direction.

/* RTL page baseline */
html[dir="rtl"] {
  direction: rtl;
}

/* Isolate an RTL span within LTR prose */
.rtl-inline {
  direction: rtl;
  unicode-bidi: isolate;
}

/* Embed LTR within RTL (e.g., a URL or number) */
.ltr-embed {
  direction: ltr;
  unicode-bidi: isolate;
  display: inline-block;
}

CSS Logical Properties

Logical properties use flow-relative references (inline-start, inline-end, block-start, block-end) instead of physical directions (left, right, top, bottom). In an RTL document, inline-start maps to the right side, allowing the same CSS to work correctly in both LTR and RTL contexts without direction-specific overrides.

/* Physical (avoid for RTL-compatible layouts) */
.box {
  margin-left: 1rem;
  padding-right: 1.5rem;
  border-left: 3px solid #f59e0b;
  text-align: left;
}

/* Logical (works correctly in both LTR and RTL) */
.box {
  margin-inline-start: 1rem;
  padding-inline-end: 1.5rem;
  border-inline-start: 3px solid #f59e0b;
  text-align: start;
}

/* Logical properties for layout */
.nav {
  float: inline-start;        /* was: float: left */
  inset-inline-start: 0;      /* was: left: 0 */
  inset-inline-end: auto;     /* was: right: auto */
}

Browser support: All modern browsers. IE11 and older do not support logical properties—use physical fallbacks if legacy support is required.

writing-mode for Vertical Scripts

While Arabic and Hebrew are horizontal RTL scripts, writing-mode becomes relevant if you work with Mongolian (vertical) or create mixed-axis layouts. For Arabic and Hebrew, writing-mode: horizontal-tb (the default) is correct. Do not set writing-mode: vertical-rl for Arabic—it will display incorrectly.

/* Correct for Arabic and Hebrew */
.arabic-text {
  direction: rtl;
  writing-mode: horizontal-tb; /* Default—no need to set */
}

/* Incorrect—never do this for Arabic/Hebrew */
.wrong {
  writing-mode: vertical-rl; /* Rotates text incorrectly */
}

Bidirectional Text Handling

Real-world Arabic, Hebrew, Persian, and Urdu content almost always contains bidirectional (bidi) text: RTL base content mixed with LTR sequences such as numbers, URLs, Latin brand names, email addresses, and code snippets. The Unicode Bidirectional Algorithm (UBA) handles most cases automatically, but complex mixed-direction content requires explicit markup to avoid rendering errors.

How the Unicode Bidi Algorithm Works

The UBA is built into every modern browser. It assigns directional properties to each Unicode character (strong LTR, strong RTL, neutral, or weak), then resolves the display order of runs of characters. Characters with intrinsic directionality (Arabic letters are strongly RTL, Latin letters are strongly LTR) determine the direction of their surrounding neutrals (punctuation, spaces).

The base paragraph direction (set by dir attribute) resolves ambiguity for neutral characters at the edges of directional runs. Without explicit markup, complex sequences can render in unexpected visual order even though the logical character order in the DOM is correct.

Embedding LTR Content in RTL Text

Wrap LTR content (numbers, URLs, Latin text) in a span with dir="ltr" and unicode-bidi: isolate to prevent the UBA from misinterpreting adjacent RTL context. Without isolation, parentheses and punctuation adjacent to the LTR content can flip to the wrong side.

<!-- RTL paragraph with embedded LTR URL -->
<p dir="rtl" lang="ar">
  يمكنك زيارة الموقع على
  <span dir="ltr" style="unicode-bidi: isolate;">
    https://font-converters.com
  </span>
  للمزيد من المعلومات.
</p>

<!-- RTL text with embedded LTR number -->
<p dir="rtl" lang="ar">
  السعر هو
  <span dir="ltr" style="unicode-bidi: isolate;">$49.99</span>
  فقط.
</p>

Embedding RTL in LTR Text

Similarly, when embedding RTL words or phrases within an LTR document, use dir="rtl" and isolation to create a contained RTL context. This is common in multilingual glossaries, linguistics content, and documentation that discusses RTL scripts.

<!-- LTR paragraph with embedded Arabic word -->
<p>
  The Arabic word for peace is
  <span dir="rtl" lang="ar" style="unicode-bidi: isolate;">سلام</span>
  (salam).
</p>

<!-- LTR paragraph with embedded Hebrew phrase -->
<p>
  The Hebrew phrase
  <bdi lang="he">שלום עולם</bdi>
  means &quot;hello world&quot;.
</p>

The <bdi> element (Bidirectional Isolation) is semantically equivalent to a span with unicode-bidi: isolate and is the preferred semantic element for isolating text of unknown or different directionality.

Common Bidi Pitfalls

  • Parentheses flipping: In RTL context, parentheses mirror visually—open and close swap sides, which is correct Unicode behavior. Use isolation to control which context they appear in.
  • Number trailing punctuation: A period or comma after a number may appear on the wrong side without isolation. Always wrap standalone numbers in LTR spans within RTL containers.
  • Mixed list items: List items with mixed-direction content should each have explicit dir attributes rather than relying on the parent list direction.
  • Input fields: Use dir="auto" on text inputs to detect direction from the first typed character—essential for multilingual forms.

Choosing RTL Fonts

Not all Arabic or Hebrew fonts render correctly in web environments. A production-quality RTL web font must contain complete OpenType GSUB and GPOS tables for shaping, cover the required Unicode blocks, and be optimized for screen rendering. The following fonts are proven choices for web use.

Font NameScriptStyleWeightsLicenseNotes
Noto Naskh ArabicArabicNaskh serif400, 500, 600, 700OFL (free)Best choice for body text; excellent vowel mark support
IBM Plex ArabicArabicModern sans-serif100–700OFL (free)Pairs with IBM Plex Sans for bilingual UI
CairoArabicGeometric sans-serif200–900OFL (free)Popular for modern Arabic web design; variable font available
AmiriArabicTraditional Naskh serif400, 700OFL (free)Ideal for literary, scholarly, and formal content
TajawalArabicHumanist sans-serif200–900OFL (free)Clean and modern; good for news and editorial sites
Noto Sans HebrewHebrewSans-serif100–900OFL (free)Safe default; covers full Hebrew block including niqqud
Frank Ruhl LibreHebrewTraditional serif300–900OFL (free)Classic Hebrew newspaper style; excellent for long-form reading
RubikHebrew + LatinRounded sans-serif300–900OFL (free)Bilingual Hebrew/Latin; great for Israeli tech products
HeeboHebrew + LatinHumanist sans-serif100–900OFL (free)Versatile bilingual font; widely used for Israeli web projects

Loading RTL Fonts with @font-face

Use unicode-range to restrict each RTL font to its script, preventing the browser from loading Arabic fonts on pages with no Arabic characters. Combine with font-display: swap for immediate text rendering during load.

/* Load Noto Naskh Arabic only for Arabic characters */
@font-face {
  font-family: 'Noto Naskh Arabic';
  src: url('/fonts/NotoNaskhArabic-Regular.woff2') format('woff2');
  font-weight: 400;
  font-style: normal;
  font-display: swap;
  unicode-range: U+0600-06FF, U+0750-077F, U+08A0-08FF,
                 U+FB50-FDFF, U+FE70-FEFF;
}

@font-face {
  font-family: 'Noto Naskh Arabic';
  src: url('/fonts/NotoNaskhArabic-Bold.woff2') format('woff2');
  font-weight: 700;
  font-style: normal;
  font-display: swap;
  unicode-range: U+0600-06FF, U+0750-077F, U+08A0-08FF,
                 U+FB50-FDFF, U+FE70-FEFF;
}

/* Bilingual Arabic/Latin font stack */
body:lang(ar) {
  font-family: 'Noto Naskh Arabic', 'Times New Roman', serif;
}

/* Bilingual Hebrew/Latin font stack */
body:lang(he) {
  font-family: 'Heebo', 'Arial', sans-serif;
}

OpenType Features for Arabic

Arabic script rendering relies entirely on OpenType GSUB (Glyph Substitution) and GPOS (Glyph Positioning) tables. The browser's text shaping engine (HarfBuzz on Chrome/Firefox, CoreText on Safari) applies these features automatically for content with lang="ar". However, understanding which features are active helps diagnose rendering issues and enables advanced typographic control.

Feature TagNameDescription
caltContextual AlternatesSelects context-sensitive glyph alternates for correct letter connections. Fundamental for all Arabic fonts.
initInitial FormsSubstitutes glyphs for letters at the start of a word that connect to the next letter.
mediMedial FormsSubstitutes glyphs for letters in the middle of a word, connected on both sides.
finaFinal FormsSubstitutes glyphs for letters at the end of a word, connected only on the right (in RTL flow).
isolIsolated FormsSubstitutes the standalone form for letters that cannot connect on either side.
markMark PositioningPositions combining marks (vowel points, hamza, shadda) relative to their base glyph using GPOS anchors.
mkmkMark-to-Mark PositioningPositions combining marks relative to other combining marks—required for stacked vowels and diacritics.
ligaStandard LigaturesCombines letter pairs into single ligature glyphs. The lam-alef (لا) ligature is mandatory in Arabic.
rligRequired LigaturesLigatures that are mandatory for correct text rendering and cannot be disabled by the user.

Controlling Arabic OpenType Features in CSS

Most Arabic shaping features are applied automatically by the browser when the language is set correctly. Manual feature control is useful for enabling optional stylistic variants or debugging rendering issues.

/* Arabic text — shaping features applied automatically with lang="ar" */
[lang="ar"] {
  font-family: 'Noto Naskh Arabic', serif;
  direction: rtl;

  /* Enable optional features */
  font-feature-settings:
    "calt" 1,   /* Contextual alternates (default on) */
    "liga" 1,   /* Standard ligatures (default on) */
    "rlig" 1,   /* Required ligatures (always on) */
    "mark" 1,   /* Mark positioning (default on) */
    "mkmk" 1;   /* Mark-to-mark (default on) */
}

/* Enable kashida (Arabic justification extension) if font supports it */
[lang="ar"].kashida-enabled {
  font-feature-settings: "calt" 1, "liga" 1, "rlig" 1,
                          "mark" 1, "mkmk" 1, "kern" 1;
  text-align: justify;
  text-justify: kashida; /* Experimental—check browser support */
}

/* Optional decorative alternates for headings */
.arabic-heading {
  font-feature-settings: "calt" 1, "rlig" 1, "ss01" 1; /* Stylistic set 1 */
}

Subsetting RTL Fonts

Full Arabic and Hebrew fonts can be 150–300KB even in WOFF2 format, because they contain hundreds of glyph variants for each positional form, optional decorative alternates, and extended Unicode coverage. Subsetting to only the needed Unicode ranges reduces this to 50–100KB without affecting rendering quality for typical web content.

Unicode Ranges for Subsetting

Use these ranges in both CSS unicode-range descriptors and your subsetting tool (pyftsubset, glyphhanger, or Subsetter.io) to target only the characters needed.

/* Arabic @font-face with unicode-range */
@font-face {
  font-family: 'Cairo';
  src: url('/fonts/cairo-arabic.woff2') format('woff2');
  font-weight: 400;
  font-display: swap;
  unicode-range:
    U+0600-06FF,   /* Arabic block (256 chars) */
    U+0750-077F,   /* Arabic Supplement (48 chars) */
    U+08A0-08FF,   /* Arabic Extended-A */
    U+FB50-FDFF,   /* Arabic Presentation Forms-A */
    U+FE70-FEFF,   /* Arabic Presentation Forms-B */
    U+0660-0669,   /* Arabic-Indic numerals */
    U+060C,        /* Arabic comma */
    U+061B,        /* Arabic semicolon */
    U+061F,        /* Arabic question mark */
    U+0640;        /* Arabic tatweel (kashida) */
}

/* Hebrew @font-face with unicode-range */
@font-face {
  font-family: 'Heebo';
  src: url('/fonts/heebo-hebrew.woff2') format('woff2');
  font-weight: 400;
  font-display: swap;
  unicode-range:
    U+0590-05FF,   /* Hebrew block (112 chars) */
    U+FB1D-FB4E,   /* Hebrew Presentation Forms */
    U+20AA,        /* New Shekel sign */
    U+05F3-05F4;   /* Hebrew punctuation geresh/gershayim */
}

Subsetting with pyftsubset

The pyftsubset tool from FontTools creates subsetted font files from the command line. Critical: always include --layout-features=* to preserve all OpenType GSUB/GPOS tables required for Arabic shaping.

# Subset an Arabic font—MUST include all layout features
pyftsubset NotoNaskhArabic-Regular.ttf   --unicodes="U+0600-06FF,U+0750-077F,U+08A0-08FF,              U+FB50-FDFF,U+FE70-FEFF,U+0660-0669,              U+060C,U+061B,U+061F,U+0640"   --layout-features="*"   --flavor=woff2   --output-file=NotoNaskhArabic-ar-subset.woff2

# Subset a Hebrew font
pyftsubset Heebo-Regular.ttf   --unicodes="U+0590-05FF,U+FB1D-FB4E,U+20AA,U+05F3-05F4"   --layout-features="*"   --flavor=woff2   --output-file=Heebo-he-subset.woff2

Critical warning

Never use --layout-features="" or omit layout features when subsetting Arabic fonts. Doing so strips the GSUB tables that enable contextual shaping, resulting in disconnected, unreadable Arabic text.

Expected File Sizes After Subsetting

FontFull WOFF2Subsetted WOFF2Reduction
Noto Naskh Arabic Regular~180KB~75KB58%
Cairo Regular~150KB~60KB60%
Amiri Regular~300KB~100KB67%
Heebo Regular~90KB~35KB61%
Frank Ruhl Libre Regular~110KB~40KB64%

Testing RTL Layouts

RTL typography bugs are often invisible to developers who only test with LTR content. A systematic testing approach with real RTL text catches layout mirroring issues, bidi rendering bugs, and font shaping failures before they reach production.

Common RTL Pitfalls

  • Physical CSS properties not mirrored: Using padding-left instead of padding-inline-start creates reversed padding in RTL contexts. Audit all physical directional properties.
  • Icons and arrows not mirrored: Directional icons (back arrows, next arrows, progress indicators) should flip in RTL. Use CSS transform: scaleX(-1) or provide mirrored SVG variants for directional glyphs.
  • Missing lang attribute: Without lang="ar" or lang="he", the shaping engine may not apply the correct OpenType feature lookups, producing degraded glyph selection even with a good font.
  • Hardcoded left-aligned text: text-align: left overrides the browser's natural RTL alignment. Use text-align: start to respect the content's writing direction.
  • Subsetted font missing GSUB tables: Subsetting Arabic without --layout-features=* strips shaping tables, producing disconnected letters. Always verify rendering after subsetting.
  • Scroll position jumping on direction change: Switching direction on the <html> element can cause scroll position issues. Handle language switches carefully and reset scroll position if needed.

RTL Testing Checklist

Layout

  • ☐ Navigation appears on correct side
  • ☐ Sidebar position is mirrored
  • ☐ Flex/grid layouts reverse correctly
  • ☐ Icons and arrows are mirrored
  • ☐ Form labels align to correct side
  • ☐ Breadcrumbs read right to left

Typography

  • ☐ Arabic letters join correctly (no disconnection)
  • ☐ Vowel marks (harakat) position correctly
  • ☐ Numbers display in correct order
  • ☐ Mixed bidi content renders in correct order
  • ☐ Font loads (check DevTools Network tab)
  • ☐ Fallback font renders acceptably

Interaction

  • ☐ Text input fields accept RTL input
  • ☐ Cursor moves correctly in RTL fields
  • ☐ Copy-paste preserves text direction
  • ☐ Screen reader announces content order

Performance

  • ☐ RTL font loads in under 500ms
  • ☐ No layout shift on font swap
  • ☐ unicode-range prevents unnecessary loads
  • ☐ Font is preloaded for above-fold text

Test Strings for Arabic and Hebrew

Use these strings to verify font rendering. Correct Arabic rendering shows connected, contextually shaped letters; correct Hebrew rendering shows properly positioned diacritics.

<!-- Arabic test strings -->
<!-- Basic connectivity: all letters should join -->
<p lang="ar" dir="rtl">بسم الله الرحمن الرحيم</p>

<!-- Lam-alef ligature (mandatory): لا must merge into single glyph -->
<p lang="ar" dir="rtl">لا إله إلا الله</p>

<!-- Mixed bidi: Arabic with embedded Latin and number -->
<p lang="ar" dir="rtl">السعر هو <span dir="ltr">USD 49.99</span> فقط</p>

<!-- Hebrew test strings -->
<!-- Basic Hebrew consonants -->
<p lang="he" dir="rtl">שלום עולם</p>

<!-- Hebrew with niqqud (vowel points) -->
<p lang="he" dir="rtl">בְּרֵאשִׁית בָּרָא אֱלֹהִים</p>

<!-- Hebrew with embedded LTR URL -->
<p lang="he" dir="rtl">בקר באתר <bdi>https://font-converters.com</bdi></p>

Right-to-Left Font FAQs

Common questions about Arabic, Hebrew, and RTL web typography

Sarah Mitchell

Written & Verified by

Sarah Mitchell

Product Designer, Font Specialist

Related Resources

Ready to Subset Your Arabic or Hebrew Font?

Use our Font Subsetter to reduce Arabic font sizes from 150–300KB to 50–100KB while preserving all OpenType shaping features.

Open Font Subsetter