Getting a Persian or Arabic site to look right takes more than text-align: right. Direction affects layout, spacing, icons, numbers, form fields and even your carousel arrows. Below is the checklist I use for every right-to-left (RTL) project with Bootstrap 5, plus a free starter template you can clone.
TL;DR: set
dir="rtl"on<html>, loadbootstrap.rtl.min.css, use logical properties in your own CSS, flip only the icons that show direction, and test with real Persian/Arabic text.
1. Start at the root: lang and dir
<!doctype html>
<html lang="fa" dir="rtl">
dir="rtl" changes how the browser lays out the page: inline flow, table column order, scrollbar side in some browsers, and the default alignment of text. lang matters too. It drives hyphenation, font selection, and screen reader pronunciation. Use fa for Persian and ar for Arabic.
Don't set direction with CSS (direction: rtl on body). The HTML attribute is semantic, it's inherited by form controls, and the :dir() selector and Bootstrap both rely on it.
2. Use Bootstrap's official RTL build
Since v5, Bootstrap ships a full RTL stylesheet generated with RTLCSS. Swap one file:
<link rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.rtl.min.css">
What you get for free:
-
ms-*/me-*andps-*/pe-*mean start and end, so in RTLms-3puts the margin on the right. -
text-startaligns right,float-endfloats left, and so on. - Navbar, dropdowns, input groups, breadcrumbs, pagination and modals are all mirrored.
A common trap: an icon placed before its label needs space at its end, so it gets me-2 in both LTR and RTL. If you "fix" it to ms-2 because you think in left/right, the icon ends up stuck to the text. Think "start = where reading begins".
Using Sass? Compile your own build and run it through RTLCSS. You can keep one source and produce style.css and style.rtl.css. Bootstrap uses the /* rtl:ignore */ and /* rtl:raw: */ directives for exceptions.
3. Write your own CSS with logical properties
Your custom CSS isn't flipped automatically when you just use the CDN file. Logical properties remove the need for a second stylesheet:
| Physical (avoid) | Logical (use) |
|---|---|
margin-left |
margin-inline-start |
padding-right |
padding-inline-end |
left: 0 |
inset-inline-start: 0 |
border-left |
border-inline-start |
text-align: left |
text-align: start |
border-top-left-radius |
border-start-start-radius |
.sidebar {
position: fixed;
inset-inline-start: 0; /* right in RTL, left in LTR */
border-inline-end: 1px solid var(--bs-border-color);
}
Browser support is universal in evergreen browsers. Transforms are the exception: translateX(-100%) doesn't flip, so off-canvas animations need a direction-aware rule:
.sidebar { transform: translateX(100%); }
[dir="ltr"] .sidebar { transform: translateX(-100%); }
4. Fonts: pick one designed for Persian/Arabic
System fallbacks for Persian vary a lot by OS and often look cramped. Vazirmatn is a free (OFL) variable font with Persian and Arabic glyphs and good Latin support:
<link rel="stylesheet"
href="https://cdn.jsdelivr.net/gh/rastikerdar/vazirmatn@v33.003/Vazirmatn-font-face.css">
:root { --bs-body-font-family: Vazirmatn, system-ui, sans-serif; }
body { line-height: 1.9; }
Two tips:
- Increase line-height. Arabic-script letters have tall ascenders and dots below the baseline, so 1.5 often feels tight. 1.8–2 reads better for body text.
-
Don't use
letter-spacingon Persian/Arabic text. The script is cursive, and spacing breaks the joins between letters.
5. Icons: flip only what shows direction
Not every icon should be mirrored. A rule of thumb:
- Flip: arrows, chevrons, "back/next", reply, send, undo/redo, list-with-bullets, progress indicators.
- Don't flip: search, check marks, clocks (clocks run clockwise everywhere), media play buttons (debatable, but most RTL apps keep ▶), logos, and anything that shows a real-world object.
With Bootstrap Icons, just choose the opposite glyph in RTL (bi-chevron-left for "next"), or add a utility:
[dir="rtl"] .flip-rtl { transform: scaleX(-1); }
Bootstrap's carousel controls and breadcrumb separators are already handled by the RTL build.
6. Numbers, forms and mixed content
This is where most RTL bugs live.
Digits. Persian users expect Persian digits (۰۱۲۳…) in content. Convert at render time, not in your data:
const toFa = s => String(s).replace(/\d/g, d => '۰۱۲۳۴۵۶۷۸۹'[d]);
document.querySelectorAll('.fa-num').forEach(el => el.textContent = toFa(el.textContent));
For prices, Intl.NumberFormat('fa-IR').format(1250000) gives ۱٬۲۵۰٬۰۰۰ with the correct separator.
Inputs. Fields like email, URL, phone, card numbers and codes should stay LTR even on an RTL page:
<input type="email" class="form-control" dir="ltr">
<input type="tel" class="form-control text-start" dir="ltr" inputmode="numeric">
On the server, normalize what users type. Persian keyboards produce ۱۲۳, Arabic keyboards produce ١٢٣, so convert both to ASCII before validation.
Mixed direction text. A phrase like "Bootstrap 5 RTL" inside a Persian sentence is usually fine. User-generated strings, usernames and anything ending in punctuation can jump around, though. Wrap them:
<bdi>{{ username }}</bdi>
<span dir="auto">{{ user_comment }}</span>
dir="auto" on text inputs and textareas also helps when users type in either language.
7. Tables, charts and third-party widgets
- Tables mirror automatically with
dir, so the first column ends up on the right. Keep numeric columns right-aligned anyway (text-end= left in RTL; pick deliberately). - Charting libraries often need an explicit RTL option, such as reversed axes or a
rtl: truelegend. CSS-only bar charts built with flexbox mirror for free. - Date pickers: Persian sites usually need the Jalali (Solar Hijri) calendar.
Intl.DateTimeFormat('fa-IR')formats dates in that calendar out of the box.
8. Testing checklist
- Use real text, not lorem ipsum. Persian words differ in length and shape, and layout bugs only show with real copy.
-
Toggle direction live in DevTools (
document.documentElement.dir = 'ltr'). If anything that shouldn't move breaks, you have a physical property somewhere. -
Search your CSS for
left,right,margin-left,padding-rightandtranslateX. Each one is a candidate bug. - Check mobile off-canvas menus. They are the most common RTL animation bug.
- Test form validation with Persian digits and copy-pasted text.
-
Screen reader pass. With the correct
lang, VoiceOver and NVDA switch voices.
A free starter to skip the setup
I put all of the above into a small MIT-licensed template: a landing page (hero, features, pricing, FAQ) and a dashboard (off-canvas sidebar, stat cards, CSS bar chart, table). It uses Bootstrap 5 RTL, Vazirmatn and a Persian-digit helper, with no jQuery and no build step.
- Repo: https://github.com/Cmatrix1/rtl-html-starter
- Live demo: https://cmatrix1.github.io/rtl-html-starter/
Clone it, change --brand in style.css, and start writing content. If you need complete production templates (shop, admin panel, corporate), we also maintain a collection of Persian RTL HTML templates at RTLHTML.
Got an RTL bug that isn't covered here? Drop it in the comments and I'll add it to the checklist.
Top comments (0)