RTL testing: the complete guide
Learn how to find, reproduce and fix direction, bidi text, layout, form, formatting, typography and keyboard failures in Arabic interfaces.
Guide16 min read
An Arabic route can look mirrored and still be wrong. The page shell may run right to left while an order ID reverses around a hyphen, a phone field accepts digits from the wrong edge, an icon points backward, or the keyboard visits controls in an order that no longer matches the screen.
RTL testing has to separate those failures. They do not share one cause, and a global CSS flip cannot repair them all. Some come from document semantics, some from the Unicode Bidirectional Algorithm, some from physical CSS, and some from a component whose visual order no longer agrees with its DOM order.
The useful question is not, "Does the Arabic page look RTL?" It is, "Which layer owns this behavior, and what evidence shows that layer is correct?" This guide follows that question from the root element down to individual values and controls.
What RTL testing has to prove#
A reliable test covers four different kinds of direction:
- Document direction: The Arabic document declares
dir="rtl", so direction is available to the browser, components, selectors and assistive technology. - Text direction: Arabic, Latin text, digits and punctuation form readable bidirectional runs. Embedded values are isolated when their surrounding text could change their order.
- Layout direction: Start and end positions follow the writing direction without moving genuinely physical content such as a map compass or media playback timeline.
- Interaction order: Focus, reading and task order remain logical after the visual layout changes.
Do not collapse these into a single screenshot assertion. A screenshot can reveal a clipped label but cannot prove that the root has the correct dir attribute. A DOM assertion can confirm dir="rtl" but cannot tell whether a translated button overflows at 320 pixels. Automated checks and human review answer different questions.
Use a real Arabic build, not an English route flipped in DevTools. Real content changes word length, line breaks, glyph metrics, number formatting and fallback fonts. The English flip is still a quick way to locate physical CSS: set dir="rtl" on its html element rather than adding direction: rtl, so that styles written against the dir attribute or :dir(rtl) apply as they will in production. Treat it as a diagnostic stress test, not release evidence.
The browser behaviour described on this page was measured in Chromium 151. Firefox and Safari were not checked, so repeat the tests in the browsers your users have.
Set direction in the document, not only in CSS#
The root of an Arabic document should normally declare both language and direction:
<html lang="ar" dir="rtl">lang and dir answer different questions. lang="ar" identifies the content language. It does not make the document RTL. The HTML dir attribute establishes base direction and is inherited by descendants that do not declare their own direction.
Problem
<html lang="ar">html {
direction: rtl;
}This may render from right to left, but it is the wrong ownership model. Direction is part of the content's semantics. It should survive missing styles, and components should be able to query it through direction-aware selectors. In Chromium, an element styled only with direction: rtl does not match :dir(rtl), because that pseudo-class follows document directionality rather than the CSS declaration. MDN likewise recommends the attribute over the direction property where possible.
Better
<html lang="ar" dir="rtl">:dir(rtl) .next-icon {
transform: scaleX(-1);
}scaleX(-1) mirrors the icon. A 180-degree rotation looks the same on a symmetric chevron, but it also turns the icon upside down, which shows on anything asymmetric, such as a reply arrow.
Test the rendered route, not just the template. Client-side navigation, locale middleware and hydration can leave the initial attributes in place after the locale changes. In DevTools, inspect the live html element, then inspect a nested component and confirm its computed direction. R001 reports an Arabic page whose root direction is missing or left to right; R002 covers a missing or contradictory language declaration.
Direction can be scoped. An English quotation inside an Arabic article may use dir="ltr", and a user-generated comment may use dir="auto". What should not happen is a forced-LTR wrapper around an Arabic component merely to make one embedded value behave. R020 flags that broader mismatch. Isolate the value instead.
Mixed-direction text is not string reversal#
Browsers do not reverse an Arabic string. They apply the Unicode Bidirectional Algorithm to characters with strong, weak and neutral directional classes, then arrange the resulting runs for display. Arabic letters are strong RTL characters. Latin letters are strong LTR characters. Digits, spaces, hyphens, plus signs and punctuation participate under different rules and can take their visual position from context.
That distinction explains why two values that look similar in source behave differently. In a right-to-left paragraph after Arabic text, 2026-0042 displays as 0042-2026, while INV-2026-0042 stays intact. The Latin prefix creates a strong LTR run. A slash-separated date such as 12/05/2026 also stays together, because the separators are resolved differently from hyphens. The browser is following the bidi rules, not inconsistently guessing which strings are IDs.
Isolate values, do not reorder them#
Problem
<p dir="rtl">رقم الطلب 2026-0042</p>The author intended the order ID to display as 2026-0042. It displays as 0042-2026 in this context.
A fragile fix is to reverse the value in application code for Arabic. That corrupts copying, searching and accessible text, and it can reverse again when the context changes.
Better when the value direction is unknown
<p dir="rtl">رقم الطلب <bdi>2026-0042</bdi></p>Better when the product defines the value as LTR
<p dir="rtl">رقم الطلب <span dir="ltr">2026-0042</span></p><bdi> isolates its contents and, by default, determines their base direction from the first strong character. dir="auto" on an existing semantic element provides the same useful pattern for unknown content. The W3C guidance on inline bidi markup recommends tightly wrapping opposite-direction phrases so their punctuation and numbers do not leak into surrounding runs.
There is an edge case: values made only of digits and punctuation have no strong letter. <bdi> and dir="auto" resolve values such as 2026-0042 and +966 55 123 4567 to LTR, which is useful here. Do not wrap an Arabic label and its value together in one dir="auto" element. The Arabic label becomes the first strong content and makes the whole wrapper RTL.
<!-- Too broad: the label determines the direction of the wrapper. -->
<span dir="auto">رقم الجوال +966 55 123 4567</span>
<!-- The value is isolated from the Arabic sentence. -->
<span dir="rtl">رقم الجوال <bdi>+966 55 123 4567</bdi></span>Test punctuation, signs and dynamic boundaries#
Do not test bidi behavior with one safe token. Build a small matrix from values the product actually renders:
| Value type | Useful test value | What to inspect |
|---|---|---|
| Numeric order ID | 2026-0042 | Hyphen-separated groups remain in product order |
| Latin-prefixed ID | INV-2026-0042 | Prefix and number stay one readable run |
| Phone number | +966 55 123 4567 | Plus sign and digit groups keep their positions |
support@example.com | Address, punctuation and adjacent Arabic text remain separate | |
| Product name | C++ | Plus signs stay after the letter |
| Percentage | 50% | Sign placement follows the product's locale convention |
| Negative amount | -5 | The minus sign stays with the number, on the side your number format puts it |
Place each value at the start, middle and end of an Arabic sentence. Follow it with a comma, closing parenthesis and another number. Test values loaded after initial render, since a placeholder may be safe while the API response introduces the failure. R021 reports unisolated phones, emails and Latin tokens inside Arabic text. It is a useful backstop, but product-specific IDs still deserve explicit test cases.
Use <bdo dir="ltr"> only when an override is genuinely required. Unlike isolation, an override forces characters into the declared order. One case worth testing is a phone number written with Arabic-Indic digits and spaces. The digit groups can resolve right to left even inside <bdi> or dir="ltr"; <bdo dir="ltr"> keeps the authored group order. That is an override with a specific reason, not a general bidi fix.
Replace physical layout assumptions with logical ones#
An RTL layout is not a mirrored bitmap. It is a layout whose inline start is on the right. CSS logical properties express that relationship directly. margin-inline-start, for example, maps to the left margin in LTR horizontal writing and the right margin in RTL horizontal writing.
Problem
.field-icon {
position: absolute;
right: 12px;
}
.field-input {
padding-right: 40px;
}This works only while the icon belongs on the physical right. An RTL override can patch it, but every new physical declaration creates another branch to maintain.
Better
.field-icon {
position: absolute;
inset-inline-end: 12px;
}
.field-input {
padding-inline-end: 40px;
}Apply the same reasoning to margins, padding, borders, offsets, corner radii, text alignment and floats. text-align: start is usually the right abstraction for content that should align with its writing direction. left and right are still correct when the requirement is physical. A badge pinned to the east side of a map, a video scrubber tied to media time, or a chart axis with a fixed mathematical meaning should not move merely because the UI language changes.
Flexbox and grid already respond to direction. Do not add row-reverse simply to make an RTL row appear mirrored. On an RTL container, the inline main axis already starts on the right for the default row flow. Adding row-reverse can cancel the intended visual change while leaving the DOM and tab order untouched. R081 reports this pattern when row-reverse is used to imitate RTL.
Transforms need separate review because translateX(24px) remains a movement toward the physical right. If the motion means "enter from inline end," make the sign direction-aware. Shadows also retain physical x-offsets. Sometimes that is correct because the light source is physical; sometimes a callout shadow or slide animation was meant to follow the component. R032 and R033 cover these easily missed assumptions, while R030 and R031 cover broader physical properties and floats.
Test layout at the component boundary. Switch the same story, route or fixture between dir="ltr" and dir="rtl" without reloading unrelated state. Compare the header, breadcrumb, form row, card actions, pagination and toast placement. At each width, inspect both the component and the full page, because a child can fit locally while pushing the document into horizontal overflow.
Mirror meaning, not every icon#
Directional icons fall into three groups:
- Reading or navigation direction: Back, forward, next, previous, undo and redo often need an RTL counterpart or a horizontal flip.
- Physical direction: Compass arrows, map directions and controls tied to a physical side keep their real-world orientation.
- Shape or brand: Logos, phone handsets, check marks and most object icons usually stay unchanged unless the design system defines an RTL variant.
Name icons by meaning, such as arrow-inline-start, previous or reply, instead of baking left into the component API. An icon called chevron-left invites consumers to choose by appearance, then fails when the same action moves to the other side. R025 reports icons named for a physical direction that are never mirrored, and R082 catches interface copy that tells users to use a physical arrow direction.
Do not judge an arrow alone. Verify the action, label and placement together. A back arrow should lead to the previous screen, sit at the expected inline-start edge, and expose a translated accessible name. Then activate it with mouse, touch and keyboard. Mirroring the glyph while leaving its hit area or tooltip behind is still a defect.
Forms need field-level direction decisions#
Setting the form to RTL is a sensible default for Arabic labels, help text, selects and free-text input. It is not a sensible default for every value. Phone numbers, email addresses, URLs, account codes and many identifiers are entered and edited as LTR sequences even when their labels are Arabic.
<form dir="rtl" lang="ar">
<label for="name">الاسم الكامل</label>
<input id="name" name="name" type="text">
<label for="phone">رقم الجوال</label>
<input id="phone" name="phone" type="tel" dir="ltr" autocomplete="tel">
<label for="email">البريد الإلكتروني</label>
<input id="email" name="email" type="email" dir="ltr" autocomplete="email">
</form>type="tel" is LTR by default. Text, search, email, URL and number inputs inherit the page direction unless they declare one. That means using the semantically correct input type is not enough to settle every field's editing direction. R070 reports telephone, email and URL controls rendered RTL, while R023 covers the opposite problem, text fields rendered LTR where Arabic input is expected.
For user-generated prose whose direction is not known in advance, dir="auto" can be appropriate. It is a first-strong-character heuristic, not language detection. A comment that begins with an English product name and continues in Arabic can resolve LTR. If the application knows the content language, an explicit direction is safer. If the server needs to preserve the user's chosen direction, the HTML dirname attribute can submit it alongside the value.
Test editing, not only the empty control. Paste a complete value, move the caret through punctuation, select a middle range, delete one character, and type at both ends. Check placeholder alignment, prefix icons, reveal-password buttons, clear buttons and validation messages. Native validation text is not controlled by the page's lang; Chromium running in English can show an English required-field message on an Arabic page. If the product requires Arabic validation, render and localize the product's own message instead of assuming the browser will follow the document language. R071 reports untranslated labels, validation and help text.
For a <select>, check the closed control and the open option list. Confirm Arabic option text, arrow placement, focus ring and selected value at the supported browser and operating-system combinations. Native control rendering varies, so reproduce uncertain behavior in the team's own browser matrix rather than assuming one engine's result. R072 reports a left-to-right select containing Arabic options.
Format values deliberately#
Direction does not decide locale formatting. The Arabic routes for Saudi Arabia and the UAE may require different currency, calendar or numbering choices, and the output of Intl depends on locale data supplied by the runtime.
The defaults observed for ar-SA and ar-EG use Arabic-Indic digits, while ar and ar-AE use Western digits; all four use the Gregorian calendar. These defaults can change with locale data and browser versions. If a business requirement depends on the numbering system or calendar, pass it explicitly. The Intl.DateTimeFormat options support both.
const money = new Intl.NumberFormat("ar-SA", {
style: "currency",
currency: "SAR",
numberingSystem: "arab",
});
const date = new Intl.DateTimeFormat("ar-SA", {
calendar: "gregory",
numberingSystem: "arab",
dateStyle: "medium",
});The code above is an explicit product decision, not a universal Arabic default. Some products deliberately use Western digits. Some audiences expect a different calendar. Align the fixture with the market requirement, then test every place that formats the same domain value: list, detail view, export preview, receipt and validation message.
Avoid assembling formatted text with string concatenation. Locale formatters can supply spacing and invisible bidi controls that make the complete output behave correctly. A test that strips those characters or compares only visible glyphs can miss a regression. Assert the domain choice, such as currency and numbering system, then visually verify the rendered result in its Arabic sentence.
Check dates with unambiguous test data such as a day above 12, prices with decimals and thousands separators, negative amounts, percentages, and zero. R060 reports English month or day names and US-ordered dates in Arabic text. R061, R062 and R063 cover mixed digit systems, foreign currency formatting and inconsistent percent or unit ordering.
Arabic typography changes the size of the interface#
Arabic letters join and reshape according to context. Positive letter spacing designed for uppercase Latin labels can break that connected texture. A shared style such as text-transform: uppercase; letter-spacing: .08em should not flow unchanged into an Arabic locale. R051 reports positive letter spacing on Arabic text, and R055 reports Latin display styling applied to Arabic.
Font fallback is more than a visual mismatch. If the declared web font lacks Arabic glyphs, a fallback face may have different width, line height and baseline metrics. The button that fit in a design screenshot can clip only after the fallback loads. Use an Arabic-capable family in the declared stack, wait for fonts before taking visual snapshots where practical, and inspect the rendered font for a failing node in DevTools. R050 reports measured fallback or a stack with no Arabic-capable family.
Arabic body copy also needs enough vertical room for marks and letterforms. Test real headings and multi-line validation messages rather than adjusting line height from an isolated Latin specimen. Zoom the page to 200 percent and check whether marks, focus outlines or adjacent lines are clipped. Fixed heights are especially risky for controls whose labels may wrap. R042 and R043 cover clipped text and fixed-width containers exceeded by Arabic content; R052 and R053 cover cramped line height and undersized body text.
Avoid treating synthetic italics as an automatic emphasis style for Arabic. Use the typeface and emphasis conventions approved for the Arabic design. R054 reports italic or oblique styling on Arabic text so it can be reviewed rather than inherited unnoticed.
Find overflow and overlap at hostile widths#
RTL failures often appear on the left edge, where teams are less accustomed to looking for them. A physically positioned child, long untranslated token or translated label can increase the page's scroll width without looking obviously broken at a wide desktop size.
Test at the minimum supported viewport, at common mobile and desktop widths, and at intermediate widths where flex items wrap. At each size:
- Scroll to both horizontal extremes. Any unexpected movement is evidence of document-level overflow.
- Compare
scrollWidthwithclientWidthfor the document and suspicious containers. - Inspect fixed, sticky and absolutely positioned elements near the left edge.
- Open menus, dialogs, tooltips, validation summaries and toasts. Closed-state screenshots do not exercise their bounding boxes.
- Replace fixture text with the longest real Arabic labels and values the interface supports.
- Repeat after web fonts and asynchronous data have loaded.
Do not hide the symptom with overflow-x: hidden on the page. That can remove the scrollbar while leaving content unreachable. Locate the element whose bounding box escapes the viewport and fix its sizing, offset or wrapping rule. R040 reports document-level horizontal overflow, R041 reports visible text-bearing elements that overlap, and R042 reports clipped text hidden without an ellipsis.
Keep visual order and interaction order coherent#
CSS can move controls without changing the DOM. The browser's default tab sequence follows DOM order, and screen-reader reading order is also grounded in that structure. The WAI keyboard guidance recommends keeping focus and reading order logical and predictable.
This does not mean focus must always travel right to left across every row. It means the order must preserve the task and agree with the relationships the layout communicates. A search field followed by a submit button should not receive focus in the opposite task order merely because flex items were visually reversed.
Start with logical DOM order, apply dir, and let direction-aware layout place items. Avoid positive tabindex values as a repair. They create a second ordering system that becomes difficult to maintain when controls are conditional or responsive.
Test the page without a pointer. Begin at the browser chrome, press Tab through navigation and the main task, use arrow keys inside composite widgets, and verify that focus remains visible. Open and close overlays, then confirm focus returns to the triggering control. Record the visual position of each focused element. R081 is particularly relevant when a reversed flex row makes that path fight the screen.
A practical RTL testing workflow#
Start with a route that represents a complete task, such as checkout, account creation or order lookup. A shell-only page misses the combinations that expose bidi, validation and overlay bugs.
1. Confirm the semantic baseline#
Inspect the live root for lang="ar" and dir="rtl". Navigate between locales without a full reload and check again. Inspect a deeply nested component to confirm its computed direction, then search for broad dir="ltr" or direction: ltr rules that may cancel inheritance.
2. Exercise mixed content#
Load realistic phone numbers, numeric and Latin-prefixed IDs, emails, URLs, percentages and prices next to Arabic labels. Check source value, visual value and copied value. If one fails, reduce it to the smallest sentence that reproduces the issue and add isolation around the value, not around the whole component.
3. Stress layout and content#
Run the task at narrow, medium and wide viewports. Increase text size or browser zoom, load the longest supported strings, and open every conditional surface. Watch the left viewport edge and measure overflow rather than relying on a quick visual scan.
4. Operate every control#
Type and edit Arabic text, a phone number, an email and a code. Trigger required, format and server errors. Use only the keyboard through the same task. Confirm labels, help text, names and errors are Arabic, while inherently LTR values remain editable in their expected order.
5. Compare with LTR by behavior#
Compare the Arabic and LTR routes side by side, but do not demand geometric symmetry. Compare relationships: inline-start navigation remains at inline start, labels stay attached to their fields, next and previous still mean the same actions, and content that should remain physical does not move.
6. Automate stable invariants#
Good automated assertions include the root attributes, absence of unexpected horizontal overflow, direction of known field types, computed logical spacing, visible translated labels and a predictable focus sequence. Add visual snapshots for high-value components at more than one width. Keep manual review for language quality, whether a value is readable to an Arabic user, and whether mirroring preserves meaning.
Ritla can run the relevant Arabic QA checks across direction, bidi hazards, physical CSS, overflow, typography, formats, forms and accessibility. Use the findings as specific leads, then reproduce each one in the product state where it matters. For a compact release pass, use the RTL testing checklist for Arabic websites.
Debug the owning layer#
When a failure appears, reduce it before adding an override:
| Symptom | Inspect first | Likely repair |
|---|---|---|
| Whole Arabic page aligns left | Live html attributes | Set lang="ar" and dir="rtl" at the root |
| One ID or phone number reorders | Element boundary and bidi isolation | Wrap only the value in <bdi> or an explicit dir |
| Spacing is wrong only in RTL | Physical CSS and transforms | Replace direction-relative physical rules with logical ones |
| Arrow looks wrong | Meaning of the action | Use a semantic RTL icon variant where meaning reverses |
| Caret behaves unexpectedly | Input type, dir and actual value | Set direction for that field's data, then test editing |
| Text clips after translation | Font, fixed dimensions and overflow | Use an Arabic-capable font and allow content-driven sizing |
| Tab order jumps across the screen | DOM order, flex reversal and tabindex | Restore logical DOM order and remove ordering patches |
This prevents a common cycle: a bidi problem is patched with flex reversal, the flex reversal creates a focus problem, and positive tabindex is added to hide that. Fixing the layer that owns the behavior produces less code and a test that explains what must remain true.
Checks in this guide
- R001document direction missing, contradicted, or declared only in CSS
- R002html[lang] missing, non-Arabic, or contradicting detected content
- R020Forced-LTR container, or a bidi override, over Arabic content
- R021Unisolated bidi hazards: phones, emails, Latin tokens inside Arabic text
- R081flex row-reverse faking RTL instead of dir (tab order fights visual order)
- R032Direction-sensitive shadow x-offsets without an RTL counterpart
Show every check in this guideShow fewer
- R033translateX moves the element off-screen in RTL
- R030Direction-sensitive physical properties without an RTL override
- R031float: left/right on content elements in RTL context without override
- R025Directional icon pointing against the sequence its control advances
- R082UI text referencing physical arrow directions
- R070tel/email/url inputs rendered RTL (these must stay LTR)
- R023Text inputs rendered LTR where Arabic input is expected
- R071Untranslated form labels / validation / help text
- R072<select> rendered LTR with Arabic options
- R060English month/day names or MM/DD/YYYY dates in Arabic text
- R061Arabic-Indic and Western digits mixed in the same context
- R062Foreign currency formatting in a SAR/AED market context
- R063Percent/unit ordering inconsistencies (%50 vs 50%)
- R051letter-spacing applied to Arabic text (breaks the connected script)
- R055Latin display styling (uppercase + tracking) applied to Arabic
- R050Arabic text falling back past the declared font family (measured, declared, or a webfont that failed to load)
- R042Clipped text: content wider than its box with overflow hidden and no ellipsis
- R043Fixed-pixel-width container whose Arabic text exceeds the space
- R052line-height below 1.5 on Arabic body text
- R053Arabic body text below 14px
- R054Italic/oblique applied to Arabic text
- R040Document-level horizontal overflow
- R041Visible text-bearing elements overlapping