Common RTL bugs in production websites

See the RTL bugs that repeatedly reach production, why browsers render them that way, how to reproduce each failure and what to fix.

Guide15 min read

The RTL bugs that reach production are rarely caused by one missing mirror switch. They come from ordinary LTR habits: setting direction in CSS instead of the document, wrapping a dynamic value in a plain span, placing components with left, reversing a flex row, or assuming scrollLeft behaves the same in both directions.

Those habits survive English testing because English supplies the direction they expect. Arabic content changes the base direction, mixes strong and weak character types, moves inline start to the right and sends overflow toward an edge teams may not inspect.

This guide collects the recurring failure patterns. Each section shows the symptom, the browser behavior underneath it, the common wrong repair, the better implementation and a test that can keep it from returning.

1. The page looks RTL but has no RTL document direction#

The Arabic route appears right-aligned, so the team assumes document setup is correct. The live root actually has lang="ar" with no dir, while a stylesheet supplies the visual direction.

Problem

html
<html lang="ar">
css
html {
  direction: rtl;
}

lang identifies language. It does not set direction. In Chromium 151, an Arabic page with lang="ar" and no dir has a computed direction of LTR unless CSS changes it. CSS can make the page render RTL, but it is not the same document contract. An element that receives direction only from CSS does not match :dir(rtl) in the measured browser.

Better

html
<html lang="ar" dir="rtl">

The HTML dir attribute establishes base direction and lets descendants inherit it. Component selectors and browser behavior can then respond to the actual document direction.

Inspect the live html element after direct entry, hydration and client-side locale switching. It is common for visible copy to change while root attributes remain from the previous locale. R001 reports a missing or LTR root direction on an Arabic page. R002 reports a missing or contradictory language declaration.

Do not put dir="rtl" on every child as compensation. Set the root once, then use local direction only for content that genuinely differs.

2. A plain span fails to isolate a booking reference#

A travel site shows a booking reference after an Arabic label. Someone notices it reordering and wraps it in a span. Nothing changes because a plain inline element does not create a directional boundary.

Still broken

html
<p dir="rtl">رقم الحجز <span>7730-5512</span></p>

In Chromium 151, the reference displays as 5512-7730, exactly as it did without the span. A plain span groups DOM nodes, but it isolates no bidi content.

Better when direction is unknown

html
<p dir="rtl">رقم الحجز <bdi>7730-5512</bdi></p>

Better when the value is defined as LTR

html
<p dir="rtl">رقم الحجز <span dir="ltr">7730-5512</span></p>

The browser arranges the line with the Unicode Bidirectional Algorithm. Arabic letters, Latin letters, digits and hyphens have different directional types. <bdi> isolates the value and determines its base direction from its content. The W3C inline bidi guidance recommends tightly wrapping inserted opposite-direction phrases.

Do not reverse the source value. The reference that displays as 5512-7730 still copies as 7730-5512; reversing the stored string would corrupt the data. R021 reports unisolated phones, emails and Latin tokens inside Arabic text.

Test the reference formats your product actually issues. A Latin-prefixed form such as TCK-5093-22 stays intact after Arabic in the measured browser, so a fixture built only from prefixed references never reproduces the bug a digit-leading one exposes.

3. A Latin name pulls a nearby number into the wrong run#

Unisolated names can change the apparent relationship between text and numbers around them. A file-sharing notice is enough to show it:

html
<p dir="rtl">رفعت Lina 4 ملفات</p>

In Chromium 151, the 4 joins the Latin name's left-to-right run and displays between the Arabic verb and Lina, away from the word it counts. The browser is not randomly moving the number. The name created a strong LTR run, and the number resolved with it.

Better

html
<p dir="rtl">رفعت <bdi>Lina</bdi> 4 ملفات</p>

Wrap user-generated or unknown-direction names individually. Do not wrap the full sentence in dir="auto"; the sentence contains content from several directional sources, so one first-strong decision cannot describe all of it. W3C's strings and bidi examples show why inserted strings need their own isolation boundaries.

Test an Arabic name, a Latin name and a digit-leading user name next to counts and punctuation. Add two adjacent user names to expose boundaries that a single fixture misses.

4. English punctuation appears on the wrong visual end#

An English sentence inserted into an RTL paragraph may show its final punctuation at the left edge of the Latin text. A common source is an untranslated error string passed straight from an API. In Chromium 151, Try again. in that context displays as .Try again.

The punctuation is neutral at the run boundary. Without an LTR boundary, it takes its position from surrounding bidi context.

Problem

html
<p dir="rtl">حدث خطأ: Try again.</p>

Better

html
<p dir="rtl">
  حدث خطأ: <span lang="en" dir="ltr">Try again.</span>
</p>

Keep the final punctuation inside the isolated foreign-language span. lang="en" identifies the language for text processing; dir="ltr" handles direction. They solve different problems.

The wrong repair is adding Arabic punctuation outside the English phrase merely to move the visible mark. That changes the content and fails when the phrase or surrounding text changes. R024 reports punctuation at the wrong visual end of Arabic text, and R080 reports intentional foreign-language runs without language markup.

Test complete phrases with their comma, full stop, exclamation mark or closing parenthesis. Review the browser result rather than a translation-system preview with no surrounding RTL context.

5. Physical CSS pins components to the English side#

The most visible production defects often come from physical properties that describe a logical relationship.

Problem

css
.toast-close {
  position: absolute;
  right: 8px;
}

.toast-body {
  padding-right: 32px;
  text-align: left;
}

In RTL the close button stays on the physical right, where the Arabic message begins, instead of at the end of the line. The message itself stays pinned to the left edge, leaving a ragged right margin in a script that starts there.

Better for an inline-end close button

css
.toast-close {
  position: absolute;
  inset-inline-end: 8px;
}

.toast-body {
  padding-inline-end: 32px;
  text-align: start;
}

CSS logical properties map inline start and end according to writing direction. In Chromium 151, the first item of a flex row, grid or table row begins at the right edge under RTL, and margin-inline-start maps to the right margin.

Physical values do not automatically mirror. margin-left, left, translateX() and SVG drawings keep their physical meaning. That is sometimes correct. A compass, chart axis or media timeline should not flip only because the page language changes.

Audit margins, padding, offsets, borders, corners, floats, shadows, text indentation and transforms. R022, R030, R031, R032 and R033 report common physical assumptions. Fix shared tokens and components before adding page-level overrides.

6. row-reverse is used to fake RTL#

An English component is visually reversed with flex-direction: row-reverse, and the team calls it RTL support. This changes the flex main axis, not the document direction or text behavior.

In an LTR page, row-reverse puts the first item at the right edge. In an RTL page, the same declaration reverses the RTL axis again and lays items left to right in DOM order in Chromium 151. The visual result can therefore cancel the direction the component was meant to support.

The default flex row already follows the container's direction. Start with semantic document direction and a logical DOM sequence:

html
<nav dir="rtl" class="steps">
  <a href="/booking/dates">التواريخ</a>
  <a href="/booking/guests">الضيوف</a>
  <a href="/booking/confirm">التأكيد</a>
</nav>
css
.steps {
  display: flex;
  flex-direction: row;
}

Reversal and CSS order affect visual presentation without necessarily changing source, speech or tab order. MDN warns that row-reverse can disconnect visual and DOM order. R081 reports flex reversal used to imitate RTL.

Test with Tab, not only a screenshot. Record the visual position of each focused control. Do not repair a mismatch with positive tabindex; it creates another ordering system that becomes fragile when controls are conditional.

7. Icons mirror by shape instead of meaning#

Two opposite bugs appear here: every icon is flipped, or no icon is flipped.

Back, forward, next, previous, undo and reply often follow reading or navigation direction. Logos, check marks, compass directions, phone handsets and many media controls usually retain their intrinsic or physical meaning.

Name assets and component props by action, such as previous or inline-start, instead of arrow-left. A physical name encourages feature code to choose a shape rather than express intent. R025 reports a directional icon that points against the way its control moves, such as a "next" arrow pointing back toward the start, and flags unmirrored icons named for a side as worth a look. R082 reports interface text that tells users to use a physical arrow direction.

SVG content adds another trap. An SVG drawing does not mirror merely because the HTML page is RTL. Text direction inside SVG can be set, but paths and transforms remain authored geometry. Supply an RTL variant or a deliberate transform when the symbol's meaning reverses.

Test the control as a unit: icon, label, tooltip, hit area, action and accessible name. A mirrored arrow whose tooltip still says "left" has not been fixed.

8. Overflow is checked on the wrong edge#

In LTR testing, teams learn to look for content escaping the right edge. On an RTL page, the dangerous edge often moves left.

In Chromium 151, content positioned beyond the left edge of an RTL page adds horizontal scrollable width. Equivalent content beyond the right edge does not. The LTR page behaves in the opposite direction. An absolute child at left: -300px can therefore create 300 pixels of sideways scroll only in the RTL build.

At each supported width:

  1. Compare document scrollWidth and clientWidth.
  2. Scroll to both horizontal extremes.
  3. Inspect fixed, sticky and absolutely positioned elements near the left edge.
  4. Open dialogs, menus, tooltips and toasts.
  5. Wait for fonts and dynamic content.
  6. Inspect the bounding box of the element that escapes.

Do not apply overflow-x: hidden to the root as the first fix. It removes the scrollbar while content may remain unreachable. R040, R041, R042 and R043 report document overflow, text overlap, clipping and fixed-width containers exceeded by Arabic text.

The edge case is intentional off-canvas UI. If a drawer begins outside the viewport, its closed transform must avoid expanding the page's scrollable area in both directions, and its open state must restore a reachable focus path.

An RTL carousel may render correctly but its next and previous controls do nothing or move backward. The code assumes that scrolling toward later content always increases scrollLeft.

For an RTL scroll container whose content grows leftward, scrollLeft is zero at the start and becomes negative toward the end. In Chromium 151, scrollBy({ left: 300 }) does nothing at the initial RTL position because it requests movement past the start; moving toward later content requires the opposite sign.

Fragile

js
scroller.scrollBy({ left: 300, behavior: "smooth" });

Do not scatter sign flips across click handlers. Define navigation in logical terms such as previous and next, then map that intent to scroll behavior for the container's direction. Read the actual element direction in the component, and test both starting and intermediate positions.

Assertions should not assume scrollLeft >= 0. Test which item becomes visible and whether the control reaches the logical start and end. Also test keyboard, touch and wheel input, since button code is only one path through the carousel.

10. dir="auto" makes an empty input jump sides#

dir="auto" is often added to every text field because it adapts to user input. The empty-state behavior is easy to miss.

In Chromium 151, an empty <input dir="auto"> resolves LTR. On an RTL page, its Arabic placeholder is drawn at the left edge. Typing the first Arabic letter changes the field to RTL, so alignment jumps to the right. Latin letters or digits keep it LTR.

That behavior follows the first-strong heuristic. It is useful for unknown-direction user content, but it is not always the right experience for a field whose expected language is known.

Use explicit RTL for Arabic names, addresses or queries when the field is intended for Arabic input. Use LTR for phone, email, URL and code fields. Reserve dir="auto" for genuinely mixed or unknown content, and test empty, placeholder, focused and populated states.

In Chromium 151, type="tel" is LTR by default. Email, URL, number, search and text inputs inherit the page direction unless they set one. R023 reports Arabic text inputs rendered LTR, while R070 reports phone, email and URL fields rendered RTL.

Test editing, not only alignment: paste, select, move the caret and delete around punctuation. The field can look right at rest and still be difficult to correct.

11. Copy tests miss a display-only bidi failure#

A QA script copies the booking reference and confirms the clipboard contains 7730-5512. The test passes even though the screen shows 5512-7730.

That is expected in the measured Chromium behavior. Bidi layout changes visual order, not the underlying character sequence. Selection, copying and the accessibility tree preserve the authored order. A screen-reader assertion can also miss the visual defect.

Use three different assertions when the value matters:

  • Data assertion: The DOM or API contains the correct authored value.
  • Visual assertion: An Arabic reader sees the defined order in context.
  • Interaction assertion: Copying and editing preserve the value.

None substitutes for the others. A visual screenshot alone cannot prove clipboard integrity. A copied-value test cannot prove display order.

When filing the bug, record both strings: authored 7730-5512, displayed 5512-7730. Then reduce the case to a plain RTL paragraph and add isolation. This quickly distinguishes a bidi-boundary failure from a layout or data mutation.

12. Arabic falls back or loses its connected shape#

A button looks correct in English because the declared font supports Latin. Arabic glyphs fall through to a system font with different metrics, and the label clips or shifts after font load.

Inspect the actual rendered font in DevTools. R050 reports measured fallback or a declared stack with no Arabic-capable family. Test the intended web font, slow loading and failure states at narrow widths.

Typography tokens can create another defect. Positive letter spacing applied to uppercase Latin labels may pull connected Arabic letters apart. text-transform: uppercase has no Arabic equivalent, and synthetic italics may not suit the Arabic typeface. R051, R054 and R055 report these styles.

The fix is an Arabic-capable font stack and script-aware typography, not a shorter translation. Test headings, buttons, multiline errors and mixed Arabic-Latin labels. Check line height and fixed component height together because marks or a second line can be clipped even when width is sufficient. R052 and R053 cover cramped line height and undersized body text.

13. Signs and units are treated like ordinary digits#

A team tests a plain price such as 1499, sees the digits remain readable, and assumes every numeric value is safe. Percent signs, minus signs and phone prefixes introduce different bidi behavior.

In Chromium 151, a VAT line such as ضريبة القيمة المضافة 15% shows the rate as %15, while 15% on its own displays as authored. A balance change of -7 shows its minus sign on the right, as 7-, both after Arabic and alone. The same value can therefore look different in a table cell and in a sentence, which reads as an inconsistency even when each instance follows the rules.

Whether %15 is acceptable is a market decision, not a browser accident, so write it down. When the product defines the value as LTR, isolate the complete value rather than moving characters in application data:

html
<p dir="rtl">
  ضريبة القيمة المضافة <span dir="ltr">15%</span>
</p>

Phone numbers are the hardest member of this family: an unisolated number reverses its digit groups and moves its plus sign, and a number written in Arabic-Indic digits can reverse even inside <bdi>. They have their own guide, phone numbers in RTL interfaces.

Test positive, negative and zero values, percentages and units, after an Arabic label and inside a longer sentence. Verify display, copy and editing separately. R021 catches common unisolated phone hazards, while R063 reports percent and unit ordering inconsistencies.

14. Tables and grids are reversed twice#

The page becomes RTL, then a developer manually reverses table columns or grid items because an English screenshot started on the left. The result looks LTR again, or column meaning no longer matches headers and keyboard order.

Direction-aware layout already changes inline start. In Chromium 151, the first item in a flex row, grid and table row sits at the right edge under RTL. Test this default before adding reordering rules.

For a data table, keep header and data cell DOM order aligned by meaning. Applying dir="rtl" to the table can place the first column on the right without rebuilding every row. If a numeric or code column needs LTR text, set direction on the cell content, not on the entire table.

html
<table dir="rtl">
  <tr>
    <th>رقم المطالبة</th>
    <th>تاريخ التقديم</th>
    <th>المبلغ المعتمد</th>
  </tr>
  <tr>
    <td><bdi>88-1402</bdi></td>
    <td>25/09/2026</td>
    <td><bdi>2,350 AED</bdi></td>
  </tr>
</table>

The <bdi> looks redundant in that cell, and on its own it is: in Chromium 151, a bare 88-1402 in an RTL cell displays as authored. It stops being redundant the day the cell gains an Arabic prefix, such as مطالبة 88-1402, which displays the number as 1402-88. Isolating in the cell component protects every future variant of the cell.

Grid placement and CSS order can move items visually while leaving source and tab order untouched. That may be harmless for purely decorative boxes, but it is fragile for sortable headers, row actions or cards whose sequence conveys meaning. MDN's grid accessibility guidance warns that visual reordering does not change underlying source order.

Test header-cell associations, sorting, horizontal scrolling and keyboard focus with real mixed-direction values. Do not judge the table only by whether its first column appears on the right.

15. The test calls correct Arabic order a bug#

Not everything that appears reversed relative to English is broken. Arabic readers expect the paragraph and its runs to follow RTL reading order. A screenshot reviewer who does not know the expected value may "fix" correct browser behavior by reversing DOM order or forcing a whole component LTR.

Write the expected authored value before reviewing the screen. Separate three questions:

  • What is the logical sequence of labels and controls for the task?
  • Which embedded values have a product-defined LTR order?
  • Which visual changes are the correct result of RTL flow?

Ranges are the clearest case. In Chromium 151, opening hours written ساعات العمل 09:00-17:00 display as 17:00-09:00. A reviewer who reads left to right sees closing time first and files a bug. An Arabic reader starts at the right, meets 09:00 first and reads the range correctly. A price range such as 100-200 behaves the same way. The booking reference from section 2 displays through the identical mechanism, but there the reordering is a defect, because a reference is one token whose groups must stay in their issued order. Same browser behavior, opposite verdicts: the expected result comes from what the value means.

Separators matter as well. In the same browser, 25/09/2026, 18:45 and 2,350 stay together after Arabic text, while hyphens and spaces split digit groups. A fixture built only from slash-separated dates therefore gives a false sense of safety.

Script context matters too. The measured behavior applies after Arabic-type letters, including Persian and Urdu, but the same booking reference after a Hebrew label displays as authored. Do not convert one test result into a rule for every RTL script.

The practical test is to ask an Arabic reviewer what the complete line should communicate, while engineering checks known embedded values against their defined order. Record both the source value and the observed display in the bug report. This prevents a correct reading direction from being reported as a defect and makes actual bidi corruption reproducible.

Catch the pattern, not only the page#

When one of these bugs appears, reduce it to the owning layer before adding an override:

SymptomFirst diagnostic
Whole page behaves LTRInspect live root dir and computed direction
One value reordersRemove layout and test it in a plain RTL paragraph
Component sits on the wrong sideCompare physical and logical computed properties
Focus jumpsCompare DOM order with visual order
Page scrolls sidewaysFind the box crossing the left RTL edge
Carousel moves backwardLog direction, logical target and scrollLeft at both ends
Input changes alignmentTest empty, placeholder and first typed character
Arabic clips after loadInspect rendered font and bounding boxes

Fix shared primitives first. One physical spacing token, scrolling helper or input wrapper can create the same failure across many routes. Add a regression fixture at that layer with real Arabic content and both directions.

Every check cited above is a Ritla check, and a free scan of one route shows which of these fifteen patterns it already has. Treat each finding as the start of a minimal reproduction rather than a reason to add a global RTL patch.

Three of these patterns have guides of their own: bidirectional text for sections 2 to 4, 11 and 15, RTL CSS for sections 5 to 7 and 14, and horizontal overflow in RTL for section 8.

Checks in this guide

Show every check in this guideShow fewer

See what your Arabic pages are hiding

Paste a URL. Ritla renders the page on desktop and mobile, runs every check, and shows the top issues with screenshot evidence.