Most teams that underestimate RTL support are thinking about it as a text direction switch. Set dir="rtl", flip the layout, hand it to a translator. In practice, the work is distributed across the stack in ways that don't become visible until something breaks—a CSS property that doesn't respond to direction attributes, a component that mirrors correctly in isolation but not in context, a bidirectional string that renders fine in testing and falls apart with real user data.
The reason RTL is expensive to add late isn't that any individual piece is especially hard. It's that directionality assumptions get embedded at every layer—design tokens, layout logic, component behavior, string handling, data formatting—and unpicking them from a mature codebase requires touching more than most estimates account for. Teams that build for RTL from the start make a series of small decisions. Teams that retrofit it make those same decisions under time pressure, surrounded by code that was written assuming the opposite.
RTL work falls into three layers: layout mirroring, script and text rendering, and bidirectional content. Each requires different work to solve, and each intersects with the others in ways that make piecemeal fixes unreliable. This guide maps all three, with enough detail for founders and product teams to scope the work accurately before it starts.
Layout mirroring is the most visible layer. When a user switches to Arabic or Hebrew, the spatial logic of the interface reverses: what was on the left moves to the right, reading flow runs in the opposite direction, and the sequence of interactive elements follows suit. A checkout flow that reads naturally left-to-right in English will feel backwards in Arabic if the layout is translated without mirroring.
Script and text rendering operates at a different level. Arabic is a cursive script with more visual complexity per line than Latin text. Hebrew has its own typographic characteristics. Persian uses an Arabic-derived script with additional letters and its own numeral conventions. Each has rendering requirements that go beyond setting a direction attribute—spacing, line height, font selection, and component sizing all behave differently.
Bidirectional content is where both layers collide. Real interfaces rarely contain purely RTL text. A product description in Arabic might include an English brand name, a URL, a number, a currency symbol, and a date—each with its own directionality. How those mixed strings render depends on rules that aren't always intuitive, and the results vary by browser and context.
These layers interact. A layout decision affects how a mixed-direction string sits inside a container. A script rendering issue compounds a spacing problem introduced by the layout mirror. Testing one in isolation doesn't guarantee the others will behave correctly together. That interdependency is why RTL is a systems problem, and why addressing it piecemeal produces partial solutions that hold together until they don't.
The general rule: anything tied to reading flow or sequential order should mirror. Navigation drawers move from left to right. Progress bars fill right to left. Pagination controls swap sides. In a multi-step flow, "Next" and "Back" exchange positions.
Mirroring can't be applied as a single transform to the whole interface. The same component may behave differently depending on where it sits—a sidebar that mirrors correctly on a dashboard might need different treatment inside a document editor where the content itself is directional. Screens need to be reviewed individually.
Icons fall into two categories. Directional icons—arrows, chevrons, playback controls, sliders—carry spatial meaning and should be flipped. An arrow pointing right in LTR points the wrong way when reading direction has reversed.
Non-directional icons—calendars, phones, shopping carts, padlocks—represent objects or concepts with no inherent orientation. Mirroring them produces something that looks wrong. The test is whether the icon's meaning depends on left-right orientation.
Some icons require judgment. A trend chart could reasonably be mirrored or left alone depending on how its axes are labeled. What matters is that these decisions get documented in the design system so they're applied consistently rather than revisited each time a component is updated.
The engineering sources of layout mirroring failures are predictable. Physical CSS properties—margin-left, padding-right, left: 0—always mean the physical side regardless of text direction; they don't respond to the direction attribute. Absolute positioning and hardcoded offsets have the same problem. JavaScript that calculates positions, manages carousel offsets, or handles swipe interactions often encodes LTR assumptions directly in the logic, and those assumptions only surface when dir="rtl" is present.
These aren't obscure edge cases. They're the default output of codebases where RTL wasn't in scope at the start.
Arabic and Hebrew have different visual rhythm than Latin scripts. Arabic in particular is a cursive script with more visual complexity per line—a layout calibrated for English can feel cramped in Arabic, and one calibrated for Arabic may feel sparse in English. Font choice, line height, and letter spacing typically need revisiting for each script.
The product impact shows up in components with constrained space. A button comfortable in English may clip its Arabic label or leave awkward whitespace in Hebrew. The only reliable way to catch these issues is testing with real translated strings. Pseudo-localization tools that approximate string length are useful for catching structural problems early, but they don't replicate the visual weight of Arabic or Hebrew type.
Translated strings don't behave predictably relative to their English source. Arabic sometimes runs longer, sometimes shorter—the relationship varies by content type. Tab labels, button text, alert dialogs, and tooltip content all need to be verified with actual translations before layouts are finalized. Layouts that pass QA with approximate content can still fail with production copy.
Persian (Farsi) uses an Arabic-derived script but is a distinct implementation concern, not a variant of Arabic. It includes characters not found in Arabic, uses different numerals by convention (Persian-Indic: ۰۱۲۳۴۵۶۷۸۹), and has its own typographic and line-breaking characteristics. Treating Persian as Arabic with a translation swap produces rendering errors and numeral display issues that native speakers notice immediately.
Most RTL interfaces aren't purely RTL. Arabic product descriptions include English brand names. Hebrew sentences end with URLs. Arabic labels appear next to numeric values. Each piece of that string has its own directionality, and how they render together is governed by the Unicode Bidirectional Algorithm—a set of rules that determine order and alignment for mixed-script content.
The algorithm works well in typical cases. It breaks down around neutral characters: spaces, punctuation, and brackets take on the directionality of surrounding content, which can cause word order or punctuation placement to render incorrectly. The results are content-dependent—a string can pass all testing and fail only when a specific combination of characters appears in production data.
Not everything in an RTL interface should be RTL. Numbers, URLs, email addresses, and code should display left-to-right regardless of the surrounding interface direction—that's how users expect to read and enter them. When a component doesn't account for this, the results range from visually odd to functionally broken: digits rendered in reverse order, input cursors positioned incorrectly, data entry errors that are subtle enough to miss in testing.
The common fix is setting dir="ltr" explicitly on specific fields—number inputs, URL fields, email inputs, code blocks—within an otherwise RTL layout. The issue is common enough that it belongs in any RTL implementation checklist.
RTL locales use different conventions for dates, numbers, and currency. These are data formatting problems, not UI styling problems. A date formatted with a locale-unaware component will display incorrectly regardless of what the surrounding interface looks like.
The specifics matter. Numeral systems differ: Eastern Arabic (٠١٢٣٤٥٦٧٨٩) is standard in some contexts, but practice varies between countries—Egypt and Saudi Arabia differ, and Persian uses its own set (۰۱۲۳۴۵۶۷۸۹). Calendar systems differ: Hijri and Persian Solar are in use alongside Gregorian. Currency symbol placement follows different conventions. None of these can be addressed with a CSS override—formatting needs to happen at the data layer using locale-aware libraries. The JavaScript Intl API covers most common cases; date-fns and Luxon cover more complex ones.
Translations produced without context about where a string appears tend to need revision once they're integrated. A word count limit that isn't communicated to the translator produces strings that overflow their containers. A button label extracted without its surrounding UI context gets translated at the wrong register.
Translation management platforms like Phrase, Lokalise, or Crowdin support screenshot annotations and character limit constraints on a per-string basis. Using these consistently means translators work with the same constraints the interface imposes, and length or tone problems surface during translation rather than during QA.
A few content-specific issues come up consistently in Arabic and Hebrew localization. Arabic has grammatical gender that affects agreement across a sentence, so a single English string may require multiple Arabic variants depending on context. Hebrew distinguishes grammatical gender in second and third person pronouns. Both languages also require plural forms beyond the singular/plural split English uses—see the next section. These cases benefit from review by a native speaker with product context, not just linguistic fluency.
Most i18n libraries are built around English-style pluralization: singular and plural, one rule. Arabic has six grammatical plural categories (zero, one, two, few, many, other), each with distinct forms. Hebrew has four. Standard library defaults handle none of this correctly out of the box.
Libraries like i18next support complex pluralization through the Unicode CLDR plural rules, but you have to opt into it—loading the CLDR data and configuring locale-specific plural handling isn't automatic. A translation that looks complete in English QA can render the wrong plural form throughout an Arabic interface. This is a configuration problem that's easy to miss until native speaker review catches it.
Set this up before translation starts. Getting the plural structure right after strings have already been delivered means a second translation pass.
The highest-leverage tool in RTL development is a simple toggle that switches dir on the staging environment without requiring a language change or translated content. Engineers checking layout changes, designers reviewing components, and QA working through a release can all use it immediately.
The implementation is simple—a button that sets dir="rtl" on the document root and persists the choice in session storage. Its value is in what it changes about behavior: teams with a toggle check RTL as a matter of course; teams without one check it in dedicated passes and accumulate assumptions in between. Put it in place early.
The failures that appear most often, in order of how often they're missed:
Text overflow and clipping. Strings longer than their English equivalents overflow containers sized against the LTR layout. Buttons, tab labels, tooltips, and modal titles are the most common sites.
Misaligned components. Physical CSS or absolute positioning that wasn't reviewed for RTL places elements in the wrong position. Sometimes obvious; sometimes off by just a border width.
Incorrect swipe and scroll direction. Carousels, swipe gestures, and scroll-linked interactions that encode LTR offsets in JavaScript behave incorrectly. Visual inspection won't catch these.
Mixed-script rendering errors. Bidirectional strings with incorrect word order or misplaced punctuation. These are content-dependent and only appear with the specific string combinations that trigger them.
Numeric input direction. Number, URL, and email fields not explicitly set to LTR render incorrectly in some browsers, producing subtle data entry errors.
Running existing end-to-end tests against a direction-switched build—set dir="rtl", run the suite, treat failures as regressions—catches functional issues that visual review misses. Including this in the CI pipeline means RTL regressions surface when they're introduced. Visual regression tools like Percy or Chromatic catch layout breaks where a component still works but looks wrong.
Neither replaces native speaker review. A native Arabic or Hebrew speaker will identify issues that pass every automated check—translation register, cultural mismatches, interaction patterns that feel unnatural to someone who reads primarily in that direction. Schedule it as a distinct QA phase before launch, with enough time to act on findings.
Use CSS logical properties as the default. margin-inline-start instead of margin-left. Enforce it in code review. The ongoing cost is negligible; the retrofit cost is not.
Build direction awareness into the design system. Spacing tokens should use logical values. Icon components should accept a direction prop or read from context. Document mirroring behavior for every interactive pattern so decisions are made once and applied consistently.
Define translation length constraints before strings are written. Maximum character counts for buttons, labels, and tab titles should be established during design and passed to translators as hard limits. Translation management platforms like Phrase, Lokalise, and Crowdin support this natively. Setting it up costs almost nothing; fixing overflows after translations are delivered costs a second pass and an engineering cycle.
Put the direction toggle in early. The longer a team goes without one, the more directional assumptions accumulate unnoticed.
Run a full audit before committing to a timeline. You need to know: how many physical CSS properties need converting, which JavaScript components carry directional assumptions, what the icon library requires, and where the design system has gaps. Estimates made before an audit are consistently wrong in the same direction—they assume the work is smaller than it is.
Prioritize by user impact. Onboarding, core task completion, and navigation come before edge-case screens. A partially-RTL product that handles primary flows correctly is significantly better than one where RTL was applied uniformly but shallowly.
Set translation constraints before commissioning translations. Add direction-switched runs to the automated test suite at the start of the retrofit, not as a final gate—RTL regressions caught when introduced are far cheaper than a batch discovered before launch.
RTL follows the same cost curve as most structural decisions in software. The earlier the choice, the lower the cost; the later it's deferred, the more it replicates.
A design token uses a logical property or a physical one—that decision propagates across every component that references it. An icon component is direction-aware or it isn't—that propagates across every screen that uses icons. A translation workflow includes character limits or it doesn't—that affects every string in the product.
None of these are significant decisions in isolation. Accumulated across a codebase, a design system, and a content workflow, the difference between a product built with RTL in mind and one retrofitted for it is typically measured in months of engineering time and at least one additional translation pass. The technical difficulty is the same either way. What changes is how many places you have to go to make it.
Think of us as your tech guide, providing support and solutions that evolve with your product.