Phone numbers in RTL interfaces

Display, link, collect and test phone numbers in Arabic interfaces without reversing groups, signs or field editing behavior.

Guide13 min read

A phone number can be correct in the DOM, correct when copied and wrong on screen. In Chromium 151, an unisolated +966 55 123 4567 inside an RTL paragraph displayed its groups in reverse visual order, with the plus sign at the opposite end. Copying still returned the authored sequence.

The fix is not to reverse the stored value. Treat the phone as one LTR data item inside an RTL interface, then handle an Arabic-Indic digit version as a separate edge case. Use the same distinction in sentences, contact cards, tables, links and inputs.

Separate storage, display and editing#

Phone handling has at least three representations:

  • Stored value: the canonical data sent to the backend or calling system.
  • Display value: the grouping and digit style shown to the user.
  • Editing value: the characters and caret behavior inside the input.

They can differ without contradiction. A backend might store a compact international form while the interface adds spaces for reading. The visible formatting must never mutate the semantic number merely to compensate for bidi rendering.

Write the contract before implementing direction:

LayerDecision to document
StorageAccepted canonical form, country code, extension policy
DisplayDigit system, group separators, country-code presentation
InputAccepted local and international forms, paste behavior
LinkExact value passed to the calling URI
ValidationWhich numbering plans and error states are supported
AccessibilityVisible label, accessible name and spoken review

Do not use a screenshot as the only source of truth. Record the authored value and expected visual grouping so QA can detect a rendering error without guessing which sequence is correct.

Why an unisolated phone reverses#

Browsers display mixed Arabic, Latin text, digits and punctuation using the Unicode Bidirectional Algorithm. The algorithm knows character classes, not the business meaning “phone number.” Digits form numeric runs; spaces and the plus sign are resolved from surrounding directional context.

Problem

html
<p dir="rtl">رقم الجوال +966 55 123 4567</p>

In Chromium 151, measured on 2026-09-17, the visible Latin-digit result was 4567 123 55 966+. Firefox and Safari were not measured for the browser-specific results in this guide, so run the same fixture in the supported matrix.

This is visual reordering. The source remains:

text
+966 55 123 4567

Changing the source to match the incorrect display produces bad data when the value is copied, submitted, linked or shown outside the Arabic sentence.

The Arabic label and the phone have different directional roles. Keep the paragraph RTL and add a narrow boundary around the value.

Isolate the phone value, not the whole row#

When the phone's direction is known, declare it directly:

html
<p dir="rtl">
  رقم الجوال <span dir="ltr">+966 55 123 4567</span>
</p>

Wrapping the value in <bdi> also kept this Western-digit phone in order in Chromium 151:

html
<p dir="rtl">
  رقم الجوال <bdi>+966 55 123 4567</bdi>
</p>

For a known phone type, dir="ltr" is clearer about the contract. Use <bdi> or dir="auto" when one component renders values whose direction is not known in advance.

A plain span does not isolate anything:

html
<p dir="rtl">
  رقم الجوال <span>+966 55 123 4567</span>
</p>

The browser still resolves that value with its surroundings. R021 reports unisolated phones, emails and Latin tokens inside Arabic text.

Do not apply LTR to the complete contact row:

html
<div dir="ltr">
  <span>رقم الجوال</span>
  <span>+966 55 123 4567</span>
</div>

That repairs one value by giving the Arabic label the wrong base direction. R020 reports a forced-LTR container or bidi override over Arabic content. Keep the exception at the phone boundary.

Keep the label outside auto-direction content#

dir="auto" and <bdi> derive direction from the first strong letter inside them. A Western-digit phone has no letters, so it resolved LTR in the measured browser. If the Arabic label sits inside the same wrapper, the label supplies the first strong character and makes the whole element RTL.

Too broad

html
<p dir="auto">رقم الجوال +966 55 123 4567</p>

Bounded value

html
<p dir="rtl">
  رقم الجوال <span dir="ltr">+966 55 123 4567</span>
</p>

This rule applies to icons and actions too. A phone icon, copy button or edit action can share the row, but it should not be placed inside the directional text value solely to influence layout. Use flex or grid for component structure and a bidi boundary for text.

Arabic-Indic phone digits are a separate edge case#

Changing the digit glyphs does not preserve the behavior of a Western-digit phone. In Chromium 151, spaces between Arabic-Indic digit groups resolved RTL even inside <bdi> or dir="ltr", including in English text. The visual group order reversed.

html
<p dir="rtl">
  رقم الجوال <span dir="ltr">+٩٦٦ ٥٥ ١٢٣ ٤٥٦٧</span>
</p>

For a product that requires this exact digit style and left-to-right group order, a bidi override preserved the authored order in the measured browser:

html
<p dir="rtl">
  رقم الجوال <bdo dir="ltr">+٩٦٦ ٥٥ ١٢٣ ٤٥٦٧</bdo>
</p>

<bdo> forces ordering instead of allowing normal bidi resolution. Use it only for a fixed-format value whose required visual order is known. It is not a wrapper for arbitrary user text.

The other valid product decision may be to display phone numbers with Western digits even when surrounding numbers use Arabic-Indic digits. That is a market and product rule, not a browser default to assume. Document the choice and test it consistently in display, input, export and notifications.

R061 reports Arabic-Indic and Western digits mixed in the same context. A phone exception should therefore be intentional and documented, not an accidental formatter difference.

Keep formatting out of the DOM boundary#

The bidi wrapper should receive a finished display string. It should not parse the number, choose a country, insert an extension or decide which digits to use.

A phone-domain layer can expose separate outputs:

js
const phoneView = {
  display: "+966 55 123 4567",
  href: "tel:+966551234567",
  copy: "+966551234567",
  submit: "+966551234567",
};

Those example policies are not universal. A product may copy the grouped display form or submit a different structured payload. The important part is that each output is named and tested rather than scraped from rendered text.

Render the prepared display value inside the direction boundary:

html
<span class="phone-value" dir="ltr">+966 55 123 4567</span>

The view component should not reverse characters, split digit groups into separate visual columns or reconstruct the value from left and right fragments. One semantic phone should remain one text value for bidi resolution and selection.

Do not normalize phone data with a generic “remove every non-digit” rule unless the domain contract explicitly supports that transformation. A leading plus sign, an extension and accepted local notation carry meaning. Use the parser and numbering-plan support chosen by the product, then report unsupported input rather than silently changing it.

Test the pipeline independently from rendering:

  • parser input to canonical data;
  • canonical data to display string;
  • canonical data to tel: URI;
  • display or canonical data to copy output;
  • edit buffer to submitted value;
  • localized digit conversion, when supported.

Then test the rendered boundary with fixed strings. This separation tells engineering whether a reversed phone is a bidi defect or whether the formatter produced the wrong group order before the browser received it.

A clickable phone needs one value for display and one for the URI. The visible version can contain approved grouping. The href should follow the product's canonical calling value.

html
<a dir="ltr" href="tel:+966551234567">
  +966 55 123 4567
</a>

The tel URI scheme defines telephone references. Do not build the URI by copying whatever formatted string happens to be visible. Presentation spaces, localized digits or extension labels may not belong in the same representation used by the caller.

Keep the link's Arabic context outside the LTR boundary when it includes a label:

html
<p dir="rtl">
  اتصل بنا:
  <a dir="ltr" href="tel:+966551234567">+966 55 123 4567</a>
</p>

If the number has an extension, document how the product stores, displays and links it. Do not invent URI syntax inside a view component. Build from parsed phone data and test on the devices the product supports.

An accessible name should describe the action and number without hiding the visible text from assistive technology. Have an Arabic-speaking screen-reader user review pronunciation and group comprehension for critical contact flows.

Phone inputs should edit left to right#

An Arabic form remains RTL, while the phone field uses the direction of phone data:

html
<form lang="ar" dir="rtl">
  <label for="phone">رقم الجوال</label>
  <input
    id="phone"
    name="phone"
    type="tel"
    dir="ltr"
    autocomplete="tel"
    placeholder="+966 55 123 4567"
  >
</form>

In Chromium 151, <input type="tel"> was LTR by default. Declare dir="ltr" anyway when that is the product contract. It makes intent visible and keeps the component independent of browser defaults or inherited CSS.

R070 reports telephone, email or URL inputs rendered RTL. R073 reports identity fields missing autocomplete tokens. R071 reports untranslated form labels, validation or help text.

Do not use text-align: right to make the phone field “fit” the Arabic page. Alignment can move the visible value without changing its editing direction. Keep the input LTR, then place the input component within the RTL form using logical layout properties.

dir="auto" adds little to a phone-only field. An empty auto-direction input resolved LTR in the measured browser, and digits kept it LTR. Explicit direction states the known data type and avoids content-dependent behavior.

Test editing, not only the filled screenshot#

A phone field can display a final value correctly and still be difficult to edit. Test the sequence a user performs:

  1. focus the empty field;
  2. type the plus sign and country code;
  3. type spaces or allow the formatter to add them;
  4. move the caret across every separator;
  5. select and replace one group;
  6. backspace at a group boundary;
  7. paste local and international forms;
  8. undo and redo;
  9. submit, receive an error and correct the value.

Watch logical value, visible value and caret position after every formatting update. A controlled input that rewrites the display on each keystroke can send the caret to the wrong place even though direction is LTR.

Do not reverse digit arrays or use CSS transforms to obtain the desired appearance. The first corrupts data; the second reflects glyphs and pointer geometry. Let the value stay in logical order and give it a narrow direction boundary.

Test selection and copying after formatting. The copied number should match the product's documented copy format, which may differ from storage or display. Make that a deliberate decision rather than an incidental result of the mask library.

Treat masking as an input transformation#

Phone masks are not bidi fixes. They parse input, insert separators and sometimes enforce a country format. Direction still needs to be correct before and after the mask runs.

Test the formatter with:

  • a leading plus sign;
  • pasted spaces, hyphens and parentheses if accepted;
  • local numbers with a leading zero;
  • international numbers;
  • deletion across inserted separators;
  • a number longer or shorter than the default example;
  • composition and mobile keyboard input;
  • an invalid value that remains editable.

Do not validate every market with one visual pattern. The supported numbering plan determines grouping and length. Keep parsing and validation in a phone-domain layer, not a generic “digits only” field helper.

If the UI displays localized digits, decide when conversion occurs. Converting on every keystroke can affect caret calculations and copy output. Converting only after blur changes what the user sees mid-edit. Either approach needs a written contract and tests.

Never infer validity from visual grouping alone. A correctly grouped number can be invalid, and an unformatted number can still represent a valid value accepted by the product.

Design country-code selectors as two directional regions#

A combined country selector and phone field contains RTL interface content and LTR phone data. Treat them as neighboring regions rather than forcing one direction over the complete control.

html
<div class="phone-control" dir="rtl">
  <button type="button" class="country-picker">
    السعودية
  </button>
  <input type="tel" dir="ltr" autocomplete="tel">
</div>

The control's layout follows the Arabic interface. The input value remains LTR. If the selected calling code is rendered as its own value, give that value an LTR boundary too.

Use logical spacing and insets:

css
.phone-control {
  display: flex;
  gap: 0.5rem;
}

.phone-control input {
  min-inline-size: 0;
  flex: 1 1 auto;
}

Do not add row-reverse merely to make the RTL control start on the right. A normal flex row already follows the container direction. Keep DOM order meaningful and test Tab order against the visual task.

The country list needs Arabic labels, searchable names and a clear selected state. The calling code is data inside that Arabic menu, so isolate it rather than setting the whole option LTR.

Display phones consistently in tables and cards#

Contact cards and data tables often reuse a generic text cell. A global RTL cell direction can reorder phone groups. A global LTR cell direction can give Arabic labels the wrong base.

Render by field type:

html
<td>
  <a dir="ltr" href="tel:+966551234567">+966 55 123 4567</a>
</td>

Direction does not require a particular physical alignment. A phone can remain LTR while the column sits at the table's logical start. Choose alignment for scanning and the product's table design, then test it separately from bidi order.

At narrow widths, avoid arbitrary breaks inside a number whose groups must remain readable. Give the value a dedicated line, reduce competing columns or put a genuinely wide table inside a local horizontal scroller. Do not hide root overflow.

A copy action should copy the documented representation, not scrape whatever text happens to be visually adjacent. If the table shows a masked number for privacy, the copy policy must follow that privacy rule too.

Keep validation messages outside the value boundary#

The error is Arabic interface text; the invalid phone remains LTR data. Do not wrap them together in an LTR container.

html
<div dir="rtl">
  <input
    id="phone"
    name="phone"
    type="tel"
    dir="ltr"
    aria-describedby="phone-error"
  >
  <p id="phone-error">أدخل رقم جوال صالحاً</p>
</div>

Keep the submitted value when validation fails so the user can correct it. Test that the field receives focus according to the form's error strategy and that the Arabic message is associated with the field.

In Chromium 151 with an English browser interface, the page's Arabic lang did not translate the built-in required-field message. If Arabic validation text is required, provide product-owned messages while preserving validity and focus behavior.

The language reviewer must judge whether the message explains the accepted format. A non-Arabic-speaking tester can still verify that the approved message appears, stays RTL, fits its container and refers to the right input.

Copying can hide a visual phone bug#

In Chromium 151, selecting or copying the unisolated phone returned the authored sequence even when the groups displayed in the wrong visual order. The accessibility tree also retained logical text.

That means these checks are insufficient on their own:

  • copy the number into a text editor;
  • inspect only textContent;
  • inspect only the accessibility-tree string;
  • confirm only that the tel: URI is correct.

Test three outputs independently:

  1. Visual: groups and sign appear in the expected order.
  2. Logical: copied and submitted data match the contract.
  3. Interactive: the field edits and the link calls as intended.

For accessibility, also test the spoken output. Digit grouping, pauses, language and accessible labels affect comprehension in ways a DOM string does not show.

Debug from data to pixels#

When a phone looks wrong, work through the layers in order. Changing CSS before checking the data can hide a formatter defect; changing the stored value before checking markup can corrupt correct data.

  1. Capture the source. Read the exact string or structured phone object before formatting.
  2. Capture formatter output. Record the display, copy, link and submit representations.
  3. Inspect the DOM. Confirm the phone is one narrow text boundary with dir="ltr", <bdi> or a justified <bdo>.
  4. Inspect computed direction. Check the value, field and surrounding Arabic container separately.
  5. Compare visual order. Use the documented grouping, not copy output, as the expectation.
  6. Exercise interaction. Copy, click, edit and submit the same phone.

Classify the symptom before fixing it:

SymptomLikely layerEvidence
DOM string already has swapped groupsFormatter or upstream dataSource and formatter output
DOM is correct, visual groups reverseMissing or unsuitable bidi boundaryMarkup and screenshot
Western digits pass, Arabic-Indic groups reverseDigit-class edge caseSame phone in both digit systems
Display passes, copied value is wrongCopy serializationClipboard output and policy
Display and copy pass, call target is wrongURI serializationLink href and device test
Final value passes, caret jumps while typingControlled-input formatterKeystroke recording and selection range
Label becomes LTR with the phoneBoundary is too broadComputed direction of label and wrapper

Do not call a phone “backwards” without attaching the authored and expected sequences. In an RTL sentence, visual placement can be unfamiliar while group order remains correct. The issue should say exactly which sign or group moved.

For intermittent failures, compare the value before and after hydration, country selection, validation and locale switching. A server-rendered phone may be isolated correctly and then replaced by client markup that drops dir.

Common fixes that create a second bug#

Attempted fixWhy it failsBetter boundary
Reverse the phone stringCorrupts stored, copied and submitted orderKeep logical data, isolate display
Set the whole card to LTRGives Arabic labels the wrong base directionLTR only on the phone value
Wrap with a plain spanCreates no bidi isolationdir="ltr", dir="auto" or <bdi>
Right-align the inputChanges alignment, not value directionLTR input inside RTL layout
Use <bdo> for every phoneForces unknown content and can corrupt other digit formsReserve for tested fixed-format cases
Remove all spacesChanges approved display and may hurt scanningPreserve format inside the right boundary
Hide overflowCan make the number unreachableFix sizing or use a local scroller
Trust copied orderLogical copy can be right while display is wrongTest visual and logical outputs separately

The narrowest fix is usually the safest. Phone components should own phone direction. Page containers should own Arabic direction. Layout CSS should own placement.

Build a phone-number test matrix#

Test the same values everywhere phones appear: sentence, card, table, link, field, validation summary, export and notification.

Include:

  • international Western-digit number with spaces;
  • compact international number;
  • local number accepted by the product;
  • Arabic-Indic display if supported;
  • pasted number with accepted punctuation;
  • invalid short and long values;
  • number with an extension if supported;
  • empty, focused, populated and invalid field states;
  • copy and tel: link output;
  • a narrow viewport and 200 percent zoom.

For sentence fixtures, place the phone at the beginning, middle and end of Arabic text. Follow it with punctuation and another number. Test an Arabic label inside and outside the value wrapper to prove the boundary is narrow.

Record source, displayed grouping, copied value, submitted value and link target. A screenshot alone cannot establish all five.

Ritla reports unisolated phone values with R021 and telephone inputs rendered RTL with R070. Run a free scan of a contact or checkout page, then reproduce each finding with the product's exact digit system, grouping and form behavior.

The bidi rules behind every reversal on this page are set out in bidirectional text, including why hyphens and spaces split digit groups while slashes and colons do not.

Checks in this guide

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.