

The text in your database: (أَمْسَيْنَا). What the browser renders: )أَمْسَيْنَا(. The brackets are reversed, and none of the obvious fixes work on the first try.
I hit this while building an Arabic web app where several dhikr texts have alternate readings in parentheses: a morning reading and its evening equivalent wrapped in (...). Getting them to render correctly took three attempts.
What CSS direction: rtl Actually Does#
CSS direction: rtl affects run ordering. Characters that come later in logical order appear further left on screen. أَصْبَحْنَا (أَمْسَيْنَا) in logical order means (أَمْسَيْنَا) appears to the left of أَصْبَحْنَا visually.
What it does not do: mirror glyph shapes. The HTML dir="rtl" attribute triggers Unicode bracket mirroring, where ( renders as ) in RTL context. CSS direction: rtl alone does not. This distinction matters for every approach below.
Attempt 1: Swap the Brackets in the Source#
Store )أَمْسَيْنَا( instead of (أَمْسَيْنَا). RTL visual ordering reverses the run position: ) ends up on the right, ( on the left. No glyph mirroring means they stay as ) and ( glyphs. Result on screen: (أَمْسَيْنَا).
It works visually. But your content data now holds inverted brackets. Copy-paste gives you garbage. Screen readers read it wrong. Any non-browser consumer of the text gets confused. This is a rendering hack disguised as a data fix.
Attempt 2: Wrap Each Bracket in <span dir="ltr">#
<span dir="ltr">(</span>أَمْسَيْنَا<span dir="ltr">)</span>htmlThis prevents glyph mirroring. Each bracket stays as its own glyph. But visual run ordering still applies to each LTR span independently. The ( span is at the logical position of ( in the string, which is before أَمْسَيْنَا. In RTL visual layout, “before” means “to the right.” The ) span ends up to the left. You still see )أَمْسَيْنَا(.
Fixing mirroring is not the same as fixing position.
The Correct Fix: One LTR Island for the Whole Group#
<span dir="ltr">(<bdi>أَمْسَيْنَا</bdi>)</span>htmlThe outer <span dir="ltr"> is a single LTR run. Within it, ( sits at position 0 (left) and ) at the end (right). The <bdi> isolates the Arabic text so it renders RTL inside the LTR span, correct for the word itself. The whole unit, as a single LTR run, slots into the surrounding RTL paragraph at its logical position.
The brackets and their content move together as one unit. That is what the previous approach missed.
Generating This Server-Side#
If you are generating this in a template, a regex replacement handles the pattern:
var parenRe = regexp.MustCompile(`\(([^()]*)\)`)
func arabicHTML(s string) template.HTML {
escaped := template.HTMLEscapeString(s)
result := parenRe.ReplaceAllString(escaped,
`<span dir="ltr">(<bdi>$1</bdi>)</span>`)
return template.HTML(result)
}goThe regex matches each (text) group. The replacement wraps the entire group. Declare the compiled regex at package level so it is not recompiled on every call.
Is This Right for You?#
This applies when: Arabic text uses CSS direction: rtl without an HTML dir attribute, and the text contains parentheses around alternate or clarifying content following Latin typographic convention.
It does not apply to: Arabic text where parentheses should follow Arabic typographic convention, where the opening bracket faces right. For that, use a proper dir="rtl" context and let the Unicode bidi algorithm handle mirroring without intervention.