HTML <bdi>: when and why to use it
Learn how HTML bdi isolates inserted text, when to choose an explicit direction, and how to test names, identifiers and punctuation in Arabic UI.
By Omran Khleifat, Founder of Ritla
Guide13 min read
A laboratory portal can store a specimen reference correctly, return it unchanged from the API, and still display its groups in the wrong order beside an Arabic label. Adding a plain span around the reference does not fix it. The missing information is that the reference is a separate piece of text, with its own direction, rather than part of the surrounding sentence.
The HTML <bdi> element supplies that boundary. It isolates inserted text and, unless you specify otherwise, determines its direction from its content. It is a useful wrapper for user names, search queries, file names and product titles whose direction the interface cannot know in advance. For a reference with a defined left-to-right format, an explicit dir="ltr" is a clearer contract.
The choice matters in both directions: surrounding Arabic can change how an unisolated value appears, and an inserted Latin name can affect the placement of a number that belongs to the sentence after it. This guide uses a laboratory results portal to show both failures, then explains where to put the boundary and how to test it.
Start with a reference that actually breaks#
Suppose the portal shows a specimen reference whose two groups must remain in their recorded order. A reader comparing a printed vial label with the portal expects the identifier 58396-0724, read as an identifier from left to right. Reversing its groups changes which reference the screen appears to show.
Problem
<p dir="rtl">مرجع العينة: <span>58396-0724</span></p>After the Arabic label, the reference displays as 0724-58396. This follows the Chromium behavior supplied with this guide's brief: Western digit groups separated by a hyphen reorder after Arabic text. A plain span does not establish isolation.
Better
<p dir="rtl">مرجع العينة: <bdi>58396-0724</bdi></p>The isolated digit-only reference resolves to left-to-right and displays as 58396-0724. The Arabic label stays outside that boundary. If the reference format is explicitly LTR, make that requirement visible in the markup:
<p dir="rtl">مرجع العينة: <span dir="ltr">58396-0724</span></p>These are two valid implementations of this component. Use automatic direction for genuinely unknown text; use the explicit direction when the data format defines it.
Do not generalize this failure to every hyphenated value. A range has reading semantics that an identifier does not. Reordering range endpoints can correspond to an Arabic reader encountering the starting endpoint first from the right. Establish what the value means before calling its appearance incorrect.
To reproduce this particular bug, keep the Arabic label and the reference in the same paragraph. Testing the number alone on a separate line can hide the failure: a bare hyphenated Western-digit value does not reproduce the same context.
What bdi isolates, and what it leaves alone#
An inline boundary separates the value's bidirectional processing from the text around it. It does not translate the content, reverse the stored string or choose its language. See the WHATWG definition of the bdi element.
There are two decisions to keep separate:
- Isolation: should this inserted value interact directionally with its surrounding sentence?
- Base direction: should the value itself be interpreted as LTR, RTL or automatic?
The specimen reference needs isolation from the Arabic label and a left-to-right base direction. A patient-selected display name also needs isolation, but its direction may be Arabic or Latin. Assigning LTR to every inserted value solves only the first kind of requirement.
Isolation does not mean that every character inside becomes LTR. The browser still processes the isolated content as bidirectional text. An Arabic name can remain Arabic inside an otherwise English interface. A mixed-language title still contains runs with different directions. Treating the value as a separate unit does not erase that internal structure.
This is why <bdi> is different from an override. An override forces directional treatment onto characters; isolation establishes a boundary around normal processing. Do not replace one with the other because the tags look similar.
Keep this guide's scope narrow when debugging. If the whole page starts at the wrong edge, inspect document direction first. If a reference's groups move within an otherwise correct Arabic sentence, inspect its inline boundary. The broader mechanics are covered in bidirectional text: developer guide.
How automatic direction works#
Without a dir attribute, <bdi> behaves as automatic direction rather than simply inheriting its parent's direction. In a simple text-only value, the first strong directional character determines the base direction. Latin letters select LTR; Arabic letters select RTL. Digits, spaces and punctuation do not count as that first strong letter. The HTML directionality rules define the algorithm, including its treatment of descendants.
For the portal's patient-selected name, the useful default is:
<p dir="rtl">صاحب الملف: <bdi>ريم الحداد</bdi></p><p dir="rtl">صاحب الملف: <bdi>Elena Costa</bdi></p>These are alternative records rendered by the same template. Do not infer a direction from the interface locale. An Arabic route can contain a Latin name, and an English route can contain an Arabic one.
Digits do not make automatic direction inherit RTL#
A value containing only Western digits and punctuation resolves to LTR in <bdi>. That is why the isolated specimen reference works without an explicit dir attribute. An empty value also provides no Arabic letter from which to infer RTL.
Do not write application logic that assumes every <bdi> inside an RTL component will compute to RTL. Its purpose is to let the value supply its own direction. If direction must be fixed by the component's contract, express that contract instead of depending on the current fixture.
Put the label outside the automatic boundary#
This boundary is too broad for a reference field:
<p dir="rtl"><bdi>مرجع العينة: 58396-0724</bdi></p>The first strong letter is now in the Arabic label. Automatic direction selects RTL for the combined text, leaving the reference in the same kind of internal context that caused the original problem. The presence of a bdi tag alone is not proof of a fix.
Wrap the inserted value, not the entire label-value sentence. If a component accepts both a localized label and an external value, keep them as separate parts of its rendering contract.
Automatic direction is also not majority-language detection. A mixed title beginning with a Latin brand can select LTR even if Arabic appears later. Where a content editor knows the intended direction, allow that metadata to override the automatic default. For deeper examples, see how dir="auto" works.
Choose bdi, span dir or an existing semantic element#
The practical choice is about what you know about the value, not which tag looks more specialized.
| Content contract | Suitable markup | Reason |
|---|---|---|
| Display name with unknown direction | bdi | Isolate and infer direction from the value |
| Identifier defined as LTR | span dir="ltr" | State the format's known direction |
| Inserted Arabic phrase known to be RTL | span dir="rtl" | Keep its own direction explicit |
| Existing meaningful element with unknown direction | Add dir="auto" to that element | Preserve its semantics and supply direction |
| Whole Arabic page | Document-level dir="rtl" | Establish the page's direction at its proper scope |
An ordinary span with no direction attribute is just a wrapper. A span with dir="auto" supplies the automatic-direction alternative to <bdi>. With explicit directions, <bdi dir="ltr"> and <span dir="ltr"> are equivalent for this purpose. MDN's bdi reference explains these choices.
If the portal already links a report title, direction can go on the link:
<p dir="rtl"> التقرير المتاح: <a href="/reports/chemistry-review" dir="auto">Chemistry review</a></p>Here /reports/chemistry-review is an example route in the laboratory application. The link remains the meaningful element; an extra wrapper is unnecessary unless the application needs a separate inner boundary.
Avoid a universal helper that forces all interpolated values to LTR. It would assign the wrong base direction to an Arabic display name. A better component API distinguishes an unknown human-authored value from a known-format machine identifier, even if both eventually render as a short inline element.
Direction also says nothing about pronunciation. If a report title is intentionally English, add lang="en" where appropriate. Do not infer English from LTR: many languages use that direction. MDN's dir documentation distinguishes direction from language.
Choose the boundary by ownership of the text#
The difficult part of isolation is often deciding what belongs inside it. A useful rule is to ask which text came from the external value and which text the translated sentence owns.
If a laboratory account is literally named Helix+, the plus belongs inside the boundary. If the interface adds a colon after the account name, that colon belongs to the surrounding template. Do not move punctuation into the value merely to make a screenshot look tidier.
Problem
<p dir="rtl">أرسل Helix+ نتيجة جديدة</p>Between Arabic words, this Latin name's trailing plus can display at its other end, as +Helix. The name stored by the service has not changed, but its visible identity has.
Better
<p dir="rtl">أرسل <bdi>Helix+</bdi> نتيجة جديدة</p>The complete name, including its plus, is isolated. Test the name in the middle of the sentence, not only at its end. Neighboring text is part of the reproduction.
This same ownership decision matters for search feedback. The user's query is one value; the interface's explanation and result count are other parts of the sentence:
<p dir="rtl">نتائج البحث عن <bdi>Helix+</bdi>: 7 ملفات</p>Check the colon and count visually in the supported browsers. The example establishes a boundary, but the expected position of template punctuation should be reviewed in the actual translated sentence rather than inferred from the stored query alone.
Do not split a user-selected name into separate isolates for each word. Do not isolate each digit group of a reference independently. The boundary should express one meaningful inserted unit. Otherwise the browser receives several unrelated pieces where the product intended one value. W3C's inline bidi markup guidance discusses marking up inserted phrases.
A value can disturb the number after it#
The first specimen example concerns damage to the inserted value. The next failure concerns damage to the sentence around it.
Imagine an activity message saying that a named instrument added seven reports. An Arabic reader should encounter the instrument as one name and the quantity with the noun it counts.
Problem
<p dir="rtl">أضاف Spectra 7 تقارير</p>The following Western number joins the Latin word's left-to-right run. Visually, it ends up between the Arabic verb and the name, separated from the report noun. This is a sentence-boundary problem, even though the Latin name itself has no punctuation and looks intact.
Better
<p dir="rtl">أضاف <bdi>Spectra</bdi> 7 تقارير</p>Only the instrument name goes inside the isolate. The quantity remains part of the Arabic message. Wrapping both the name and quantity would tell the browser to keep processing them together, preserving the association the fix is supposed to remove.
There is an important limit to this advice: if a number is part of the instrument's official name, it belongs inside the name's boundary. The component cannot infer that distinction from whitespace. It needs structured data or a template that already knows which field supplies the name and which supplies the count.
Review such messages with the actual localized grammar. Isolation does not select the right Arabic plural form or repair an incorrectly translated sentence. It preserves the boundaries between the parts that the message already defines.
Keep dynamic values isolated when they update#
Implement the boundary in the rendering template, not as a one-time repair of a particular string. A report owner can change their display name, and an activity feed can receive a new record without a page reload.
This small example creates the boundary in HTML and updates only its contents:
<p dir="rtl">صاحب الملف: <bdi id="record-owner"></bdi></p>const owner = document.getElementById("record-owner");owner.textContent = "ريم الحداد";Run the script after the paragraph exists. Replace the value with Elena Costa, then with an empty string, and inspect the result each time. Automatic direction should follow the current content; the surrounding label should remain outside the element throughout those updates.
For framework components, apply the same structure in the template: an isolated value node with escaped text content. Do not build an HTML string from a patient-selected name. Isolation handles directional context, not HTML injection. Conversely, escaping a string does not establish a bidi boundary. Both concerns need their own implementation.
If an empty value displays a fallback such as an Arabic phrase meaning “not specified,” decide whether the fallback occupies the same value node or is a separate template branch. Test that branch. A Latin-only fixture does not exercise the direction change caused by the fallback.
When debugging server-rendered pages, inspect both the initial markup and the DOM after the application becomes interactive. A correct server wrapper does not help if a later rendering path replaces it with a plain span. The acceptance criterion is the final component structure and appearance in each state.
Where bdi is the wrong tool#
It does not establish page layout#
Wrapping an entire report panel in <bdi> is not a substitute for setting the direction of the page or panel. The element is intended for phrasing content, not a container for an application layout. Set document and section direction at their proper levels, then isolate inserted inline values where needed.
Likewise, text-align: right changes alignment, not the directional relationship between a Latin name and a following count. Moving the paragraph's box cannot repair its internal bidi boundary.
It does not force every numeric string into source order#
The working specimen example uses Western digits. Spaced Arabic-Indic digit groups behave differently: the supplied browser checks show their groups reordering even inside <bdi> or an element with dir="ltr". Do not promise that adding isolation preserves every possible numeric format.
If your identifier specification requires a fixed order for such data, build a fixture using the actual digit script and separators, then review the display against the authoritative reference. An override can force order, but that is a separate decision with consequences for any letters in the value. Do not apply it to arbitrary names or search queries.
It does not mean bdo#
<bdo> is a bidirectional override. Using it for an unknown display name suppresses normal directional behavior inside the value. That is the opposite of allowing Arabic and Latin content to retain their own runs. The distinction is documented in the HTML text-level semantics specification.
Inspect existing CSS too. A rule applying unicode-bidi: bidi-override or isolate-override changes the problem from isolation to forced ordering. MDN's unicode-bidi reference distinguishes these values. Removing an accidental override can be the relevant repair; adding another wrapper without understanding it can obscure the cause.
It cannot wrap text inside an attribute#
An accessible label or tooltip attribute contains text, not child elements. Typing <bdi> into that string does not create an isolation boundary. Where HTML markup is available, use it. Where only plain text is accepted, directional controls may be necessary at the string-construction boundary.
That requires deliberate pairing and testing, especially when strings are exported or reused. Follow W3C's guidance on Unicode bidi controls. Do not silently insert invisible controls into a stored specimen reference as a substitute for fixing its HTML presentation.
Debug the rendered boundary#
Inspect the actual value element in DevTools. Confirm that the wrapper survives rendering, contains exactly the external value, and has the intended dir attribute. Look for a translated label accidentally included inside it or a count that belongs outside it.
With that element selected, this console inspection separates the attribute from the computed styling:
const value = $0; // The selected value element in DevTools.({ tag: value.localName, dirAttribute: value.getAttribute("dir"), direction: getComputedStyle(value).direction, unicodeBidi: getComputedStyle(value).unicodeBidi, text: value.textContent});A missing dir attribute on <bdi> is not itself a defect: its default is automatic. On a plain span, however, missing direction markup is a reason to inspect whether any isolation exists. Computed direction alone cannot prove isolation; inspect unicode-bidi and the element's structure as well.
Copying the reference into an editor is useful for checking its stored order, but it cannot establish that the screen is correct. In the supplied browser checks, copying retains logical order even when the visible groups reorder. Accessibility-tree order likewise does not establish visual placement. Keep a visual assertion for the reference and a separate assertion for its underlying value.
If a bug disappears when you simplify the fixture, restore context before rejecting the report. The preceding Arabic label, following number and punctuation may be exactly what triggers it. A screenshot of the value alone is not an equivalent test.
Test the component, not just the tag#
Keep a small fixture set for the laboratory portal rather than filling every test with a Latin name. Each record should exercise a different boundary decision.
| Fixture | What to verify |
|---|---|
58396-0724 after the Arabic specimen label | Reference groups remain in their defined order |
ريم الحداد as the record owner | Automatic direction handles the Arabic value |
Elena Costa as the record owner | The same component accepts a Latin value |
Helix+ between Arabic words | The plus stays with the name at its intended end |
Spectra before a report count | The count remains associated with the surrounding message |
| Empty owner value and its fallback | The fallback branch retains the intended boundary |
Render the original failing markup beside the repaired version. For the reference fixture, confirm the failing version shows 0724-58396 in the supplied Chromium reproduction and the repaired version shows 58396-0724. This comparison verifies that the fixture is capable of exposing the defect.
Then run the repaired component on both Arabic and English routes. Change the owner dynamically without reloading. At a narrow width, inspect the whole sentence again after it wraps: check the reference, name punctuation and relationship between quantity and noun. Repeat in the browsers your product supports, recording browser versions with visual evidence rather than assuming the supplied Chromium measurements describe every engine.
Add a structural assertion that the value has its boundary, but do not stop there. A wrapper around the entire sentence could satisfy a shallow tag-existence test while leaving the original failure intact. A useful review checks exactly which text node is inside it.
A free Ritla scan can report , unisolated bidi hazards: phones, emails, Latin tokens inside Arabic text. Use the fixtures above to verify the repaired display by hand; continue with dynamic text direction for changing values or Arabic and English mixed text in HTML for sentences with several language runs.