Displaying Thaana well

Thaana is written right to left, its vowels are marks above and below the letters, and most Dhivehi fonts have no Latin letters. Here's everything that goes wrong because of that, with a live example and the fix. The @yashau/thaana packages and thaana.css handle most of it for you.

1. Set direction and language

Without dir="rtl" the browser lays Thaana out left to right: the text hugs the wrong edge and the full stop lands on the right. lang="dv" lets the browser and screen readers pick the right font and voice.

Wrong

ދިވެހިރާއްޖެއަކީ ރީތި ޤައުމެކެވެ.

Right

ދިވެހިރާއްޖެއަކީ ރީތި ޤައުމެކެވެ.

HTML
<html lang="dv" dir="rtl">
<!-- or on any element -->
<p dir="rtl" lang="dv">…</p>

2. Text people type

dir="auto" picks the direction from the first letter, so a Dhivehi sentence that starts with a brand name turns left to right. If the text contains any Thaana, render it right to left (resolveDirection()), and use unicode-bidi: plaintext so each paragraph of a longer text decides for itself.

Wrong

Apple ގެ އާ ފޯނު ނެރެފިއެވެ!

Right

Apple ގެ އާ ފޯނު ނެރެފިއެވެ!

JS
import { resolveDirection } from '@yashau/thaana';

el.dir = resolveDirection(text); // 'rtl' if there's any Thaana, else 'ltr'

3. Numbers and phone numbers

Inside right-to-left text, a phone number's groups swap places and a minus sign jumps to the wrong end. Wrap them in an isolate: <bdi dir="ltr"> in HTML, or isolateNumbers() for plain strings.

Wrong

ގުޅާލައްވާ: +960 333-1234

މިއަދުގެ ފިނިމިން -5 ޑިގްރީ

Right

ގުޅާލައްވާ: ⁦+960 333-1234⁩

މިއަދުގެ ފިނިމިން ⁦-5⁩ ޑިގްރީ

HTML or JS
<p dir="rtl">ގުޅާލައްވާ: <bdi dir="ltr">+960 333-1234</bdi></p>

import { isolateNumbers, stripBidiControls } from '@yashau/thaana';
label.textContent = isolateNumbers(text);   // display
save(stripBidiControls(label.textContent));  // store without the invisible marks

4. Brackets

Store brackets in logical order: ( before the enclosed text. Browsers mirror brackets in right-to-left text, so they come out right. That is why Dhivehi keyboards (Microsoft's, and the one here) swap the bracket keys: you press the key that looks right on screen and the logical character is stored. Text that stores the brackets reversed shows them facing outward and breaks copy-paste.

Wrong

ދިވެހިރާއްޖެ )ރާއްޖެ( ގައި

Right

ދިވެހިރާއްޖެ (ރާއްޖެ) ގައި

5. Punctuation

Dhivehi uses the Arabic comma ،, semicolon ؛ and question mark ؟; the full stop is the ordinary one. No space before punctuation. normalizePunctuation() fixes both.

Wrong

ކިހިނެއް ? ރަނގަޅު , ޝުކުރިއްޔާ .

Right

ކިހިނެއް؟ ރަނގަޅު، ޝުކުރިއްޔާ.

6. Line height

Fili sit above and below the letters. A tight line height or overflow: hidden cuts them off, which shows up in buttons, badges and table cells. Give Thaana about 1.75.

Wrong
ބޫބީ ބޯބެ ކިޔުން
Right
ބޫބީ ބޯބެ ކިޔުން

7. Latin fallback

Most Dhivehi fonts have no Latin letters. Name only the Dhivehi font and English words fall back to the browser's default serif. Always add a Latin font after it; fontStack() also adds installed Thaana fonts.

Wrong

ދިވެހި Fonts ގެ 2026 ވަނަ އަހަރު

Right

ދިވެހި Fonts ގެ 2026 ވަނަ އަހަރު

CSS
font-family: 'MV Iyyu', system-ui, 'Noto Sans Thaana', 'MV Boli', sans-serif;

8. No italics

Thaana has no italics, and many Thaana fonts already lean. A browser-faked slant leans the wrong way for right-to-left text. Emphasise with weight or colour, and set font-synthesis: none.

Wrong

މިއީ މުހިންމު ބަހެކެވެ.

Right

މިއީ މުހިންމު ބަހެކެވެ.

9. Inputs

Most people have no Dhivehi keyboard installed. Give fields the phonetic keyboard, right-to-left direction, and turn off autocorrect and autocapitalise (they mangle Thaana on phones). Try typing divehi in both:

Wrong
Right
HTML
<script type="module" src="https://dvfonts.yashau.com/thaana.js"></script>
<input data-thaana>

React, Svelte and Vue: see the packages.

10. Canvas and SVG

Canvas draws with whatever font is loaded right now: wait for the font first, and set the direction, or the text comes out in a fallback font, left to right. In SVG set direction="rtl" and text-anchor="start".

JS
import { loadFonts } from '@yashau/thaana';

await loadFonts({ family: 'Faruma' });
ctx.font = "32px 'Faruma'";
ctx.direction = 'rtl';
ctx.textAlign = 'start';
ctx.fillText('ދިވެހިރާއްޖެ', canvas.width - 16, 48);

11. Old fonts and PDFs

Documents typed with the old A_ fonts store Latin letters (ihevid) and only look like Thaana in that font. Text copied out of a PDF comes back with reversed dates and stray spaces. Both can be repaired: convertLegacy() and repairPdfText(), or paste it into the converter.

Wrong

cswb ihevid

ތާރީޚް 2026/1/14

Right

ދިވެހި ބަސް

ތާރީޚް 14/1/2026

12. Generating PDFs

PDF libraries' simple text functions don't shape Thaana: fili land in the wrong place. Shape the text with HarfBuzz (script Thaa, direction RTL), draw the resulting glyphs, and embed the full font. To keep the text selectable and searchable, write each run in logical order. For form fields, provide the appearance yourself rather than letting the viewer regenerate it; Acrobat and Preview reverse Thaana or drop marks. rihaPDF does all of this and documents why.

13. Storing and searching

  • Store text in logical order (the order it's typed), never pre-reversed. Strip bidi control characters before saving or comparing (stripBidiControls()).
  • Keep the display-tidied version and the stored text separate: don't save what you only changed for display.
  • Thaana has no case and no Unicode composition variants, so plain string comparison works for search.