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://fonts.mv/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.
- Optional:
&normalize=1makes every font the same size (below).
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://fonts.mv/thaana.js"></script>
<input data-thaana>
<textarea data-thaana></textarea>
<div contenteditable data-thaana></div>
<!-- or a classic script: window.Thaana -->
<script src="https://fonts.mv/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) -->Same size for every font
Dhivehi fonts differ a lot in size at the same font-size: some draw their letters at half Faruma's
size, others at nearly twice. Ask for normalize and each family is scaled with CSS size-adjust so its Thaana letters look as big as Faruma's. Switching fonts then keeps your text the same
size. The font files are not changed, and it's off unless you ask for it.
<link rel="stylesheet" href="https://fonts.mv/css2?family=Bolhu&family=Faiy&display=swap&normalize=1">import { loadFonts, fontCssUrl } from '@yashau/thaana';
await loadFonts({ family: 'Bolhu' }, { normalize: true });
fontCssUrl([{ family: 'Faiy' }], { normalize: true }); // just the URLuseDhivehiFont('Bolhu', [400], { normalize: true });Each font's page has a switch that adds it to the embed code. The scale per family is in fonts.json (sizeAdjust).
Helpers
Phonetic keyboard on an input, textarea or contenteditable. Returns detach().
Attach to every [data-thaana] field, including ones added later.
React/Svelte/Vue: show Thaana right-to-left with lang="dv" and numbers isolated, even when it starts with a Latin word.
For rich-text editors (Lexical, TipTap, ProseMirror): the Thaana character to insert, or null.
"ދިވެހި": replays keystrokes in the Divehi Phonetic layout.
Text typed in old A_ / phonetic fonts → Unicode Thaana.
Fix Thaana copied out of PDFs: reversed dates and times, spacing, list markers.
Latin , ; ? → ، ؛ ؟ after Thaana; no space before punctuation or inside brackets.
Keep -5, +960 333-1234, 9:00, 1,000, 50% intact inside RTL text (display only).
Wrap a URL, email or name in a bidi isolate (when you can't use <bdi>).
Remove invisible bidi marks before saving or comparing.
Script detection.
First-strong (like dir="auto") vs rihaPDF's rule: any Thaana → RTL; no letters → fallback.
Load a font; resolves when it can be drawn (canvas, SVG). Options: same size (above) and self-hosting (below).
A font-family with a Latin fallback and installed Thaana fonts.
Sample text and the alphabet.
@font-face CSS for your own copies of the files (self-hosting).
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.
Self-hosting
Every font and collection can be downloaded from its page or the downloads page, to serve from your own server.
With the packages
Pass the URLs of your copies by weight. The packages then register the font from your files and load nothing from
fonts.mv. For normalize, also pass the family's sizeAdjust from fonts.json.
import { loadFonts, fontFaceCss } from '@yashau/thaana';
await loadFonts({ family: 'Faruma', files: { 400: '/fonts/faruma-400.woff2' } });
// Or the @font-face rules for your own stylesheet:
fontFaceCss({ family: 'Bolhu', files: { 400: '/fonts/bolhu-400.woff2', 700: '/fonts/bolhu-700.woff2' } });useDhivehiFont('Faruma', [400], { files: { 400: '/fonts/faruma-400.woff2' } });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.