Render beautiful Tibetan typography
without ever leaving your HTML.
A utility-first CSS framework packed with classes like
tr-jomolhari,
tr-guard,
tr-text-rainbow and
tr-glow-aurora
that can be composed to render any Tibetan script, directly in your markup.
termaUI is currently a working prototype. We are actively seeking funding to bring it to full production.
Installation
Every font stack, guardian fix, text effect, and pecha layout you see on termafoundry.com is rendered using termaUI's prototype stylesheet. The framework works — this site is the proof of concept.
Framework Guides
termaUI is framework-agnostic. It's a CSS file plus an optional JS utility — it works in any environment that renders HTML.
Every termaUI class uses a tr- prefix. There are no naming conflicts with Tailwind, Bootstrap, or any other utility framework. Just load both stylesheets and combine classes freely on the same element.
<!-- Tailwind classes and termaUI tr-* classes coexist on the same element -->
<div class="p-4 rounded-xl bg-slate-900 tr-glass">
<p class="text-sm text-slate-400 tr-jomolhari tr-guard tr-text-rainbow" lang="bo">
བཀྲ་ཤིས་བདེ་ལེགས།
</p>
</div> Vanilla HTML
Drop the CDN link and optional script into your <head>. No build step needed.
<head>
<!-- 1. CSS -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/termaui/dist/termaui.min.css">
<!-- 2. terma.js (required for correct line breaking) -->
<script src="https://cdn.jsdelivr.net/npm/termaui/dist/terma.js"></script>
</head>
<body>
<p class="tr-jomolhari tr-guard" lang="bo">བཀྲ་ཤིས་བདེ་ལེགས།</p>
<script>
document.addEventListener('DOMContentLoaded', () => terma.prepareAll());
</script>
</body> Next.js (App Router)
Import the CSS in your root layout. terma.prepareAll() needs DOM access, so run it in a 'use client' component inside useEffect.
npm install termaui import 'termaui/dist/termaui.css';
// rest of your layout... 'use client';
import { useEffect } from 'react';
import terma from 'termaui';
export function TibetanInit() {
useEffect(() => {
terma.prepareAll();
}, []);
return null;
} <TibetanInit /> inside the <body> of your root layout. It renders nothing — it just runs terma.prepareAll() on mount and after each navigation.Next.js (Pages Router)
import 'termaui/dist/termaui.css';
import { useEffect } from 'react';
import { useRouter } from 'next/router';
import terma from 'termaui';
export default function App({ Component, pageProps }) {
const router = useRouter();
useEffect(() => {
terma.prepareAll();
router.events.on('routeChangeComplete', () => terma.prepareAll());
return () => router.events.off('routeChangeComplete', () => terma.prepareAll());
}, [router]);
return <Component {...pageProps} />;
} Astro
Import the CSS in your base layout's frontmatter. Call terma.prepareAll() in a client <script> — Astro bundles it automatically.
---
import 'termaui/dist/termaui.css';
---
<html><body>
<slot />
<script>
import terma from 'termaui';
terma.prepareAll();
</script>
</body></html> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/termaui/dist/termaui.min.css">
<script src="https://cdn.jsdelivr.net/npm/termaui/dist/terma.js" is:inline></script>
<script is:inline>
document.addEventListener('astro:page-load', () => terma.prepareAll());
</script> astro:page-load (not DOMContentLoaded) when View Transitions are enabled — it fires after every client-side navigation.Vite + React
import 'termaui/dist/termaui.css';
// rest of your entry point... import { useEffect } from 'react';
import terma from 'termaui';
export default function App() {
useEffect(() => {
terma.prepareAll();
}, []);
// ...your JSX
} Vite + Vue
import 'termaui/dist/termaui.css';
import { createApp } from 'vue';
// rest of your entry point... <script setup>
import { onMounted } from 'vue';
import terma from 'termaui';
onMounted(() => {
terma.prepareAll();
});
</script>
<template><slot /></template> SvelteKit
<script>
import 'termaui/dist/termaui.css';
import { onMount } from 'svelte';
import terma from 'termaui';
onMount(() => {
terma.prepareAll();
});
</script>
<slot /> Quick Start
Three things to render Tibetan properly: the stylesheet, lang="bo" on your element, and the classes you need.
<!-- 1. Add the stylesheet -->
<link rel="stylesheet" href="termaui.css">
<!-- 2. Use lang="bo" + termaUI classes -->
<p class="tr-jomolhari tr-guard" lang="bo">
བཀྲ་ཤིས་བདེ་ལེགས།
</p> Why lang="bo"?
termaUI uses the :lang(bo) selector to apply sensible defaults — font family, line-height, word-breaking — to all Tibetan content. Always set lang="bo" on elements containing Tibetan text, or on a parent <div> wrapping them.
The base reset provides: font-family: Jomolhari, line-height: 1.8, word-break: keep-all, overflow-wrap: anywhere, and font-size: 1.1em. These kick in automatically with lang="bo".
Fonts
36 Tibetan typefaces — each available via a single tr-* class. Each class sets font-family with correct WOFF2 fallback chains. All fonts are loaded on demand using unicode-range: U+0F00-0FFF, so Latin text is never affected.
<p class="tr-jomolhari tr-guard" lang="bo">བཀྲ་ཤིས་བདེ་ལེགས།</p>
<p class="tr-monlam tr-guard" lang="bo">བཀྲ་ཤིས་བདེ་ལེགས།</p>
<p class="tr-drutsa tr-guard" lang="bo">བཀྲ་ཤིས་བདེ་ལེགས།</p>
Noto Serif Tibetan also ships a full weight axis: .tr-noto-thin, .tr-noto-light, .tr-noto-medium, .tr-noto-semibold, .tr-noto-bold, .tr-noto-extrabold, .tr-noto-black.
· Preview all 36 fonts with live text →
Effects
Visual effects that work with Tibetan script — gradient text, glows, outlines, and glass panels. These use standard CSS techniques (background-clip: text, text-shadow, backdrop-filter) so they compose with any font class.
.tr-text-rainbow
Multi-color horizontal gradient across the text. Uses background-clip: text.
བཀྲ་ཤིས་བདེ་ལེགས། བོད་ཀྱི་སྐད་ཡིག
<p class="tr-jomolhari tr-guard tr-text-rainbow" lang="bo">
བཀྲ་ཤིས་བདེ་ལེགས།
</p> .tr-text-saffron
Warm saffron-to-gold gradient inspired by monastic robes. Ideal for spiritual and ceremonial text.
བཀྲ་ཤིས་བདེ་ལེགས། བོད་ཀྱི་སྐད་ཡིག
<p class="tr-jomolhari tr-guard tr-text-saffron" lang="bo">
བཀྲ་ཤིས་བདེ་ལེགས།
</p> .tr-text-ink
Subtle dark vertical gradient evoking traditional ink on paper. Best on light backgrounds.
བཀྲ་ཤིས་བདེ་ལེགས། བོད་ཀྱི་སྐད་ཡིག
<!-- Use on a light background -->
<p class="tr-jomolhari tr-guard tr-text-ink" lang="bo">
བཀྲ་ཤིས་བདེ་ལེགས།
</p> .tr-glow-aurora
Pulsing teal aurora glow with text-shadow animation. Can be combined with gradient text using the data-text attribute.
བཀྲ་ཤིས་བདེ་ལེགས། བོད་ཀྱི་སྐད་ཡིག
<p class="tr-jomolhari tr-guard tr-glow-aurora" lang="bo">
བཀྲ་ཤིས་བདེ་ལེགས།
</p>
<!-- Combine with gradient text: add data-text -->
<h1 class="tr-text-rainbow tr-glow-aurora"
data-text="བཀྲ་ཤིས་བདེ་ལེགས།"
lang="bo">
བཀྲ་ཤིས་བདེ་ལེགས།
</h1> .tr-outline-gold
Gold stroke outline with transparent fill. Uses -webkit-text-stroke. For a filled version, use .tr-outline-gold-fill.
བཀྲ་ཤིས་བདེ་ལེགས། བོད་ཀྱི་སྐད་ཡིག
<p class="tr-jomolhari tr-guard tr-outline-gold" lang="bo">
བཀྲ་ཤིས་བདེ་ལེགས།
</p>
<!-- Filled version (gold stroke + dark fill) -->
<p class="tr-jomolhari tr-guard tr-outline-gold-fill" lang="bo">
བཀྲ་ཤིས་བདེ་ལེགས།
</p> .tr-glass
Frosted glass panel with backdrop-filter: blur. Apply to a container, not text. Also available as .tr-glass-dark for darker backgrounds.
བཀྲ་ཤིས་བདེ་ལེགས། བོད་ཀྱི་སྐད་ཡིག་དཔེ་མཚོན།
<div class="tr-glass">
<p class="tr-jomolhari tr-guard" lang="bo">
བཀྲ་ཤིས་བདེ་ལེགས།
</p>
</div>
<!-- Dark variant -->
<div class="tr-glass-dark">...</div> .tr-shimmer
Animated metallic gold sweep across text. A wide gold gradient animates left-to-right continuously, giving the glyphs a living shine. Uses background-clip: text and @keyframes.
ཨོཾ་མ་ཎི་པདྨེ་ཧཱུྃ།
<p class="tr-jomolhari tr-guard tr-shimmer" lang="bo">
ཨོཾ་མ་ཎི་པདྨེ་ཧཱུྃ།
</p> .tr-text-fire
Bottom-to-top flame gradient — deep crimson at the base rising through orange to bright gold at the tips. A subtle brightness flicker animation adds living fire. Uses background-clip: text.
ཨོཾ་མ་ཎི་པདྨེ་ཧཱུྃ།
<p class="tr-jomolhari tr-guard tr-text-fire" lang="bo">
ཨོཾ་མ་ཎི་པདྨེ་ཧཱུྃ།
</p> .tr-text-lapis
Deep ultramarine gradient echoing lapis lazuli — the sacred blue pigment ground from Himalayan stone and used in Tibetan thangka paintings for centuries. Uses background-clip: text.
ཨོཾ་མ་ཎི་པདྨེ་ཧཱུྃ།
<p class="tr-jomolhari tr-guard tr-text-lapis" lang="bo">
ཨོཾ་མ་ཎི་པདྨེ་ཧཱུྃ།
</p> .tr-emboss
Carved-stone effect using multi-layer text-shadow. A light source from above creates a highlight on the upper edges and a deep shadow below, simulating script cut into warm sandstone. Unlike gradient effects, this uses color so text-shadow works natively.
ཨོཾ་མ་ཎི་པདྨེ་ཧཱུྃ།
<p class="tr-jomolhari tr-guard tr-emboss" lang="bo">
ཨོཾ་མ་ཎི་པདྨེ་ཧཱུྃ།
</p> Guardian Utilities
Tibetan script has tall vowel marks (གི, གུ, གེ) and stacked consonants that get clipped by tight layouts. Guardian utilities fix these rendering issues.
.tr-guard
Adds vertical padding (padding-block: 0.25em) to prevent clipping of vowel marks and stacked consonants. Since v0.2.0, this is included in [lang="bo"] defaults — you no longer need to add .tr-guard to every element. It remains useful when you need the guard on a non-Tibetan container, or use .tr-guard-lg (0.5em) for extra-large headings.
<p class="tr-jomolhari tr-guard" lang="bo">རྒྱལ་བའི་བསྟན་པ་རིན་པོ་ཆེ།</p>
<!-- For headings / large text -->
<h1 class="tr-jomolhari tr-guard-lg" lang="bo">...</h1> .tr-overflow-safe
Last-resort overflow protection for constrained containers. Prevents Tibetan text from spilling out of its box when no break opportunities exist.
Important: this is not tsheg-aware line breaking. It uses overflow-wrap: anywhere which breaks at byte boundaries — potentially mid-syllable — as a fallback. For correct line-breaking at tsheg, use terma.prepare() from terma.js.
<div class="tr-overflow-safe" style="max-width: 300px">
<p class="tr-jomolhari tr-guard" lang="bo">
བཅོམ་ལྡན་འདས་མ་ཤེས་རབ་ཀྱི་ཕ་རོལ་ཏུ་ཕྱིན་པའི་སྙིང་པོ།
</p>
</div> .tr-scale-up
Scales Tibetan text to 120% to match the visual weight of surrounding English text. Use .tr-scale-match (150%) for headings or when Tibetan needs more presence.
The mantra ཨོཾ་མ་ཎི་པདྨེ་ཧཱུྃ། is sacred.
The mantra ཨོཾ་མ་ཎི་པདྨེ་ཧཱུྃ། is sacred.
<p>The mantra
<span class="tr-jomolhari tr-guard tr-scale-up tr-baseline" lang="bo">
ཨོཾ་མ་ཎི་པདྨེ་ཧཱུྃ།
</span> is sacred.
</p>
<!-- 150% for headings -->
<h1><span class="tr-jomolhari tr-scale-match" lang="bo">...</span></h1> .tr-baseline
Fixes vertical alignment when Tibetan appears inline with English. Sets vertical-align: middle and line-height: 1.8. Always pair with .tr-scale-up for inline use.
In Tibetan, "hello" is བཀྲ་ཤིས་བདེ་ལེགས། — a common greeting.
<p>In Tibetan, "hello" is
<span class="tr-jomolhari tr-guard tr-scale-up tr-baseline" lang="bo">
བཀྲ་ཤིས་བདེ་ལེགས།
</span> — a common greeting.
</p> .tr-shad-pair
Prevents double-shad (། །) from splitting across lines. Wrap the sequence in a <span>. Double-shad marks verse endings and must never be separated.
བྱང་ཆུབ་སེམས་དཔའི་སྡེ་སྣོད་། །ཡང་དག་པའི་མཐའ་ཞེས་བྱ་བ།
...སྡེ་སྣོད་<span class="tr-shad-pair">། །</span>ཡང་དག... .tr-nobr-tsheg
Protects non-breaking tsheg + shad sequences (e.g. ང་།) from splitting. The tsheg before shad must never cause a line break. Also fixable via terma.prepare() which auto-replaces with non-breaking tsheg (U+0F0C).
...རིང་<span class="tr-nobr-tsheg">ང་།</span> ... Typography
Font sizes, line heights, letter spacing, and alignment utilities tuned for Tibetan script metrics. Each font size class pairs font-size with a line-height optimized for Tibetan's tall vowel marks and stacked consonants.
Font Sizes
T-shirt scale from .tr-text-sm to .tr-text-5xl. Each size sets both font-size and a paired line-height — larger text uses tighter leading, smaller text gets more breathing room.
<p class="tr-jomolhari tr-guard tr-text-sm" lang="bo">...</p> /* 0.85rem / 1.6 */
<p class="tr-jomolhari tr-guard tr-text-base" lang="bo">...</p> /* 1.1rem / 1.8 */
<p class="tr-jomolhari tr-guard tr-text-lg" lang="bo">...</p> /* 1.4rem / 1.8 */
<p class="tr-jomolhari tr-guard tr-text-xl" lang="bo">...</p> /* 1.75rem / 1.7 */
<p class="tr-jomolhari tr-guard tr-text-2xl" lang="bo">...</p> /* 2.25rem / 1.6 */
<p class="tr-jomolhari tr-guard tr-text-3xl" lang="bo">...</p> /* 3rem / 1.5 */
<p class="tr-jomolhari tr-guard tr-text-4xl" lang="bo">...</p> /* 4rem / 1.4 */
<p class="tr-jomolhari tr-guard tr-text-5xl" lang="bo">...</p> /* 5rem / 1.3 */ .tr-leading-*
Standalone line-height overrides. Use .tr-leading-relaxed for verse and liturgical text, .tr-leading-loose for spaced meditation text.
<!-- Verse text with relaxed spacing -->
<div class="tr-jomolhari tr-guard tr-text-lg tr-leading-relaxed" lang="bo">
<p>བྱང་ཕྱོགས་མུན་པའི་སྨག་རུམ་ན། །</p>
<p>གངས་ལ་ཉི་མ་ཤར་འདྲ་བའི། །</p>
</div> Text Alignment
.tr-text-left, .tr-text-center, .tr-text-right, .tr-text-justify — standard alignment utilities.
<p class="tr-jomolhari tr-guard tr-text-center" lang="bo">
བཀྲ་ཤིས་བདེ་ལེགས།
</p> .tr-indent
Adds text-indent: 2em for traditional Tibetan prose paragraphs.
བཅོམ་ལྡན་འདས་མ་ཤེས་རབ་ཀྱི་ཕ་རོལ་ཏུ་ཕྱིན་པའི་སྙིང་པོ། འདི་སྐད་བདག་གིས་ཐོས་པ་དུས་གཅིག་ན། བཅོམ་ལྡན་འདས་རྒྱལ་པོའི་ཁབ་བྱ་རྒོད་ཕུང་པོའི་རི་ལ།
<p class="tr-jomolhari tr-guard tr-indent" lang="bo">
བཅོམ་ལྡན་འདས་མ་ཤེས་རབ་ཀྱི་ཕ་རོལ...
</p> .tr-justify-bo
Tibetan justification. Requires terma.prepare() — without it, this class does nothing useful. Browsers treat all Tibetan text as a single unbreakable word, so text-align: justify has no break points to work with. terma.prepare() injects zero-width spaces after each tsheg, giving the browser the break opportunities it needs. This class then applies the alignment.
བཅོམ་ལྡན་འདས་མ་ཤེས་རབ་ཀྱི་ཕ་རོལ་ཏུ་ཕྱིན་པའི་སྙིང་པོ། འདི་སྐད་བདག་གིས་ཐོས་པ་དུས་གཅིག་ན། བཅོམ་ལྡན་འདས་རྒྱལ་པོའི་ཁབ་བྱ་རྒོད་ཕུང་པོའི་རི་ལ། དགེ་སློང་གི་དགེ་འདུན་ཆེན་པོ་དང་། བྱང་ཆུབ་སེམས་དཔའི་དགེ་འདུན་ཆེན་པོ་དང་ཐབས་གཅིག་ཏུ་བཞུགས་ཏེ།
<p class="tr-jomolhari tr-guard tr-justify-bo" lang="bo">
བཅོམ་ལྡན་འདས་མ་ཤེས་རབ་ཀྱི་ཕ་རོལ...
</p> Stacking & Complex Script
Tibetan consonants stack vertically within syllables using Unicode subjoined characters (U+0F90-U+0FBC). Sanskrit mantras and dharani contain especially complex stacks — 3 or 4 consonants deep — that need extra vertical room and explicit OpenType feature support.
.tr-stack-safe
Extra line-height and padding for text containing complex consonant stacks. Prevents clipping of tall Sanskrit mantra syllables that extend well below the baseline.
ཨོཾ་མ་ཎི་པདྨེ་ཧཱུྃ། ཏདྱ་ཐཱ། ཨོཾ་གཏེ་གཏེ་པཱ་ར་གཏེ་པཱ་ར་སཾ་གཏེ་བོ་དྷི་སྭཱ་ཧཱ།
<!-- Text with complex stacks needs extra room -->
<p class="tr-jomolhari tr-guard tr-stack-safe tr-ligatures" lang="bo">
ཨོཾ་མ་ཎི་པདྨེ་ཧཱུྃ། ཏདྱ་ཐཱ།...
</p> .tr-ligatures
Explicitly enables OpenType ligature features (blws — Below Base Substitution, abvs — Above Base Substitution) that fonts use to compose vertical consonant stacks. Without these, some browsers may render stacked consonants as separate glyphs on the baseline. Since v0.2.0, these features are included in [lang="bo"] defaults — .tr-ligatures is now optional for standard Tibetan elements.
རྒྱུ། སྒྲུབ། བསྒྲིགས། སྤྱོད།
<p class="tr-jomolhari tr-guard tr-ligatures" lang="bo">
རྒྱུ། སྒྲུབ། བསྒྲིགས། སྤྱོད།
</p> .tr-yig-chung
Commentary text (ཡིག་ཆུང་།) — traditional 25% size reduction with letter-heads aligned to the top of root text. Use as an inline <span> within root text.
བཅོམ་ལྡན་འདས་མ་ཤེས་རབ་ཀྱི་ཕ་རོལ་ཏུ་ཕྱིན་པའི་སྙིང་པོ། འདི་སྐད་བདག་གིས་ཐོས་པ་དུས་གཅིག་ན། བཅོམ་ལྡན་འདས་རྒྱལ་པོའི་ཁབ་བྱ་རྒོད་ཕུང་པོའི་རི་ལ། དགེ་སློང་གི་དགེ་འདུན་ཆེན་པོ་དང་།
<p class="tr-jomolhari tr-guard tr-ligatures" lang="bo">
བཅོམ་ལྡན་འདས་མ་ཤེས་རབ་ཀྱི...
<span class="tr-yig-chung">འདི་སྐད་བདག་གིས་ཐོས...</span>
དགེ་སློང་གི...
</p> Pecha Styling
Traditional Tibetan loose-leaf manuscript format (དཔེ་ཆ།). Authentic three-column layout: abbreviated title (ཆིང་) in the left margin, main text in the center, folio number on the right — all separated by vertical red rules. Wide ~6:1 landscape ratio with double red border.
.tr-pecha
Main container: 6:1 aspect ratio, double red border, three-column grid. Use .tr-pecha-title for the left margin, .tr-pecha-body for the text, and .tr-pecha-folio for the right folio number.
<div class="tr-pecha tr-jomolhari tr-guard" lang="bo">
<!-- Left: abbreviated title (ching) -->
<div class="tr-pecha-title">ཤེས་རབ་སྙིང་པོ།</div>
<!-- Center: main text -->
<div class="tr-pecha-body">༄༅། །བཅོམ་ལྡན་འདས་མ་ཤེས་རབ་ཀྱི་ཕ་རོལ...</div>
<!-- Right: folio number -->
<div class="tr-pecha-folio">༢</div>
</div> .tr-pecha-dark
Gold text on dark indigo — sacred text variant (གསེར་ཡིག). The amber column separators adapt automatically.
<div class="tr-pecha tr-pecha-dark tr-jomolhari tr-guard" lang="bo">
<div class="tr-pecha-title">རྡོ་རྗེ་གཅོད་པ།</div>
<div class="tr-pecha-body">༄༅། །བཅོམ་ལྡན་འདས་མ...</div>
<div class="tr-pecha-folio">༥</div>
</div> .tr-pecha-title / .tr-pecha-folio / .tr-yig-mgo
.tr-pecha-title — left margin column for the abbreviated title (ཆིང་). .tr-pecha-folio — right margin column for the folio number (ཤོག་གྲངས་). .tr-yig-mgo — add this class to a <span> to automatically prepend the traditional opening mark ༄༅།.
<div class="tr-pecha tr-jomolhari tr-guard" lang="bo">
<div class="tr-pecha-title">བར་དོ་ཐོས་གྲོལ།</div>
<div class="tr-pecha-body">
<span class="tr-yig-mgo"></span>བར་དོ་ཐོས་གྲོལ...
</div>
<div class="tr-pecha-folio">༣</div>
</div> .tr-pecha-title-rotated
Optional modifier for the title column. Rotates the label 90° counter-clockwise using transform: rotate(-90deg) — uses transform rather than writing-mode for reliable Tibetan glyph rendering.
<div class="tr-pecha-title">
<span class="tr-pecha-title-rotated">ཤེས་རབ།</span>
</div> terma.js
JavaScript companion for termaUI that fixes problems CSS alone cannot solve. The CSS classes work without it — fonts, effects, sizes, guardian utilities, pecha, and all visual styling are CSS-only. But for correct Tibetan line breaking you need terma.js. Without it, browsers break text at every tsheg, splitting multi-syllable words mid-word by default.
.tr-jomolhari etc.).tr-text-rainbow, .tr-shimmer etc.).tr-guard, .tr-scale-up etc.).tr-text-xl, .tr-leading-* etc.).tr-pecha, .tr-pecha-dark etc.).tr-stack-safe, .tr-ligatures etc.).tr-shad-pair (manual wrapping).tr-justify-bo — does nothing without terma.prepare().tr-nobr-tsheg — CSS partial fix only; terma.js automates correctlycontenteditable editorsterma-clusters.js)terma.prepare(element)
Processes all Tibetan text inside the given element:
- Inserts zero-width spaces after tsheg (་) for proper line-break opportunities
- Replaces tsheg before shad with non-breaking tsheg (U+0F0C) to prevent ང་། splitting
- Wraps double-shad (། །) with word-joiners so they stay together
<!-- Include the script (CDN) -->
<script src="https://cdn.jsdelivr.net/npm/termaui/dist/terma.js"></script>
<!-- Process a single element -->
<script>
terma.prepare(document.querySelector('[lang="bo"]'));
</script>
<!-- Or process all Tibetan text on the page -->
<script>
terma.prepareAll();
</script> Combine with .tr-justify-bo for the best Tibetan justification: terma.prepare() gives browsers break points, and the CSS class handles the alignment.
terma.prepareEditable(element)
Prepares a contenteditable element for live Tibetan text entry. Calls prepare() immediately, then re-applies it on every input event (debounced 150 ms) so zero-width spaces survive further typing. Use instead of prepare() for any live-edit Tibetan surface — note-taking UIs, typing tutors, rich text editors.
termaUI automatically adds white-space: pre-wrap to [contenteditable][lang="bo"] elements to prevent ZWS from being collapsed by the browser.
<!-- HTML -->
<div contenteditable="true" lang="bo" class="tr-jomolhari" id="editor"></div>
// JS — one call, no listener boilerplate
terma.prepareEditable(document.getElementById('editor'));
// Or target all [contenteditable][lang="bo"] at once
terma.prepareAllEditables(); terma.cluster(element)
Prevents dotted-circle artifacts (◌) — phantom characters that appear when a browser tries to display a combining mark (vowel sign, subjoined consonant) with no base character before it. This happens when Tibetan text is built character-by-character via JavaScript DOM operations (e.g. textContent += char).
Requires terma-clusters.js to be loaded alongside terma.js. When both are present, terma.prepare() calls clustering automatically — you only need cluster() when you want clustering without the full prepare pipeline.
<!-- Load both scripts -->
<script src="https://cdn.jsdelivr.net/npm/termaui/dist/terma-clusters.js"></script>
<script src="https://cdn.jsdelivr.net/npm/termaui/dist/terma.js"></script>
<!-- terma.prepare() now auto-clusters on every call -->
<script>
terma.prepareAll(); // clusters + line-break fixes in one call
</script> terma.normalize(element)
Applies Unicode NFC normalization to all text inside the element. Solves the "invisible search failure" problem: the same Tibetan syllable can be encoded as different Unicode sequences that look identical but compare as unequal strings. Normalizing to NFC before storing or searching ensures consistent matching.
- Fixes mismatches between pre-composed and decomposed sequences
- Run on user input before database queries, or on content at render time
- Note: NFC does not fix wrong character order (dotted circle bugs) — that requires fixing the source data or input method
// Normalize a single element
terma.normalize(document.querySelector('[lang="bo"]'));
// Or normalize all Tibetan text on the page
terma.normalizeAll();
// Normalize a string directly (e.g. before a search query)
const query = userInput.normalize('NFC'); terma.alignHeadMarks(element)
Pixel-perfect head-mark alignment for mixed font sizes. Tibetan script aligns from the head mark (མགོ་ཅན།) at the top of each letter, but CSS uses baseline (bottom) alignment. When differently-sized Tibetan spans appear inline, head marks scatter vertically. This function fixes that.
prepare() calls this automatically as its final step — you only need alignHeadMarks() when you change font sizes dynamically after prepare() has already run.
- CSS Layer 1 (instant, ~95%): termaui.css applies
vertical-align: text-topto sized spans inside[lang="bo"]— works without JavaScript - JS Layer 2 (pixel-perfect): Measures each font's ascent ratio via Canvas
TextMetricsand computes a per-spanvertical-alignoffset to within 0.04 px - Latin-only spans are detected and classified as
.tt-latin— reverted to baseline so English text is unaffected - Automatically re-runs on
document.fonts.readyto correct measurements after webfont swap
// Re-align after dynamically changing a span's font-size
terma.alignHeadMarks(document.querySelector('[lang="bo"]'));
// Or re-align all Tibetan containers on the page
terma.alignHeadMarksAll();
// Measure a font's ascent ratio (advanced use)
const ratio = terma.measureAscentRatio('Jomolhari');
// → 0.740 (Jomolhari's head-mark height as fraction of font-size) Mixed Language Patterns
Real-world apps mix Tibetan and English. Here are the recommended patterns.
Inline Tibetan in English text — .tr-mixed
Add .tr-mixed to any inline Tibetan <span>. It scales the Tibetan text to 1.15em and shifts the baseline by -0.15em so both scripts sit on the same visual line — no manual font-size or vertical-align needed.
The Heart Sutra (ཤེས་རབ་སྙིང་པོ།) is one of the most important texts in Mahayana Buddhism. Its full title in Tibetan is བཅོམ་ལྡན་འདས་མ་ཤེས་རབ་ཀྱི་ཕ་རོལ་ཏུ་ཕྱིན་པའི་སྙིང་པོ།
<p>The Heart Sutra
(<span class="tr-jomolhari tr-mixed" lang="bo">
ཤེས་རབ་སྙིང་པོ།
</span>) is one of the most
important texts in Mahayana Buddhism.
</p> Tibetan block in English page
For a full block of Tibetan text (a verse, prayer, or paragraph), use a <div> or <blockquote> with lang="bo".
Milarepa's Homage:
ན་མོ་གུ་རུ།
མར་པ་ལོ་ཙཱ་བཀའ་དྲིན་ཅན།
མི་ལ་རས་པ་རྣལ་འབྱོར་པ།
<blockquote class="tr-jomolhari tr-guard tr-overflow-safe" lang="bo">
<p>ན་མོ་གུ་རུ།</p>
<p>མར་པ་ལོ་ཙཱ་བཀའ་དྲིན་ཅན།</p>
<p>མི་ལ་རས་པ་རྣལ་འབྱོར་པ།</p>
</blockquote> UI labels and navigation
For bilingual interfaces — nav items, buttons, labels — use the inline pattern at smaller sizes.
<nav>
<a href="/home">
Home / <span class="tr-noto tr-guard tr-scale-up tr-baseline" lang="bo">གཙོ་ངོས།</span>
</a>
<a href="/about">
About / <span class="tr-noto tr-guard tr-scale-up tr-baseline" lang="bo">སྐོར།</span>
</a>
</nav> Font size normalization
Since v0.2.0, all 44 @font-face declarations include a size-adjust descriptor measured against Jomolhari as the reference. Fonts that naturally render large (e.g. Qomolangma-Drutsa) are scaled down; fonts that render small (e.g. Monlam Uni OuChan variants) are scaled up. The result: you can swap fonts with a single class change and body text stays the same visual size — no per-font font-size overrides needed.
<!-- All three render at the same visual size — no font-size override -->
<p class="tr-jomolhari tr-text-lg" lang="bo">བཀྲ་ཤིས་བདེ་ལེགས།</p>
<p class="tr-noto tr-text-lg" lang="bo">བཀྲ་ཤིས་བདེ་ལེགས།</p>
<p class="tr-babelstone tr-text-lg" lang="bo">བཀྲ་ཤིས་བདེ་ལེགས།</p> Cheat sheet — v0.2.0
Quick reference for the most common class combinations. Since v0.2.0, .tr-guard and .tr-ligatures are included in [lang="bo"] defaults and no longer need to be listed explicitly.
/* Body text */
tr-jomolhari
/* Inline Tibetan in English — auto-scales & aligns */
tr-jomolhari tr-mixed
/* Decorative heading */
tr-drutsa tr-guard-lg tr-text-saffron
/* Modern UI text */
tr-noto tr-overflow-safe
/* Verse in a glass card */
tr-glass > tr-jomolhari tr-overflow-safe
/* Live contenteditable editor */
tr-jomolhari + contenteditable lang="bo" + terma.prepareEditable(el) What's New
v0.3.0 — Lean package, extended font library
- npm package reduced from 12.9 MB → ~600 KB — only 4 curated fonts now ship with the npm package: Jomolhari, Monlam Bodyig, Qomolangma-UchenSarchen, and Qomolangma-Drutsa. These cover traditional Uchen, modern digital Uchen, scholarly Uchen, and cursive (drutsa) — the essential four.
- Extended font library (
termaui-fonts) — the remaining 40+ fonts are available as a separate CDN-only package. Browsers only download fonts that are actually used on the page, so there is no performance penalty for having a large font list in the stylesheet. - Fallbacks updated — all
font-familyfallbacks in utility classes now reference only bundled fonts. If an extended font fails to load, Jomolhari or Qomolangma-Drutsa takes over gracefully. :lang(bo)default stack updated — base font stack is nowJomolhari, Qomolangma-UchenSarchen, Monlam Bodyig, serif.
v0.2.0
CSS
size-adjuston all 44@font-facedeclarations — every Tibetan font is normalized against Jomolhari (ascent = 74 px reference) so swapping fonts with a single class change keeps body text at the same visual size[lang="bo"]defaults now include.tr-guardand.tr-ligatures— padding-block and OpenType ligatures are applied automatically; these classes are no longer required on every element.tr-mixed— new utility for inline Tibetan inside English text; auto-scales to1.15emand shifts baseline by-0.15em- Head-mark alignment (Layer 1) — sized spans inside
[lang="bo"]getvertical-align: text-topautomatically; Latin-only spans classified as.tt-latinrevert to baseline
terma.js
terma.prepareEditable(element)— prepares acontenteditableelement for live Tibetan text entry with debounced re-processing on every input eventterma.prepareAllEditables()— convenience wrapper that targets all[contenteditable][lang="bo"]elements- Auto-clustering in
prepare()— whenterma-clusters.jsis loaded,prepare()automatically wraps combining marks into.tr-clusterspans to prevent dotted-circle artifacts - Head-mark alignment (Layer 2) —
terma.prepare()now auto-aligns head marks across mixed font sizes using CanvasTextMetrics; pixel-perfect to 0.04 px terma.alignHeadMarks(el)/terma.alignHeadMarksAll()— explicit alignment API for dynamic size changes afterprepare()has already runterma.measureAscentRatio(fontFamily)— exposed Canvas ascent measurement for advanced use; cached per font family- Automatic re-alignment on
document.fonts.ready— ascent cache clears and all aligned containers re-measure after webfont swap