RTL Web Development for AI Agents
You are generating frontend code. Unless explicitly told otherwise, assume the project may need RTL support. Following these conventions prevents layout bugs that are expensive to fix later.
The One Rule
Always use CSS logical properties and RTL-safe Tailwind classes. Never use physical direction properties (left, right, margin-left, margin-right) unless the positioning is intentionally physical and should NOT mirror.
Quick Mapping: Tailwind
| Never use | Always use | Why |
| -------------- | -------------- | ----------------------------------- |
| ml-* | ms-* | margin-left → margin-inline-start |
| mr-* | me-* | margin-right → margin-inline-end |
| pl-* | ps-* | padding-left → padding-inline-start |
| pr-* | pe-* | padding-right → padding-inline-end |
| left-* | start-* | left positioning |
| right-* | end-* | right positioning |
| text-left | text-start | text alignment |
| text-right | text-end | text alignment |
| border-l-* | border-s-* | border start |
| border-r-* | border-e-* | border end |
| rounded-l-* | rounded-s-* | border-radius start |
| rounded-r-* | rounded-e-* | border-radius end |
| rounded-tl-* | rounded-ts-* | top-start radius |
| rounded-tr-* | rounded-te-* | top-end radius |
| rounded-bl-* | rounded-bs-* | bottom-start radius |
| rounded-br-* | rounded-be-* | bottom-end radius |
<!-- Bad -->
<div class="ml-4 mr-2 pl-3 pr-1 text-left rounded-l-lg border-l-2">
<!-- Good -->
<div class="ms-4 me-2 ps-3 pe-1 text-start rounded-s-lg border-s-2"></div>
</div>
Quick Mapping: CSS & CSS-in-JS
| Physical | Logical |
| -------------------------------- | --------------------------------------------- |
| margin-left / marginLeft | margin-inline-start / marginInlineStart |
| margin-right / marginRight | margin-inline-end / marginInlineEnd |
| padding-left / paddingLeft | padding-inline-start / paddingInlineStart |
| padding-right / paddingRight | padding-inline-end / paddingInlineEnd |
| border-left / borderLeft | border-inline-start / borderInlineStart |
| border-right / borderRight | border-inline-end / borderInlineEnd |
| left | inset-inline-start |
| right | inset-inline-end |
| width | inline-size |
| height | block-size |
| text-align: left | text-align: start |
| text-align: right | text-align: end |
| float: left | float: inline-start |
| float: right | float: inline-end |
For the complete property table (border-color, border-width, border-style, min/max sizes), see references/mappings.md.
The dir Attribute
<html dir="rtl" lang="ar"></html>
<!-- OR -->
<html dir="ltr" lang="en"></html>
- Always set
dirandlangon<html>, not via CSSdirectionalone - Use
dir="auto"on user-generated content containers where language is unknown - For mixed-content elements, set
diron the element for correct word ordering
<p dir="auto">مرحباً Welcome to the article about RTL design</p>
Flexbox & Grid Auto-Flip
Flexbox and CSS Grid automatically respect the writing mode. Items reorder when dir="rtl" is set.
.header {
display: flex;
justify-content: space-between;
} /* auto-flips */
.layout {
display: grid;
grid-template-columns: 220px 1fr;
} /* auto-flips */
Gotcha: flex-direction: row-reverse also reverses in RTL, causing a double-flip back to LTR order. Never use row-reverse to "fix" RTL layout -- flexbox handles direction automatically.
Gotcha: explicit order values in flexbox do NOT auto-flip. Handle manually for RTL.
The :dir() Pseudo-Class
Use :dir(rtl) for direction-based styling. Unlike [dir="rtl"], it matches elements that inherit direction from ancestors.
.element:dir(rtl) {
/* RTL styles */
}
.element:dir(ltr) {
/* LTR styles */
}
Browser support: Firefox, Chrome, Safari 16.4+, Edge.
Bidirectional Isolation: <bdi> and <bdo>
<bdi>isolates user-generated content whose direction is unknown (prevents corrupting surrounding text)<bdo dir="ltr">forces LTR for phone numbers, credit cards, code that must stay LTR
<p>Welcome back, <bdi>Ahmed</bdi>! Your score: <bdi>۳۵۰</bdi></p>
<bdo dir="ltr">+1 (555) 123-4567</bdo>
<bdo dir="ltr">4111-1111-1111-1111</bdo>
Tailwind rtl: Variant
Use rtl: for directional icons and components that need explicit flipping:
<button class="rtl:rotate-180"><ArrowLeftIcon /></button>
<div class="text-start rtl:text-right">
<div class="ms-4 rtl:me-4 rtl:ms-0"></div>
</div>
Always prefer logical property utilities (ms-*, me-*, ps-*, pe-*, start-*, end-*) over rtl: variant hacks.
RTL Exceptions: When NOT to Flip
Never flip:
- Symmetrical icons (home, search, user, settings, camera, star, heart)
- Media player controls (play, pause, forward -- tape direction, not time)
- Right-hand objects (icons per Material Design guideline)
- Data visualizations (charts, graphs, maps)
- Images, videos, code blocks -- keep original orientation
- Phone numbers, credit cards, math expressions -- stay LTR
- Music notation, clock faces
Force LTR when needed:
<span dir="ltr">+1 (555) 123-4567</span>
<code dir="ltr">console.log("hello");</code>
Bidirectional Icons
Flip (they indicate direction): arrows, breadcrumb separators, send icon, undo/redo, sort indicators.
Don't flip (universal/symmetrical): play/pause/stop, search, menu, close, plus, checkmark, home, settings, download/upload.
[dir="rtl"] .icon-arrow-right {
transform: scaleX(-1);
}
Arabic Typography Pitfalls
See references/arabic-typography.md for the full guide. Critical rules:
- Letter-spacing: MUST be zero on Arabic text -- letters connect, spacing breaks them
- Use solid colors for Arabic text --
rgba()andopacitycause rendering artifacts between connected letters - Underlines overlap Arabic dots -- use
text-decoration-skip-ink: autoorbox-shadowunderlines - Line height -- Arabic needs 1.6-1.8 vs Latin 1.4-1.5 (diacritics get cut off otherwise)
- Word-break: never
break-allon Arabic -- connected letters can't break mid-word - No abbreviations in Arabic -- letters must stay connected, "Sat" for "Saturday" doesn't work
- Provide min-width on buttons -- Arabic translations are often shorter or longer than English
Component RTL Patterns
See references/component-patterns.md for detailed examples covering:
- Button icons (icon position flips)
- Form inputs (email/URL stay LTR, name/address RTL)
- Breadcrumbs (separator auto-flips)
- Tables, tabs, cards, toasts, blockquotes, toggle switches
- Page headers (logo and actions swap)
Search Input with Icon
.c-input--search {
background-image: url("data:image/svg+xml,...");
background-position: right 6px center;
padding-inline-end: 32px;
}
Floats: Avoid Them
Floats do NOT auto-flip. If you must use them:
.media__photo {
float: left;
margin-right: 16px;
}
[dir="rtl"] .media__photo {
float: right;
margin-right: 0;
margin-left: 16px;
}
Better: use flexbox instead.
Transform Animations in RTL
translateX() does NOT auto-flip. Use scaleX(-1) for arrow icons:
.c-link:hover svg {
transform: translateX(6px);
}
[dir="rtl"] .c-link svg {
transform: scaleX(-1);
}
[dir="rtl"] .c-link:hover svg {
transform: scaleX(-1) translateX(6px);
}
CSS Naming Conventions
Use start/end in class names, not left/right:
/* Bad */
.c-header__left {
}
.c-header__right {
}
/* Good */
.c-header__start {
}
.c-header__end {
}
React / Next.js RTL Setup
<html dir={locale === "ar" ? "rtl" : "ltr"} lang={locale}>
CSS-in-JS (styled-components, Emotion) requires a Stylis plugin for RTL. MUI provides @mui/stylis-plugin-rtl:
import rtlPlugin from "stylis-plugin-rtl";
import { CacheProvider } from "@emotion/react";
import createCache from "@emotion/cache";
const cacheRtl = createCache({ key: "muirtl", stylisPlugins: [rtlPlugin] });
Testing RTL
- Toggle
dir="rtl"on<html>in browser DevTools - Test every component in both LTR and RTL
- Check for: text overflow, alignment, icon orientation, scrollbar position, truncation direction
- Test with actual Arabic content -- not just mirrored Lorem Ipsum
Companion: ESLint Plugin
For automated linting of physical direction properties, use eslint-plugin-tailwind-rtl. It catches RTL bugs in CI and code review that slip past generation:
npm install --save-dev eslint-plugin-tailwind-rtl