RTL testing checklist for Arabic websites
What to check before an Arabic website ships: direction, bidi text, logical CSS, layout, typography, numbers, forms and accessibility, with a fix for each.
Guide10 min read
Adding dir="rtl" flips a page. It does not make the page right. The layout mirrors, and then a phone number reads backwards, a price shows a different set of digits from the date beside it, a drawer slides in from the wrong edge, and the error message on the sign-up form is still in English.
This checklist is for teams shipping an Arabic version of a product, including teams where nobody reads Arabic. Most of it can be checked without reading a word of the language. Each item says what breaks, how to spot it and how to fix it, and where Ritla has a check for it, names that check.
Before you start#
Set the test up so the bugs have somewhere to show:
- Test the real Arabic build. A mirrored English page hides the problems that only real Arabic text causes: fonts, joined letters, line height and text length.
- Use realistic content. Long names, addresses, phone numbers, prices, dates, product names in Latin script and text written by users. Placeholder copy passes tests that real content fails.
- Test at more than one width. A desktop layout and a phone layout around 390px wide break in different places.
- Keep the English page open beside it. Comparing the two side by side is the fastest way to see what did not mirror.
Document direction and language#
Everything else depends on the root element, so check the page source, not only what renders:
- The
<html>element hasdir="rtl". Without it, the browser lays the page out left to right.R001 - The
<html>element haslang="ar", or a regional tag such asar-SAorar-AE.R002 langdoes not set direction. A page withlang="ar"and nodiris still laid out left to right.- Direction comes from the
dirattribute, not only from CSS. A page styled withdirection: rtlrenders right to left, but the:dir(rtl)selector does not match it, so styles written against:dir(rtl)never apply. The W3C is explicit: set the base direction in markup, never with CSS. - The
<title>and meta description are translated. They are what people see in search results and browser tabs.R004 - Open Graph tags are translated, and
og:localeis a real Arabic locale such asar_SA.R005 - The
hreflangset includes the Arabic pageR003, and the canonical URL points at the Arabic page itself rather than the English one.R006
<html lang="ar-SA" dir="rtl">
<head>
<title>الأسعار والخطط</title>
<link rel="canonical" href="https://example.com/ar/pricing">
<link rel="alternate" hreflang="ar" href="https://example.com/ar/pricing">
<link rel="alternate" hreflang="en" href="https://example.com/en/pricing">
<meta property="og:locale" content="ar_SA">
</head>Untranslated text#
Untranslated strings are the easiest bug to find without reading Arabic: look for Latin script where Arabic should be.
- Body text. Paragraphs, headings and table content still in English.
R010 - Interactive labels. Buttons, navigation, menus, tabs and footer links. Fix these first, because people find their way by them.
R011 - Attribute text.
placeholder,alt,titleandaria-labelare invisible in a visual pass, but screen readers read them aloud.R012 - Form text. Labels, hints and, above all, error messages.
R071 - Intentional English. Brand and product names can stay in Latin script. Mark them with
translate="no"so translation tools leave them alone, and withlang="en"so screen readers switch voice.R080
Error messages need a manual pass, because they only exist after something goes wrong. Submit each form empty, type an invalid email address, and read what comes back. Watch the browser's own messages too: the bubbles shown for required or type="email" come from the browser, not from your page, so an Arabic page opened in an English browser shows them in English. If that matters for your users, set your own text with setCustomValidity() or show your own validation messages.
Bidirectional text#
Arabic pages are rarely pure Arabic. They carry phone numbers, email addresses, prices, order numbers, product names and code. The browser orders mixed text with the Unicode Bidirectional Algorithm, and without help it gets some of it visibly wrong. Two examples, as a browser shows them inside a right-to-left paragraph:
| Written in the source | What the reader sees |
|---|---|
+966 55 123 4567 | 4567 123 55 966+ |
C++ | ++C |
The fix is to isolate the run, so its direction is worked out on its own:
- Values you know are left to right, such as phone numbers, email addresses, URLs and order numbers: wrap them in an element with
dir="ltr".R021 - Values whose direction you do not know, such as user names or product titles from a CMS: wrap them in
<bdi>, which isolates the text and takes its direction from its own content.
<p>للتواصل: <span dir="ltr">+966 55 123 4567</span></p>
<p>تعلّم <bdi>C++</bdi> من الصفر</p>While you are looking at text direction, check these too:
- Containers forced left to right that hold Arabic text, often components copied from the English build with
dir="ltr"still on them.R020 text-align: lefton Arabic text. Usetext-align: start, which follows the direction of the page.R022- Punctuation at the wrong end of a sentence. This usually comes from joining strings across a change of direction. Isolate the inserted value instead of moving the punctuation.
R024 - Directional icons. A back arrow that points left in English must point right in Arabic.
R025Not every icon flips: the Material Design guidelines on bidirectionality keep media playback controls, clocks and icons that show no direction, such as a camera, as they are.
For the mechanics, the W3C's article on inline markup and bidirectional text and MDN's reference for the bdi element go further.
Physical CSS#
dir="rtl" mirrors what is logical: the flow of text, and the order of flexbox and grid items. It does not touch CSS written in physical terms. A margin-left stays on the left in Arabic, and so does everything positioned with left.
- Physical margins, padding, borders and offsets with no right-to-left override.
R030 float: leftandfloat: righton content.R031- Shadow offsets and
text-indentthat only make sense in one direction.R032 translateXvalues that move an element off screen once the page runs right to left: drawers, carousels and sliders.R033
The fix is to write CSS with logical properties, which follow the direction of the page:
| Physical | Logical |
|---|---|
margin-left | margin-inline-start |
padding-right | padding-inline-end |
left: 0 | inset-inline-start: 0 |
border-left | border-inline-start |
border-top-left-radius | border-start-start-radius |
text-align: left | text-align: start |
float: right | float: inline-end |
Transforms have no logical form, so mirror them with the :dir() selector:
.drawer {
position: fixed;
inset-inline-start: 0;
transform: translateX(-100%);
}
.drawer:dir(rtl) {
transform: translateX(100%);
}Utility frameworks have logical classes too. In Tailwind CSS, ml-4 becomes ms-4 and text-left becomes text-start, and Bootstrap 5 uses the same ms-*, me-* and text-start names. MDN's guide to CSS logical properties and values lists the rest.
Layout at real widths#
Arabic text differs in length and height from the English it replaces, and a mirrored layout overflows in new places. Check each page at a desktop width and at a phone width:
- The page scrolls sideways. Often one element positioned for left to right now hangs off the other edge.
R040 - Text overlaps other text. Often a negative margin or an absolute position that did not mirror.
R041 - Text is cut off. A box with
overflow: hiddenand a fixed size is too small for the Arabic string.R042 - Fixed widths. Buttons, badges and table columns sized in pixels for English labels.
R043
To find what makes a page scroll sideways, run this in the browser console. It lists every element that sticks out past either edge, including any that are hidden off screen on purpose:
[...document.querySelectorAll("body *")].filter((el) => {
const box = el.getBoundingClientRect();
return box.left < 0 || box.right > document.documentElement.clientWidth;
});Arabic typography#
Arabic is a joined script with more vertical detail than Latin, and type settings tuned for English damage it:
- Font fallback. When the declared font has no Arabic letters, the browser silently borrows a system font. Name an Arabic-capable family in the stack.
R050 - Letter spacing. Arabic letters connect, and any positive
letter-spacingpulls them apart. Remove it for Arabic text.R051 - Line height. Arabic needs more vertical room than Latin; body text set below a line height of 1.5 looks cramped.
R052 - Small text. Arabic body text below 14px loses legibility quickly.
R053 - Italics. Arabic has no italic tradition, so the browser slants the letters mechanically. Emphasise with weight instead.
R054 - Latin display styles. Uppercase does nothing to Arabic, and the wide tracking that usually comes with it breaks the letters apart.
R055
Font fallback happens character by character, so a Latin family first and an Arabic family second gives each script its own design, as long as both are loaded:
body {
font-family: "Inter", "IBM Plex Sans Arabic", sans-serif;
}
:lang(ar) :is(em, i) {
font-style: normal;
font-weight: 600;
}The W3C's Arabic & Persian Layout Requirements document how Arabic text is set, in much more depth than a checklist can.
Numbers, dates and currency#
- Mixed digits. Western digits in one place and Arabic-Indic digits (٠١٢٣٤٥٦٧٨٩) in another, on the same screen.
R061 - English dates. Month names in English, or dates in
MM/DD/YYYYorder, inside Arabic text.R060 - Foreign currency formats on a page priced in riyals or dirhams.
R062 - Percent signs typed on the wrong side of the number.
R063 - Latin punctuation. Arabic has its own comma (،), semicolon (؛) and question mark (؟).
R064
Which digits to use is a product decision, and either can be right. The mistake is not deciding, because then Intl decides for you, and its answer depends on the region in the locale:
const price = 1499.5;
new Intl.NumberFormat("ar-SA").format(price); // "١٬٤٩٩٫٥"
new Intl.NumberFormat("ar-AE").format(price); // "1,499.5"
// Decide once, then pass the decision everywhere
new Intl.NumberFormat("ar-SA", { numberingSystem: "latn" }).format(price); // "1,499.5"Dates work the same way. Pass numberingSystem to Intl.DateTimeFormat as well, and calendar too ("gregory" or "islamic-umalqura"), so the output never rests on a default.
Forms#
Forms mix both directions by nature, and they are where users give up when something is wrong.
- Free-text fields such as name, address and message follow the page and run right to left.
R023For a field that may receive either language, usedir="auto". - Machine formats such as email addresses, URLs and phone numbers run left to right, even in Arabic. The HTML standard makes
type="tel"inputs left to right by default, buttype="email"andtype="url"inputs inherit the page direction, so setdir="ltr"on them yourself.R070 - Select menus run right to left, with the arrow on the left.
R072 - Labels, hints and errors are translated.
R071 - Identity fields carry autocomplete tokens such as
name,email,telandstreet-address, so the browser can fill them.R073
<label for="name">الاسم الكامل</label>
<input id="name" name="name" autocomplete="name">
<label for="email">البريد الإلكتروني</label>
<input id="email" name="email" type="email" dir="ltr" autocomplete="email">
<label for="phone">رقم الجوال</label>
<input id="phone" name="phone" type="tel" dir="ltr" autocomplete="tel">Keyboard and screen readers#
- Tab order. Press Tab through each page. Focus should move right to left, in the order the reader sees. A layout faked with
flex-direction: row-reverselooks right but sends focus the other way; setdirand let flexbox mirror itself.R081 - Language changes. Screen readers pick a voice from
lang. English inside Arabic withoutlang="en"is read by the Arabic voice, and usually badly.R080 - Directions in the copy. "Swipe left" or "use the arrow on the right" is wrong once the layout mirrors. Say next and previous instead.
R082
What a scanner cannot see#
Automated checks read the page a browser renders. Some problems need a person:
- Signed-in pages. A Ritla scan sees what a signed-out visitor sees, so dashboards and account pages need a manual pass.
- Interaction states. Open menus, dialogs, toasts, and errors that appear only after a click or a failed submit.
- Text inside images and canvas. Chart labels drawn on a canvas and text baked into images are pixels, not text. Check that they are translated.
- Translation quality. Whether the Arabic is correct, natural and consistent in tone takes a fluent reader.
- Everything outside the page. Emails, text messages, PDFs, invoices and push notifications.
Make it part of every release#
RTL bugs come back. New components get built left to right first, and nobody notices because nobody on the team uses the Arabic version every day. A checklist run once before launch does not stay true for long.
- Run this checklist on the pages that matter before launch.
- Automate what can be automated. Scan a page for free to get a score and its top issues at desktop and mobile widths. A full report covers every page in the crawl at desktop, tablet and mobile widths, with a screenshot, the selector and a fix for each finding.
- Scan again after each release. On the Team plan, each scan is compared with the one before it, so new problems stand apart from old ones.
- Keep the manual pass for what a scanner cannot see.
Every check named in this guide has its own page, with what it detects and how to fix it: see all of Ritla's checks.
FAQ
What is RTL testing?
RTL testing checks that an interface in a right-to-left language, such as Arabic, Hebrew, Persian or Urdu, displays and behaves correctly: the page direction, the order of mixed text, the mirrored layout, the typography, number and date formats, and forms.
Is dir="rtl" enough to support Arabic?
No. It sets the base direction, which mirrors the flow of text and the order of flexbox and grid items. It does not change CSS written with physical properties, fixed widths, icons, number formats, or the order of mixed-direction text inside a sentence. Those still need checking.
Can I test an Arabic website without reading Arabic?
Mostly, yes. Direction, mirroring, overflow, clipped text, fonts, digits and untranslated Latin text can all be seen without reading the language. Whether the Arabic itself is correct, and whether the copy mentions left or right, takes someone who reads it.
Should an Arabic website use Western or Arabic-Indic digits?
Either can be right, depending on the market and the brand. What reads as a bug is mixing them on the same screen. Pick one, then pass it explicitly to every Intl call so the browser's defaults cannot pick for you.
Which pages should I test first?
The ones where a bug costs the most: sign-up, checkout and payment, pricing, and any form people use to reach you. After those, the templates the most pages share.
Checks in this guide
- R001document direction missing, contradicted, or declared only in CSS
- R002html[lang] missing, non-Arabic, or contradicting detected content
- R004<title> or meta description majority-Latin on an Arabic page
- R005OpenGraph/Twitter metadata untranslated or og:locale missing/invalid
- R003hreflang alternate set lacks any Arabic entry on an Arabic page
- R006Canonical URL points at a different-locale variant
Show every check in this guideShow fewer
- R010Visible Latin-script body text above thresholds on an Arabic page
- R011Untranslated interactive labels (buttons, nav/footer links, menu items, tabs)
- R012Untranslated attribute text (placeholder, alt, title, aria-label, button value)
- R071Untranslated form labels / validation / help text
- R080Intentional foreign-language runs missing a lang attribute
- R021Unisolated bidi hazards: phones, emails, Latin tokens inside Arabic text
- R020Forced-LTR container, or a bidi override, over Arabic content
- R022text-align: left explicitly set on majority-Arabic text
- R024Punctuation rendering at the wrong visual end of Arabic text
- R025Directional icon pointing against the sequence its control advances
- R030Direction-sensitive physical properties without an RTL override
- R031float: left/right on content elements in RTL context without override
- R032Direction-sensitive shadow x-offsets without an RTL counterpart
- R033translateX moves the element off-screen in RTL
- R040Document-level horizontal overflow
- R041Visible text-bearing elements overlapping
- R042Clipped text: content wider than its box with overflow hidden and no ellipsis
- R043Fixed-pixel-width container whose Arabic text exceeds the space
- R050Arabic text falling back past the declared font family (measured, declared, or a webfont that failed to load)
- R051letter-spacing applied to Arabic text (breaks the connected script)
- R052line-height below 1.5 on Arabic body text
- R053Arabic body text below 14px
- R054Italic/oblique applied to Arabic text
- R055Latin display styling (uppercase + tracking) applied to Arabic
- R061Arabic-Indic and Western digits mixed in the same context
- R060English month/day names or MM/DD/YYYY dates in Arabic text
- R062Foreign currency formatting in a SAR/AED market context
- R063Percent/unit ordering inconsistencies (%50 vs 50%)
- R064Latin punctuation, or wrong-script letterforms, inside Arabic text
- R023Text inputs rendered LTR where Arabic input is expected
- R070tel/email/url inputs rendered RTL (these must stay LTR)
- R072<select> rendered LTR with Arabic options
- R073Identity fields missing autocomplete tokens
- R081flex row-reverse faking RTL instead of dir (tab order fights visual order)
- R082UI text referencing physical arrow directions