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.