Skip to content

Parentheses and brackets in RTL text

Learn how parentheses and brackets behave in RTL text, when mirroring is correct, and how to debug missing pairs and mixed-direction phrases.

By , Founder of Ritla

Guide13 min read

A maintenance screen shows an Arabic instruction with an English procedure name in parentheses. Someone notices that a parenthesis faces a different way in the editor and swaps the characters in the translation. The browser now receives a closing parenthesis before the procedure and an opening parenthesis afterward. A visual repair has become malformed text.

Parentheses and square brackets normally need no manual swapping for Arabic. The reader should encounter an opening delimiter, the phrase it introduces and a closing delimiter in the phrase's reading direction. For an Arabic aside, that reading starts on the right. For an English procedure name, its internal reading starts on the left.

The useful debugging questions are whether the stored characters form the intended pair, which phrase owns them, and whether both delimiters participate in the same directional context. This guide uses a maintenance console for optical instruments to distinguish those questions from normal browser mirroring.

A parenthesis is opening punctuation, not a fixed left shape#

Store opening punctuation before the phrase and closing punctuation after it in logical order. These are ordinary Arabic notes, written with the same parenthesis and square-bracket characters used in English:

html
<p lang="ar" dir="rtl">الفحص متاح (بعد تنظيف العدسة).</p><p lang="ar" dir="rtl">حالة الجهاز [بانتظار المعايرة]</p>

The expected reading is an instruction followed by an aside, or a label followed by a bracketed status. Each pair should surround its own phrase. Do not judge the opening mark by whether it occupies the physical left edge of the Arabic phrase.

The character named LEFT PARENTHESIS, U+0028, functions as an opening parenthesis. RIGHT PARENTHESIS, U+0029, functions as its closing partner. Their names reflect familiar glyph shapes, but those shapes are not a storage rule for Arabic. The browser can draw a mirrored glyph without replacing the stored character. The W3C explanation of mirrored characters illustrates this distinction.

An RTL paragraph also contains Latin runs, numbers and punctuation with their own resolved directions. Do not interpret mirroring as a rule that every parenthesis anywhere inside an RTL page must face the opposite way. Its resolved direction matters, not just the page's direction.

Start with the correctly stored Arabic examples above before adding any CSS. If their source is balanced and their delimiters enclose the intended words, leave their punctuation alone. A different appearance from an LTR editor is not sufficient evidence of a defect.

Undo visual repairs in the source text#

Suppose the maintenance console uses the English procedure name Lens check. The intended stored parenthetical is (Lens check). A translator trying to compensate for an editor can accidentally produce this:

Problem

html
<p lang="ar" dir="rtl">  الإجراء المحدد: <span dir="ltr">)Lens check(</span></p>

This is the wrong logical sequence for a complete parenthetical. The defect is present before rendering. Changing alignment cannot turn the first closing character into an opening character in the data.

Better

html
<p lang="ar" dir="rtl">  الإجراء المحدد: <span dir="ltr">(Lens check)</span></p>

Both delimiters belong to the English procedure annotation, so both sit inside its LTR boundary. The label remains Arabic. The complete value's expected visible sequence is (Lens check).

Repair the translation entry or template that supplied the swapped characters. Do not introduce an Arabic-only replacement that swaps every parenthesis at render time. Such a replacement would also change correctly authored strings, including nested notes and user-entered content.

When reviewing a suspect translation, inspect code points or character positions rather than copying its appearance into another editor. Record which character is first in storage. A content editor configured for another base direction can produce a different preview of the same valid text.

Keep the repair scoped. A log excerpt may intentionally contain an unmatched delimiter, while a complete interface annotation should not. Whether the source is malformed depends on the content contract, not simply on counting punctuation in every string.

Pair matching and mirroring are different operations#

Unicode's paired-bracket processing recognizes compatible opening and closing characters within an isolating run sequence. Rule N0 uses the enclosed text and directional context to resolve the pair together. Mirroring is separate: rule L4 selects a mirrored glyph for a mirrored character whose resolved direction is RTL. A lone parenthesis can therefore be mirrored even though it has no matching partner. See the Unicode Bidirectional Algorithm.

That distinction rules out two misleading diagnoses: a mirrored mark does not prove that a pair was found, and a missing partner does not mean that mirroring stops.

These ordinary delimiters have defined partners in Unicode's paired-bracket data:

Opening character in storageClosing partnerExample role in the console
()A procedure annotation
[]A bracketed instrument status
{}A literal configuration fragment

A round opening parenthesis and a square closing bracket are not a matching pair. Matching shape families is part of the source contract, even if a font makes two marks look similar.

The algorithm does not supply your application's grammar. Balanced delimiters can still surround the wrong phrase, and punctuation can look plausible while the underlying string is wrong. Validate source structure and reading meaning separately.

Decide whether the brackets belong to the Arabic sentence or the inserted phrase#

Two components can look similar while owning their punctuation differently. The wrapper should reflect that ownership.

If the annotation is a complete English phrase, keep its brackets with it:

html
<p lang="ar" dir="rtl">  اختر الإجراء <span dir="ltr">(Lens check)</span> للمتابعة.</p>

If the parenthetical is Arabic and merely mentions an English procedure, keep the outer phrase Arabic and isolate the embedded name:

html
<p lang="ar" dir="rtl">  الفحص جاهز (الإجراء <span dir="ltr">Lens check</span>).</p>

The first example treats the procedure annotation as one LTR unit. The second treats the parentheses as part of an Arabic aside. Neither requires reversing the stored punctuation.

It can also be valid for an Arabic sentence to put parentheses around a separately isolated name. Both outer brackets then belong to the surrounding context. Do not classify that structure as broken merely because the brackets are outside the name's isolate. Inspect the intended phrase boundary and the actual reading.

The fragile structure puts only one member of a pair inside an isolate:

html
<p lang="ar" dir="rtl">  اختر الإجراء <span dir="ltr">(Lens check</span>) للمتابعة.</p>

Here the delimiters are split across the directional boundary. Do not rely on their pairing across that boundary. For this component, move the closing parenthesis inside the span with the rest of the English annotation. Test the exact broken string in your browser rather than assuming one universal visual result for split pairs.

A plain styling span does not itself create this boundary. Text can be divided among ordinary inline spans without every span becoming an independent bidi context. What matters is the directional behavior introduced by markup, CSS or formatting characters. Bidi isolation in HTML explains those boundaries in more detail.

Use automatic direction only when the inserted phrase is unknown#

An imported procedure name may be Arabic or English. In that case, a bdi element can isolate the complete parenthetical without hard-coding one language's direction:

html
<p lang="ar" dir="rtl">  الإجراء المحدد: <bdi>(Lens check)</bdi></p><p lang="ar" dir="rtl">  الإجراء المحدد: <bdi>(فحص العدسة)</bdi></p>

The parentheses do not determine the automatic direction. The first strong character within the phrase does. Latin letters select LTR; Arabic letters select RTL. MDN documents the default automatic behavior of bdi.

Keep the Arabic field label outside the automatic wrapper. If the wrapper includes the label first, the label can establish RTL direction even when the procedure value is English. Automatic direction sees characters, not the application's concept of a field value.

For a procedure annotation guaranteed to contain English text, explicit dir="ltr" states the contract directly. For a whole Arabic parenthetical, retain RTL. Automatic direction is useful when the content is unknown, not as a replacement for modelling known content.

Also test a value containing only digits or punctuation. Its automatic direction does not come from the visual shape of its parentheses. Numeric-only and genuinely mixed-language annotations deserve their own fixtures. If a string has several nested language changes, inspect those nested phrases instead of assuming one outer automatic wrapper describes them all.

The measured automatic-direction behavior referenced here was checked in Chromium 151 on September 24, 2026. Firefox and Safari were not included in those measurements; verify the actual strings in your supported browsers.

Missing partners need a content decision#

An incomplete translation can leave the console with this instruction:

html
<p lang="ar" dir="rtl">الفحص متاح (بعد تنظيف العدسة.</p>

The opening parenthesis has no closing partner. The intended aside is evident from the interface copy, so repair the source sentence:

html
<p lang="ar" dir="rtl">الفحص متاح (بعد تنظيف العدسة).</p>

Do not treat the first example as an invitation to force direction. Adding an isolate cannot supply the omitted character. The exact position of the unmatched mark depends on its context; inspect it in the browser, but fix the missing content regardless of where it appears.

Generated annotations need the same decision. If an optional procedure description is absent, the application should usually omit its complete parenthetical. Rendering only an opening bracket as a loading placeholder creates an incomplete annotation. Rendering an empty pair can also suggest that information is missing even when nothing is pending.

For user-entered maintenance notes, preserve the user's text unless the field explicitly requires a complete parenthetical. An operator may paste a partial instrument message containing an unmatched bracket. Automatically appending a closing character changes the evidence they supplied.

Separate validation policies for interface translations, generated annotations and verbatim notes. A structural check that is useful for the first two can be incorrect for the third. In each case, the product should say whether it accepts literal text or a constrained phrase.

Keep generated delimiters with the generated content#

Give one component responsibility for a parenthetical. Splitting an opening delimiter into a translated prefix and the closing delimiter into a separate suffix makes it easier for conditional rendering to drop one half.

Here is a small component for an application-controlled English procedure name:

html
<p lang="ar" dir="rtl">  الإجراء المحدد: <span id="procedure-note" dir="ltr" hidden></span></p>

Run this JavaScript after the markup:

js
function renderProcedureNote(name) {  const note = document.getElementById("procedure-note");  if (!note) throw new Error("Procedure note element is missing");   const present = typeof name === "string" && name.trim() !== "";  note.textContent = present ? `(${name})` : "";  note.hidden = !present;} renderProcedureNote("Lens check");

The component creates both delimiters together and clears both when the value is absent. Its contract takes an unbracketed procedure name. Supplying an already-parenthesized string would create another pair, so distinguish raw names from complete annotations in your data model.

Using textContent inserts the name as text. It avoids treating imported names as HTML. In a framework, use the equivalent escaped text binding and retain the wrapper during updates.

This code does not validate arbitrary bracket syntax inside a name. If imported names contain unmatched delimiters, decide whether to display them verbatim, flag them for catalogue correction or reject them under an explicit import contract. Do not silently run a replacement over every bracket.

For multilingual procedure names, change the wrapper's direction policy to automatic and test the relevant languages. For translated complete sentences, let the translation own the full sentence and use a supported rich-text placeholder for the inserted phrase. Do not make punctuation assembly compete with sentence translation.

Check whether a stylesheet adds another pair#

A shared annotation style may generate its own punctuation:

css
.procedure-annotation::before {  content: "(";} .procedure-annotation::after {  content: ")";}

This CSS expects the element's text to contain only the procedure name. If the component also supplies (Lens check) as its text, two different layers now own the same delimiters. Review the result as duplicate punctuation before investigating mirroring.

CSS generated content can supply visible text through pseudo-elements. Those generated marks are not text-node characters returned by the element's textContent. A character inspection can therefore show a balanced string while the screen contains extra marks from the stylesheet. Inspect the ::before and ::after rules as well as the DOM text when the two disagree.

For the generated component above, keep the pair in its text and leave the stylesheet responsible for presentation. That makes the annotation's complete string available to the same data assertions used to test its empty state. Remove only the punctuation-generating rules that conflict with this component's contract; do not remove unrelated pseudo-elements from a shared design system.

If a design system deliberately owns the pair in CSS, callers must pass an unbracketed name and the empty-state behavior needs its own test. Clearing the text alone does not remove the two content declarations. The component must also hide the annotation or otherwise stop generating its punctuation when there is no value.

Check the class after switching between compact and expanded views. A compact annotation might receive the shared style while the full detail view uses literal punctuation, so a duplicate pair can appear only at one breakpoint. Compare the classes and pseudo-element rules before rewriting either translation.

This is also why a global search for the visible bracketed string may find nothing. Search for the procedure name, then trace whether its delimiters come from the translation, component rendering or generated content. Choose one owner for a given pair. Nested pairs remain legitimate when they represent different phrases; two layers decorating the same phrase are a different situation.

Nested parentheses need meaningful boundaries#

A maintenance instruction might contain an Arabic aside with an English annotation inside square brackets:

html
<p lang="ar" dir="rtl">  الفحص متاح (بعد اختيار <span dir="ltr">[Lens check]</span>).</p>

The outer round pair belongs to the Arabic instruction. The inner square pair belongs to the English annotation. This structure makes those roles inspectable in the markup.

Before adding more wrappers, identify each phrase's opening delimiter, closing delimiter and language. A tree of meaningful phrases is easier to maintain than separate spans wrapped around individual punctuation marks. Isolating each mark independently removes the very relationship the component needs to express.

Nested content can be logically balanced and still be difficult to read. Ask an Arabic reviewer to confirm which phrase each pair encloses, especially when the aside contains several English procedure names or an additional explanation. A delimiter count cannot establish that interpretation.

Do not flatten the text into a single LTR wrapper merely to stabilize the English fragment. That wrapper would also contain Arabic explanatory words. Keep nested direction changes local to their phrases. For more complex sentence composition, use the guidance on Arabic and English mixed text.

When exact character order is itself meaningful, such as a configuration expression, treat the complete expression as a separate LTR unit. A natural-language aside and literal syntax have different contracts. Do not apply a natural-language punctuation repair to a copied configuration fragment.

Distinguish clipping from an actually missing bracket#

A compact maintenance list may show only the start of an annotation. If the closing parenthesis is outside the visible area, the string can look unbalanced even though the DOM still contains both characters.

Compare that with application code that shortens the string before rendering. Taking a prefix of (Lens check) can delete the closing character from the actual text. The first case is a visibility problem; the second has changed the content supplied to bidi processing.

Inspect the element's full textContent, then temporarily remove clipping and width constraints. MDN's text-overflow reference explains how an ellipsis signals hidden overflow. Its appearance does not prove that the source string was shortened.

For annotations that users need to read completely, allow wrapping or provide an explicit expansion. Do not leave a closing delimiter permanently inaccessible in a narrow panel. If you intentionally abbreviate a structured annotation in application code, build a complete abbreviated phrase with its own pair instead of slicing the assembled text blindly.

A soft line wrap also differs from splitting the annotation into separate block elements. Unicode processes a paragraph before its lines are reordered; an ordinary width-driven wrap does not automatically turn the two delimiters into different paragraphs. Moving each half into a separate paragraph changes that structure.

Test the actual narrow layout before choosing a no-wrap rule. Keeping a long parenthetical on one line can create a different visibility problem. If the pair is present but cut off, continue with Arabic text getting cut off in CSS rather than editing its characters.

Inspect characters before overriding browser behavior#

Use a code-point view when an editor preview and the browser disagree. Run this after rendering the generated procedure annotation above:

js
const note = document.getElementById("procedure-note");if (!note) throw new Error("Procedure note element is missing"); console.table(Array.from(note.textContent, (character, index) => ({  index,  character,  codePoint: "U+" + character.codePointAt(0)    .toString(16).toUpperCase().padStart(4, "0")})));

For the generated English annotation, the first character should be U+0028 and the last U+0029. Square brackets use U+005B and U+005D. This reveals the stored order without asking the editor's bidi preview to prove it.

Then inspect the relevant element's dir, computed direction and computed unicode-bidi, including wrappers around only part of the phrase. Look for one delimiter inside an isolate and its partner outside, or a component stylesheet that overrides the intended boundary.

Do not use transform: scaleX(-1) on text to repair a parenthesis. It changes the rendered shape without correcting its character identity. A blanket bidi-override is also the wrong response to an ordinary parenthetical: it suppresses implicit ordering within its scope rather than repairing malformed source or missing punctuation. MDN explains the behavior of unicode-bidi overrides.

Avoid accumulating invisible direction marks around individual brackets when visible markup can express the phrase. W3C guidance on Unicode bidi controls recommends markup where it is available. Plain-text attributes and exports need separate treatment because they cannot carry a span's boundary.

If an imported note behaves unexpectedly, use the character inspection to identify hidden formatting characters too. Do not remove them indiscriminately from user text. First establish where they came from and whether the field is intended to preserve a literal excerpt.

Verify both the characters and what they enclose#

Build a fixture around the maintenance console's annotation component, not a gallery of unrelated identifiers. Include these cases:

FixtureVerification target
Arabic instruction with an Arabic asideNatural RTL reading and a complete pair
English procedure annotationWhole English phrase inside its declared boundary
Arabic aside containing the English procedureCorrect nesting and phrase ownership
Square-bracketed statusMatching delimiter family
Deliberately swapped or missing delimiterSource validation catches the intended defect
Empty optional annotationNo orphan punctuation remains
Long annotation in the compact listBoth content and reveal behavior remain usable

For each valid fixture, compare the stored text with its expected logical string. Then inspect the actual page visually: each pair should enclose the intended phrase, and adjacent sentence punctuation should belong to the sentence. Do not use a plain-text rendering of an Arabic sample as the visual reference; record a reviewed browser screenshot.

Check initial rendering and subsequent updates. Clear the selected procedure, choose another one and switch interface language. These transitions expose punctuation left behind by conditional rendering. In rich-text translations, also inspect whether a placeholder replacement retained the complete directional wrapper.

Repeat the visual review in supported browsers and at narrow widths. A passing text assertion establishes the character sequence, while a screenshot establishes visible placement. Copying and accessibility-tree inspection follow logical text order, so they do not substitute for the visual check. Review accessible wording separately when annotations carry instructions.

For punctuation at the wrong visual end, a free Ritla scan can report R024: Punctuation rendering at the wrong visual end of Arabic text. Pair completeness and which words the brackets enclose still need the targeted review above. Continue with how browsers render mixed-direction text when the source is valid but its surrounding context remains unclear.

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.

Scan a page freeBrowse the checks