Why Thai text is harder than it looks#
The web is built on the assumption that words are separated by spaces — line breaking, word counting, and the default leading all rest on it. When the primary content is Thai, that assumption disappears completely, and the consequences show up one at a time with no error message attached.
- Thai does not put spaces between words. The spaces you do see mark phrase or sentence boundaries, not word boundaries.
- Glyphs stack: a consonant can carry a vowel above it and a tone mark above that, plus a vowel below. Leading that feels right in English is cramped in Thai.
- Fonts chosen for a Latin design usually have no Thai coverage, so the browser falls back to a system font at a different optical size and the page stops looking like one design.
- Any tool that counts words by splitting on whitespace will see an entire Thai paragraph as a single word.
Line breaking#
The good news is that modern browsers already break Thai using a dictionary. The catch is that they only do it when they know the text is Thai, which makes a correct lang attribute far more visually load-bearing than most people expect — and screen readers need it regardless.
javascript
:lang(th) {
/* let the browser use its dictionary; do not fight it */
line-break: auto;
word-break: normal;
/* only to stop long Latin words and URLs from overflowing */
overflow-wrap: break-word;
/* headroom for a tone mark sitting on top of a vowel */
line-height: 1.85;
}
/* never do this to Thai: it breaks inside a syllable cluster */
.dont-do-this { word-break: break-all; }- Declare the language properly with
lang="th", on the document or on the content block, and the browser handles word breaking for you. - Do not reach for
word-break: break-allon Thai text — it splits syllables and strands a vowel at the start of the next line. - The
hyphensproperty does nothing useful here, because Thai does not hyphenate across a break. - For mixed content or long links,
overflow-wrapprevents overflow without disturbing Thai word breaking. - Avoid
justifyfor Thai in narrow columns: there are no inter-word spaces to stretch, so the extra space lands in the wrong places.
Leading and spacing#
| Element | Typical English value | Better for Thai | Reason |
|---|---|---|---|
| Body text | 1.5 | 1.75 – 1.9 | Vowels and tone marks stack up to two levels above the line |
| Large headings | 1.1 | 1.25 – 1.35 | The bigger the type, the more visible the collision |
| Letter spacing | Adjust freely | Leave it at normal | Extra tracking visually detaches marks from their consonant |
| Measure | Around 65 characters | Around 40 – 45 characters | Thai carries more meaning per character of width |
Badly set Thai never throws an error. It just makes the page quietly more tiring to read, in a way readers cannot name.
Word-based reading time does not work#
javascript
// splitting on whitespace treats a whole Thai paragraph as one word,
// so count Thai characters instead and add the remaining Latin words
const thaiChars = (text.match(/[\u0E00-\u0E7F]/g) ?? []).length
const latinWords = text.replace(/[\u0E00-\u0E7F]/g, ' ').split(/\s+/).filter(Boolean).length
// roughly 400 Thai characters per minute, 230 English words per minute
const minutes = thaiChars / 400 + latinWords / 230
return Math.max(1, Math.round(minutes))- Set lang correctly on the document and on any block that switches language
- Pick a font with real Thai coverage and declare an explicit fallback
- Give Thai body text its own leading rather than inheriting the Latin default
- Compute reading time from character counts when the content is Thai
- Test wrapping on narrow screens with real Thai copy, not lorem ipsum