Docs
Everything here is free to use. New to Thaana on the web? Read Displaying Thaana well. Building with an AI agent? Point it at /llms.txt.
Embed a font
The CSS API works like Google Fonts. Each font's page builds the URL for you; this is the format.
<link rel="stylesheet" href="https://dvfonts.yashau.com/css2?family=Faruma&family=Bolhu:wght@400;700&display=swap">
<p dir="rtl" lang="dv" style="font-family: 'Faruma', system-ui, sans-serif">ދިވެހިރާއްޖެ</p>family=Namewith spaces as+. Old names work too (A_Waheed).:wght@400;700picks weights; without it you get the default weight.- Fonts are split into Thaana and Latin parts, so a Thaana-only page downloads only the Thaana part.
- Many Dhivehi fonts have no Latin letters, so always list a Latin fallback after the family.
- Self-hosting? Every font and collection is on the downloads page.
Thaana keyboard
Most people have no Dhivehi keyboard installed. One script turns Latin keystrokes into Thaana with the Divehi Phonetic layout (Microsoft's standard layout, used across Maldivian sites). Try it:
Type “divehi” to get ދިވެހި.
<script type="module" src="https://dvfonts.yashau.com/thaana.js"></script>
<input data-thaana>
<textarea data-thaana></textarea>
<div contenteditable data-thaana></div>
<!-- or a classic script: window.Thaana -->
<script src="https://dvfonts.yashau.com/thaana.iife.js"></script>- Only typed characters change. Paste, IME composition, autocorrect and real Dhivehi keyboards pass through.
- Brackets work like Microsoft's layout: press the key that looks right on screen (
)to open on the right). The pairs are swapped, so the stored text keeps(before the enclosed text and browsers mirror it in RTL. ,;?type the Dhivehi forms ، ؛ ؟.- Works with React, Vue and Svelte controlled inputs; native undo keeps working.
Layout
Top: with Shift. The raw map is at /keymap.json.
Packages
One install makes Thaana work in your app: keyboard, fonts, bidi and punctuation helpers. The framework packages
include everything from @yashau/thaana.
npm i @yashau/thaanaimport { attachKeyboard, loadFonts, fontStack } from '@yashau/thaana';
import '@yashau/thaana/thaana.css'; // sane defaults for :lang(dv)
await loadFonts({ family: 'Faruma' });
const input = document.querySelector('#name');
input.style.fontFamily = fontStack('Faruma');
let dv = true; // e.g. bound to a DV/EN toggle
const detach = attachKeyboard(input, { enabled: () => dv });npm i @yashau/thaana-reactimport { useState } from 'react';
import { ThaanaInput, useThaanaKeyboard, useDhivehiFont, fontStack } from '@yashau/thaana-react';
export function Form() {
const [name, setName] = useState('');
const [dv, setDv] = useState(true);
useDhivehiFont('Faruma');
const notes = useThaanaKeyboard<HTMLTextAreaElement>({ enabled: dv });
return (
<>
<ThaanaInput dv={dv} value={name} onChange={(e) => setName(e.target.value)}
style={{ fontFamily: fontStack('Faruma') }} />
<textarea ref={notes} />
<button onClick={() => setDv(!dv)}>{dv ? 'ދިވެހި' : 'EN'}</button>
</>
);
}npm i @yashau/thaana-svelte<script>
import { ThaanaInput, thaana, thaanaKeyboard, loadFonts, fontStack } from '@yashau/thaana-svelte';
let name = $state('');
let dv = $state(true);
loadFonts({ family: 'Faruma' });
</script>
<ThaanaInput {dv} bind:value={name} style="font-family: {fontStack('Faruma')}" />
<!-- or on any element -->
<textarea use:thaana={{ enabled: dv }}></textarea>
<div contenteditable {@attach thaanaKeyboard({ enabled: dv })}></div>npm i @yashau/thaana-vue<script setup>
import { ref } from 'vue';
import { ThaanaInput, vThaana, useDhivehiFont, fontStack } from '@yashau/thaana-vue';
const name = ref('');
const dv = ref(true);
useDhivehiFont('Faruma');
</script>
<template>
<ThaanaInput v-model="name" :dv="dv" :style="{ fontFamily: fontStack('Faruma') }" />
<textarea v-thaana="{ enabled: dv }"></textarea>
</template>
<!-- or globally: app.use(ThaanaPlugin) -->Helpers
| Function | What it's for |
|---|---|
| attachKeyboard(el, { enabled }) | Phonetic keyboard on an input, textarea or contenteditable. Returns detach(). |
| autoAttach() / import "@yashau/thaana/auto" | Attach to every [data-thaana] field, including ones added later. |
| <ThaanaText text={…} /> | React/Svelte/Vue: show Thaana right-to-left with lang="dv" and numbers isolated, even when it starts with a Latin word. |
| thaanaForInputEvent(event) | For rich-text editors (Lexical, TipTap, ProseMirror): the Thaana character to insert, or null. |
| transliterate("divehi") | "ދިވެހި": replays keystrokes in the Divehi Phonetic layout. |
| convertLegacy(text, { layout }) | Text typed in old A_ / phonetic fonts → Unicode Thaana. |
| repairPdfText(text) | Fix Thaana copied out of PDFs: reversed dates and times, spacing, list markers. |
| normalizePunctuation(text) | Latin , ; ? → ، ؛ ؟ after Thaana; no space before punctuation or inside brackets. |
| isolateNumbers(text) | Keep -5, +960 333-1234, 9:00, 1,000, 50% intact inside RTL text (display only). |
| isolate(value, dir) | Wrap a URL, email or name in a bidi isolate (when you can't use <bdi>). |
| stripBidiControls(text) | Remove invisible bidi marks before saving or comparing. |
| hasThaana · hasRtl · isMixedScript | Script detection. |
| detectDirection(text) · resolveDirection(text, explicit, fallback) | First-strong (like dir="auto") vs rihaPDF's rule: any Thaana → RTL; no letters → fallback. |
| loadFonts({ family, weights }) | Load a dvfonts family; resolves when it can be drawn (canvas, SVG). |
| fontStack(family) | A font-family with a Latin fallback and installed Thaana fonts. |
| PANGRAMS · LETTERS · ARABIC_LETTERS · FILI | Sample text and the alphabet. |
Plus @yashau/thaana/thaana.css: right-to-left, a line height that doesn't clip fili, no fake italics,
and per-paragraph direction for :lang(dv). Also at /thaana.css.
For tools and agents
- /llms.txt: every recipe and the whole font list, for AI agents.
- /fonts.json: every family with weights, license, CSS URL and file URLs.
- /keymap.json: the keyboard layout as data.