Building Production-Ready RTL Design Systems: A Practical Guide

·

Building Production-Ready RTL Design Systems: A Practical Guide

Building a design system that truly supports RTL (right-to-left) languages is harder than most teams realize. Most “RTL-ready” systems collapse the moment they hit real Arabic, Hebrew, or Persian content in production. The CSS works, but the UX falls apart.

This guide covers the actual techniques production teams use to ship design systems that work in both directions — covering logical properties, typography, components, testing, and the pitfalls that cost teams weeks of rework.

The Mindset Shift: Bidirectional, Not “RTL Support”

The first mistake teams make: they design for LTR, then “add RTL support.” This produces systems where:

  • 80% works correctly
  • 20% breaks in subtle ways nobody notices until launch
  • Engineers spend months patching

The right approach: Design bidirectionally from day one. Every component, every layout, every interaction should be built to work in both directions natively.

This means:

  • Using logical properties instead of left/right physical properties
  • Thinking about flow direction, not “this goes on the left”
  • Testing in RTL continuously, not at the end

CSS Logical Properties: The Foundation

Modern CSS provides logical properties that automatically flip based on text direction. Stop writing this:

/* DON'T */ .card { margin-left: 16px; padding-right: 24px; border-left: 2px solid blue; }

Write this instead:

/* DO */ .card { margin-inline-start: 16px; padding-inline-end: 24px; border-inline-start: 2px solid blue; }

The key mappings:

Physical Logical Description
margin-left margin-inline-start Where text begins
margin-right margin-inline-end Where text ends
padding-top padding-block-start Top of writing block
padding-bottom padding-block-end Bottom of writing block
border-left border-inline-start Border at text origin
text-align: left text-align: start Align toward text origin

When you set dir="rtl" on the HTML, all logical properties automatically reverse. No JavaScript needed.

Typography: The Most Underrated Aspect

Typography is where most “RTL design systems” fail. They ship the same type scale and expect Arabic to work. It doesn’t.

Arabic Font Selection

Latin fonts and Arabic fonts have very different metrics. You can’t use Inter (a Latin font) and expect Arabic to render well. You need:

For Arabic specifically:Tajawal — Modern, geometric, pairs well with Inter – IBM Plex Sans Arabic — Technical, official feel – Cairo — Friendly, popular for consumer products – GE SS — Premium, corporate (paid)

For pairing with Latin: – Use Latin font + Arabic font that share visual weight – Test character widths at the same font-size – Adjust line-height for Arabic (often needs more)

:root { --font-latin: 'Inter', system-ui, sans-serif; --font-arabic: 'Tajawal', 'Inter', system-ui, sans-serif; --line-height-latin: 1.5; --line-height-arabic: 1.75; /* Arabic needs more */ } body { font-family: var(--font-latin); } :dir(rtl) body { font-family: var(--font-arabic); line-height: var(--line-height-arabic); }

Type Scale Adjustments

Arabic characters often appear visually heavier than Latin at the same point size. Consider:

  • Slightly reducing display sizes in RTL (5-10%)
  • Increasing line-height by 10-15%
  • Adjusting letter-spacing (Arabic doesn’t need it; remove if applied to Latin)

Mixed Content

The hardest case: a single paragraph with both Arabic and English. The Unicode bidirectional algorithm handles most of it, but you need:

<p dir="rtl"> المقال نُشر على <span dir="ltr">tinygiants.com/blog</span> منذ ٣ أيام </p>

The dir="ltr" on the URL keeps it from being mangled by the bidirectional algorithm.

Component Patterns That Work

Buttons with Icons

Always position icons relative to text flow, not physical sides:

.button { display: inline-flex; gap: 8px; } .button-icon { /* Icon position adjusts automatically with flex */ /* For directional icons (arrows), flip them in RTL */ } :dir(rtl) .icon-arrow-right { transform: scaleX(-1); }

Form Inputs

Form layouts should use logical properties:

.form-field { display: flex; flex-direction: column; gap: 4px; } .form-label { font-size: 14px; /* Don't set text-align — defaults to start, which adapts */ } .form-input { padding-inline: 12px; border-inline-start: 3px solid transparent; /* On focus: highlight starts where text starts */ } .form-input:focus { border-inline-start-color: var(--color-primary); }

Navigation

For top navigation with logo + menu:

.nav { display: flex; justify-content: space-between; /* Flex automatically handles direction */ } .nav-logo { /* No physical positioning needed */ } .nav-menu { display: flex; gap: 24px; }

For breadcrumbs, use flex-direction that reverses naturally:

.breadcrumbs { display: flex; gap: 8px; /* In LTR: Home > Category > Page */ /* In RTL: page < category < home (automatically) */ } .breadcrumb-separator { /* If using ">" symbol, change to "<" in RTL */ } :dir(rtl) .breadcrumb-separator::before { content: "<"; }

Modals and Overlays

Position from logical sides:

.modal-close-button { position: absolute; inset-block-start: 16px; inset-inline-end: 16px; /* Top-right in LTR, top-left in RTL — natural placement */ }

Icons: The Directional Ones

Most icons are direction-agnostic (✓, ⚠️, 🏠). Some are directional and need flipping:

Always flip in RTL: – Arrows pointing left/right – Back/forward buttons – “Next” indicators – Speech bubble tails

Don’t flip in RTL: – Universal symbols (search, settings, user) – Brand logos – Number sequences (use Latin numerals in most modern Arabic UI)

/* SVG sprite icons */ :dir(rtl) .icon[data-direction="auto"] { transform: scaleX(-1); }

Mark directional icons explicitly:

<svg data-direction="auto"><!-- arrow --></svg> <svg data-direction="fixed"><!-- search --></svg>

Animations and Transitions

Animations should respect direction:

/* Slide-in panel — slides from "end" of page */ .slide-panel { transform: translateX(100%); transition: transform 0.3s ease; } .slide-panel.is-open { transform: translateX(0); } /* In RTL, "100%" needs to be "-100%" */ :dir(rtl) .slide-panel { transform: translateX(-100%); }

Or use logical properties (better but less browser support):

.slide-panel { transform: translate(100%, 0); /* doesn't auto-flip */ /* Use logical instead */ inset-inline-end: -100%; transition: inset-inline-end 0.3s ease; }

Testing: The Critical Step

Build RTL into your testing process:

Component Testing

Every component story in Storybook should have: – LTR variant – RTL variant – LTR + Arabic content (to test font rendering) – RTL + English content (to test logical properties)

Visual Regression

Use Percy or Chromatic with both LTR and RTL screenshots. Catch CSS regressions early.

Real Content Testing

Stub content doesn’t reveal real-world issues. Test with: – Long Arabic words that wrap – Mixed Arabic/English sentences – Numbers (eastern vs. western) – Emoji in Arabic context – Punctuation differences

Accessibility

Screen readers need correct lang and dir attributes:

<html lang="ar" dir="rtl"> <body> <!-- mixed content --> <p>The article <span lang="en" dir="ltr">"Why Websites Fail"</span> was published recently </p> </body> </html>

Common Pitfalls

1. Hardcoded Margins

/* Looks fine until RTL */
.item + .item {
  margin-left: 16px; /* should be margin-inline-start */
}

2. Flexbox flex-direction: row-reverse

Don’t use this for “automatic RTL.” It also reverses in LTR languages, breaking the original layout. Use logical properties instead.

3. Backgrounds and Images

Background images positioned with background-position: left won’t flip. Use:

.with-bg { background-position: var(--bg-position, left center); } :dir(rtl) .with-bg { --bg-position: right center; }

4. Charts and Data Visualization

Time-series charts read right-to-left in RTL — today on the right, yesterday on the left. Most charting libraries don’t handle this automatically. Plan for custom config.

5. Numeric Content

Even in RTL Arabic content, numbers stay in their natural reading order. Don’t try to flip them.

Tools and Resources

  • PostCSS Logical — Automatically converts physical to logical CSS
  • autoprefixer — Handles vendor prefixes for logical properties
  • Tailwind CSS RTL Plugin — Logical utilities for Tailwind users
  • rtl-css.net — Reference for which CSS properties affect RTL

The ROI of Doing This Right

Teams who invest in proper RTL design systems see:

  • 40-60% reduction in bidirectional bugs
  • 50-70% faster feature development for both directions
  • Significantly better UX for RTL users (often a market they’re trying to win)

The MENA market is one of the fastest-growing regions in tech. A design system that handles RTL natively gives you a real competitive advantage there.

Conclusion

A production-ready RTL design system isn’t about adding dir="rtl" at the end. It’s about building bidirectionally from day one — with logical properties, proper typography, thoughtful components, and continuous testing.

The investment pays off. Teams that ship truly bidirectional systems unlock the MENA market in ways that “RTL-supported” systems never quite achieve.

Building or refactoring a design system? We help teams across MENA build production-ready bidirectional systems. Get in touch for a consultation.

Have a project in mind?

Tell us about your idea and we’ll get back to you within 24 hours.


عندك مشروع وعايز تتكلم؟

قول لنا فكرتك ونرد عليك خلال 24 ساعة. مش هتلاقي ضغط أو وعود بعيد عن الواقع.