RTL responsive design: what breaks on mobile

Find and fix RTL bugs at narrow widths, including overflow, clipped Arabic labels, rigid action rows and breakpoint regressions.

Guide15 min read

A desktop layout can survive bad RTL CSS through the healing power of empty space. Then the viewport narrows, an Arabic label takes a second line, and the page develops a horizontal scrollbar large enough to begin a side career.

That is why RTL responsive design needs its own test pass. Narrow widths do not merely make the desktop layout smaller. They activate different CSS rules, change which content wraps, force flex and grid items toward their minimum sizes, and reveal controls that were positioned for one physical edge. A page can therefore look correct in Arabic at 1440 pixels and still fail thoroughly at 390.

The useful question is not “Does the mobile page look mirrored?” It is: does every component still fit, read, open and receive focus when both its direction and available inline space change? This guide shows how to answer that without collecting screenshots of three popular phones and hoping the fourth is feeling cooperative.

Mobile reveals assumptions that desktop space hides#

Responsive RTL failures tend to come from four kinds of assumption:

  • The content will stay short. English labels fit on one line, so the component receives a fixed width or height. Arabic text wraps and is clipped, overlaps another element or pushes the page wider.
  • The viewport is the component's available width. A media query works on a full page, but the same component also appears inside a split view, modal or narrow dashboard column.
  • A physical side means a semantic side. A mobile override adds left, right, margin-left or padding-right, quietly undoing the logical CSS used in the desktop rules.
  • Visual reversal is the same as RTL. A layout uses row-reverse, CSS order or a horizontal transform to imitate mirroring. It may look plausible while its DOM order, keyboard order or opening direction remains wrong.

These problems often arrive together. A rigid action area squeezes an Arabic heading, the heading cannot shrink because of its automatic minimum size, and a breakpoint moves the actions with a left margin. The resulting overflow looks like one mysterious mobile bug. It is actually three ordinary bugs sharing accommodation.

Start by separating them. Check document direction, component size, content wrapping, physical-side CSS and interaction order as distinct questions. Debugging becomes much less theatrical once every suspect has its own chair.

Set direction before testing width#

Responsive testing is meaningless if the document is not actually in RTL. The Arabic page should declare language and direction in markup:

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

lang="ar" identifies the language. It does not set layout direction. Likewise, a CSS declaration such as direction: rtl can make content render right to left without giving the document the semantic direction that HTML, selectors and other tools expect. R001 reports a document direction that is missing, contradicted or declared only in CSS. The guide to dir="rtl" covers that foundation in detail.

Once direction is correct, compare the same route in LTR and RTL with the same component state. Do not compare an English empty state with an Arabic populated state and then blame direction for the extra three rows. Keep data, permissions and open panels consistent so width and direction are the variables under test.

Also distinguish viewport width from zoom. Both reduce usable space, but zoom can expose text scaling and browser chrome effects that a resized desktop window does not. Test both. Responsive CSS that survives only while nobody enlarges the page has negotiated a rather narrow peace treaty.

Follow one component from wide to narrow#

Consider a kitchen production board. Each task shows a station, an Arabic preparation note and two actions. On a wide screen, the copy and actions sit beside one another. At a narrow width, the actions move below the copy.

html
<div class="prep-task-slot">
  <article class="prep-task">
    <div class="prep-task__copy">
      <p class="prep-task__station">محطة المخبوزات</p>
      <h2>تجهيز صواني المعجنات لدفعة الصباح</h2>
    </div>
    <div class="prep-task__actions">
      <button type="button">إعادة الإسناد</button>
      <button type="button">تم التجهيز</button>
    </div>
  </article>
</div>

Here is a fragile version:

css
.prep-task {
  display: grid;
  grid-template-columns: 1fr 18rem;
  gap: 1rem;
  padding: 1rem 1rem 1rem 1.25rem;
  border-left: 0.25rem solid #b95f38;
}

.prep-task__actions {
  display: flex;
  gap: 0.5rem;
  white-space: nowrap;
}

@media (max-width: 44rem) {
  .prep-task {
    grid-template-columns: 1fr;
  }

  .prep-task__actions {
    margin-right: auto;
  }
}

The developer intended a leading accent, flexible copy and a trailing action group. The CSS instead encodes a left accent, reserves a fixed physical width for the actions, forbids their text from wrapping and uses a physical margin at the breakpoint. English may happen to fit. Arabic is being asked to respect a private agreement it never signed.

A better base keeps the relationships semantic and lets the content participate in sizing:

css
.prep-task {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: 1rem;
  padding: 1rem;
  padding-inline-start: 1.25rem;
  border-inline-start: 0.25rem solid #b95f38;
}

.prep-task__copy {
  min-inline-size: 0;
}

.prep-task__actions {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  max-inline-size: 100%;
}

@media (max-width: 44rem) {
  .prep-task {
    grid-template-columns: minmax(0, 1fr);
  }

  .prep-task__actions {
    justify-self: start;
  }
}

In LTR, border-inline-start and padding-inline-start still map to the left, so the English layout stays where it was. In RTL they map to the right. The first grid track may now shrink below its content-based minimum, the action labels may wrap, and justify-self: start follows the component direction instead of choosing a physical edge.

This is not a universal card recipe. If the design requires actions at the physical bottom-left in both languages, a physical rule may be correct. The point is to name the intended relationship first. “Leading edge” and “left edge” are different requirements, even when the English screenshot makes them look identical.

Audit the breakpoint rules separately#

A codebase can use logical properties beautifully in its base styles and still break RTL inside one media query. This happens because responsive rules are often added later, under pressure, shortly before somebody says the words “quick mobile fix.” History has recorded worse sentences, but not many.

Search inside every @media and @container block for direction-sensitive physical properties:

css
left
right
margin-left
margin-right
padding-left
padding-right
border-left
border-right
border-top-left-radius
border-top-right-radius

Their presence is not proof of a bug. Safe-area padding and a decoration tied to the physical screen edge may genuinely need physical coordinates. But each use should answer a plain question: is this relationship physical, or does it mean inline start or inline end? If it is directional and lacks an RTL counterpart, R030 reports it.

Remember that the cascade does not grant logical properties special authority. A later margin-left inside a narrow-width query can override part of an earlier logical margin and leave the element with different values on both sides. Inspect the computed style at the failing width, not just the tidy base rule in the source file.

A useful review habit is to test one pixel on either side of each breakpoint. At 44rem, for example, check just below and just above the transition in both directions. This catches rules that are individually reasonable but disagree about padding, alignment or track count during the handover.

Flex and grid items need permission to shrink#

Many apparent RTL overflows are sizing problems. Arabic merely reveals them because the translated content has different line lengths and break opportunities.

Flex items have an automatic minimum size. By default, a non-scrollable flex item may refuse to shrink below its content-based minimum. The CSS Flexible Box Layout specification defines that behavior, and MDN's flex documentation calls out the practical consequence: flex items do not shrink below their minimum content size unless you change it.

When a text-bearing child must share a row with controls, avatars or status marks, give the text item permission to shrink:

css
.prep-task__copy {
  min-inline-size: 0;
}

The familiar min-width: 0 also works in a horizontal writing mode. min-inline-size: 0 says what the rule is for and continues to make sense if the writing mode changes.

Grid has a related trap. A flexible track written as 1fr can still be constrained by the minimum contribution of its content. Use minmax(0, 1fr) when the track is allowed to become narrower than that contribution:

css
.prep-task {
  grid-template-columns: minmax(0, 1fr) auto;
}

Do not apply these fixes blindly. If the item contains a control whose minimum width must remain usable, forcing the track smaller may create a different failure. Put the shrink permission on the text-bearing item, then define a deliberate wrapping or stacking rule for the controls.

Long unbroken content needs its own policy. Normal Arabic prose can wrap between words; a supplier URL, generated key or imported filename may not. Apply overflow-wrap: anywhere to the value that is allowed to break, not to the entire page:

css
.prep-task__source-link {
  overflow-wrap: anywhere;
}

According to MDN's overflow-wrap reference, anywhere introduces a break only when an otherwise unbreakable string would overflow. Global word-break: break-all is a blunter instrument and can break ordinary text in places readers do not expect.

If a fixed-pixel-width container cannot hold its Arabic text, R043 reports it. If text is wider than its box and hidden without an ellipsis, R042 reports the clipping. Those are different failures: one concerns a rigid container, the other an element silently cutting off visible text. Fix the sizing rule rather than adding overflow: hidden and congratulating the scrollbar on its disappearance.

Break on the component's width when that is what matters#

A viewport query answers, “How wide is the browser?” A reusable component often needs the answer to a different question: “How wide am I here?”

The production-board task might occupy most of the page on one route and a narrow planning column on another. On a wide monitor, the viewport media query never activates, even though the component has less than 30rem available. The task keeps its two-column layout and overflows inside the column. Technically the viewport is spacious. The component is living in a cupboard.

CSS container queries let descendants respond to a containing element's size. Make a wrapper an inline-size query container, then stack the task when that wrapper becomes narrow:

css
.prep-task-slot {
  container-type: inline-size;
}

@container (inline-size < 36rem) {
  .prep-task {
    grid-template-columns: minmax(0, 1fr);
  }

  .prep-task__actions {
    justify-self: stretch;
  }

  .prep-task__actions > * {
    flex: 1 1 11rem;
  }
}

The wrapper matters. A container query styles descendants based on the query container; it does not let an element query its own size and style itself. Place container-type on the slot that owns the available space, not on .prep-task if .prep-task is the element you want the query to change.

Viewport queries remain appropriate for page-level changes such as the main navigation mode or overall shell. Container queries are useful when the same component appears in more than one layout context. Use the width that actually creates the decision. Naming this distinction saves a surprising amount of CSS archaeology.

Let Arabic labels change the height#

Fixed heights make translated interfaces look disciplined until a label needs a second line. Then discipline becomes amputation.

Buttons, chips, tabs and summary rows should usually have a minimum block size, not a fixed height. Keep horizontal padding, allow the label to wrap where the design permits it, and let the block grow:

css
.prep-task__actions button {
  min-block-size: 2.75rem;
  padding-block: 0.625rem;
  padding-inline: 1rem;
  line-height: 1.5;
  white-space: normal;
}

There are legitimate single-line controls. A compact tab strip may use ellipsis because its full label is available elsewhere, perhaps in an accessible name or destination heading. That is a product decision, not a general cure. Clipping a primary action can remove the word that distinguishes it from the action beside it.

Test labels with the actual Arabic translation, not machine-expanded English such as [Save changes Save changes]. Length simulation can expose width assumptions, but it cannot reproduce Arabic shaping, word boundaries or the real place a phrase wraps. Use both: synthetic stress early, final translations before release.

Also test live states. Loading text, validation messages, permission warnings and post-action confirmations often appear only after interaction. A calm screenshot of the initial state says nothing about the error banner that arrives later and sits across the controls like an uninvited tablecloth.

Keep stacking order and focus order aligned#

On an RTL page, the first item in a normal flex row or grid appears at the right edge. The browser already uses the element's direction to determine inline start. You do not need row-reverse to make the row “RTL.”

Using row-reverse as a mirror is particularly risky at responsive widths. In an RTL container it lays the row out left to right in DOM order. The visual sequence may look reversed from what the designer intended, while keyboard focus still follows the DOM sequence. R081 reports a flex row-reverse used to fake RTL when tab order fights the visual order.

The same caution applies to CSS order and manual grid placement. Visual rearrangement does not rewrite the DOM. When a horizontal toolbar becomes a vertical stack, keep the source order meaningful and let layout change its shape, not its meaning.

For the kitchen task, the heading should remain before its actions in the DOM. A keyboard user learns what the task is, then reaches “reassign” and “complete.” If the mobile design needs the actions above the heading, decide whether that visual demand is important enough to create a different reading and focus order. Sometimes it is. Usually the screenshot is merely being assertive.

Test with a keyboard at every layout mode. Start before the component, press Tab through it, and compare the focus sequence with the reading sequence. Then repeat in LTR. A layout that requires direction-specific DOM duplication is carrying more risk than a layout that follows semantic source order.

Treat action groups as content, not ornaments#

Action clusters fail at narrow widths in predictable ways: buttons shrink until their labels overlap, one button escapes the container, or the entire group stays on one line because white-space: nowrap was inherited from a desktop toolbar.

Choose a narrow-width policy explicitly:

  • Wrap the actions while preserving source order.
  • Stack them and let each button fill the available inline size.
  • Keep one primary action visible and place secondary actions in a properly labelled menu.
  • Allow local horizontal scrolling only when the component is genuinely a horizontal sequence and every item remains reachable.

Do not let flex-shrink make the decision accidentally. Buttons need enough inline space for their label and enough block space when that label wraps. Icon-only replacements can save space, but only use them when the icon is understood in context and the control has an accessible name. A mysterious glyph is compact in every language. This is not the compliment it first appears to be.

Directional icons need a separate check. An arrow that advances through a sequence may need to point toward the next item in the RTL reading direction, while a play icon, download icon or familiar brand mark should not be mirrored. R025 reports a directional icon pointing against the sequence its control advances. The guide to what should be mirrored in RTL goes deeper into that distinction.

Position mobile controls with logical insets#

Narrow layouts introduce fixed close buttons, sticky action bars, floating filters and edge-attached controls that do not exist on desktop. They deserve a direction pass of their own.

For a control attached to the end edge, express both the block and inline relationships:

css
.mobile-tools {
  position: fixed;
  inset-block-end: 1rem;
  inset-inline-end: 1rem;
}

This preserves the original LTR position at bottom-right and moves the control to bottom-left in RTL. If the requirement is physical bottom-right in every language, use right. Logical CSS is a way to encode intent, not a moral achievement badge.

Safe-area environment variables are another exception. A device's left and right safe-area insets describe physical screen geometry, so rules that apply env(safe-area-inset-left) and env(safe-area-inset-right) to their matching physical sides should not be swapped merely because the document direction changes. Keep physical hardware constraints physical. Keep language-relative placement logical.

After positioning a fixed or sticky control, check it in all four corners of the problem: LTR and RTL, before and after the breakpoint. Open the software keyboard if the control sits near the bottom. Check zoom. Then confirm that it does not cover the final line of content or the action it is meant to help.

Off-canvas panels do not inherit a sense of direction#

An off-canvas menu or filter panel may be anchored with a logical inset and still animate from the wrong side. That is because translateX() uses the element's coordinate system; it does not mirror when dir changes. A negative horizontal translation remains negative.

Choose the panel's edge first. If it belongs at inline start, anchor it with inset-inline-start. Then define the hidden transform separately for LTR and RTL so it moves beyond that actual edge. Keep the open state at zero. Do not reverse the DOM or the entire page to make the animation look right.

R033 reports a translateX that moves an element off-screen in RTL. The check is useful, but interaction still needs a manual pass: open and close the panel, confirm where it enters, verify the backdrop covers the viewport, move focus into the panel, close it with the documented control, and make sure focus returns to the trigger. A panel that slides beautifully from the correct edge but leaves keyboard focus behind is still broken. It is simply broken with choreography.

Also test the fully hidden rectangle. A panel translated the wrong distance can leave a narrow strip outside the viewport and create document-level horizontal overflow. On an RTL page, content extending beyond the left edge can make the document scroll sideways. R040 reports document-level horizontal overflow; the horizontal-overflow guide covers the deeper diagnosis.

Distinguish page overflow from component overflow#

Not every horizontal scrollbar is a defect. A wide schedule or data grid may need a local scroll container on a phone. The failure is allowing that width to escape into the document, or making part of the local content unreachable.

Use two separate tests:

  1. Document test: the page itself should not become wider than its viewport because of an ordinary component.
  2. Local test: an intentionally scrollable region should expose all of its content and retain a visible, usable way to move through it.

In DevTools, this quick check tells you whether the root document is wider than its visible area:

js
document.documentElement.scrollWidth >
  document.documentElement.clientWidth;

If it returns true, inspect likely offenders at the failing width. This snippet lists elements whose rectangles extend beyond the viewport's physical edges:

js
const viewportWidth = document.documentElement.clientWidth;

[...document.querySelectorAll("body *")].filter((element) => {
  const rect = element.getBoundingClientRect();
  return rect.left < 0 || rect.right > viewportWidth;
});

The result is a lead list, not a verdict. A deliberately transformed panel, decorative bleed or child inside a local scroller may appear there correctly. Walk up the ancestor chain and inspect width, min-width, transforms, margins and positioning until you find where containment was lost.

Avoid fixing root overflow with overflow-x: hidden on the page. That can hide the evidence while leaving controls clipped or unreachable. Contain the component that owns the overflow, or correct the sizing and positioning that created it.

For wide media, start with flow-relative constraints:

css
.prep-task img,
.prep-task video {
  max-inline-size: 100%;
  block-size: auto;
}

For a genuinely wide table, wrap the table in a labelled scrolling region rather than crushing every column into unreadable fragments. Test the beginning and end in RTL by interacting with the real container. If script controls the scroll position, do not assume LTR scrollLeft arithmetic works unchanged; prefer bringing a known item into view and verify the result in each supported browser.

Test a width, direction and state matrix#

Device presets are convenient, but the bugs live between them. Build a small matrix around the component's actual decisions.

For each responsive component, test:

DimensionCases worth exercising
DirectionLTR and RTL routes with the same data
WidthWide, just above each breakpoint, just below it, and the narrowest supported width
ContainerFull page, narrow column, modal or split pane where the component is reused
ContentShort labels, final Arabic labels, long plausible values and empty states
StateResting, loading, error, expanded, focused and disabled where relevant
InputPointer, keyboard, zoom and on-screen keyboard where relevant

This is not a demand for a screenshot of every Cartesian combination. Start with the highest-risk states, then automate stable assertions. The matrix exists to stop a single “mobile Arabic” screenshot from pretending it represents all narrow layouts.

Useful assertions include:

  • The document's scrollWidth does not exceed its clientWidth on ordinary routes.
  • Text-bearing elements do not overlap visible siblings. R041 reports visible text-bearing elements that overlap.
  • Controls remain within their intended container and retain a usable hit area.
  • Arabic labels are visible in full unless truncation is a deliberate, communicated behavior.
  • Focus order follows the DOM and still makes sense after stacking.
  • Panels enter and leave through the edge they are anchored to.
  • The layout survives browser zoom and a modest increase in text size without losing actions.

Automated screenshots help with regression detection, but add geometry and interaction assertions where the failure has a precise shape. A screenshot can show that a button vanished. An assertion can tell you that its right edge moved 46 pixels outside the component immediately after a breakpoint. The second message tends to shorten meetings.

Ship the narrow layout, not the narrow screenshot#

RTL responsive design is sound when the component keeps its meaning as space changes. Direction comes from the document, leading and trailing relationships come from logical properties, text is allowed to affect size, and breakpoints respond to the space that actually constrains the component. The mobile version is not a mirrored picture of the desktop version. It is the same interface making sensible decisions under pressure.

Run a free Ritla scan on the Arabic page before release. It can surface document overflow, clipped Arabic text, rigid containers and direction-sensitive CSS that deserve inspection. If the page already scrolls sideways, continue with how to fix horizontal overflow in RTL; if the failures are spread across margins, insets and alignment, use the RTL CSS guide for the underlying property model.

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.