Mark up the language
Put lang="th" on Thai text, or on the html element of a Thai page. Browsers use it to pick a suitable font when yours lacks a character, screen readers to choose a Thai voice, and search engines to understand the page. CSS can then target Thai with :lang(th), which the rest of this guide uses.
<html lang="en">
…
<p lang="th">ภาษาไทยอ่านง่าย</p>
</html>A fallback stack for Thai
Name a Thai font first, then the Thai fonts the main systems ship: Leelawadee UI on Windows, Thonburi on Apple devices, and Noto Sans Thai, the Thai face of Android and many Linux systems. Put the Thai-capable fonts before any Latin-only font, or the browser may set the Latin in one font and fall back for the Thai in a way you did not choose.
/* Thai first, then the system Thai fonts, then a generic family */
body {
font-family: "Sarabun", "Noto Sans Thai", "Leelawadee UI",
"Thonburi", sans-serif;
}Matching Thai to Latin in size
The font size sets the box a font draws in, not the size of its letters, and Thai fonts use that box in very different ways. TH Sarabun New, the government font, draws its letters small, which is why Thai official documents set it at 16 point. Here are the same words at the same 22-pixel size:
ภาษาไทย and English, side by side
Sarabun, 22 px
ภาษาไทย and English, side by side
TH Sarabun New, 22 px
When you choose a Thai font to go with a Latin one, compare them in a line that mixes both, as above; the font tester does this for every font here. When you combine two families under one name, give the Thai face a size-adjust so the scripts match, and use unicode-range so each file covers only its own script.
/* One family name, two files: Latin from one font, Thai from another,
scaled so the two scripts look the same size. */
@font-face {
font-family: "Brand";
src: url("fonts/brand-latin.woff2") format("woff2");
unicode-range: U+0000-00FF, U+2000-206F;
}
@font-face {
font-family: "Brand";
src: url("fonts/sarabun-thai.woff2") format("woff2");
unicode-range: U+0E00-0E7F, U+25CC;
size-adjust: 108%;
}Line height for stacked marks
A Thai line has three tiers: the letters, the vowels and tone marks above them (sometimes two deep, as in ผู้ and ที่), and the vowels below (◌ุ ◌ู). Line heights that suit Latin text crowd those tiers into the lines above and below. For body text, 1.6 to 1.8 is a good range; headings can go tighter, but check every line with marks above and below.
ผู้ใหญ่และเด็กอ่านภาษาไทยได้สบายตาขึ้น เมื่อบรรทัดมีที่ว่างพอสำหรับสระและวรรณยุกต์
line-height: 1.2
ผู้ใหญ่และเด็กอ่านภาษาไทยได้สบายตาขึ้น เมื่อบรรทัดมีที่ว่างพอสำหรับสระและวรรณยุกต์
line-height: 1.7
Watch elements that clip their content: buttons, inputs, badges and anything with overflow: hidden. A tone mark over an upper vowel can rise above the font’s nominal line, so give such elements some padding rather than a fixed height.
:lang(th) {
line-height: 1.7; /* room for marks above and below */
}
button:lang(th),
input:lang(th) {
padding-block: 0.35em; /* stacked marks must not touch the edge */
}Line breaks without spaces
Thai puts no spaces between words; a space marks the end of a phrase. Browsers find the word boundaries themselves with a built-in Thai dictionary, so lines break between words even though there are no spaces. Two common CSS habits defeat that:
word-break: break-alllets the browser break anywhere, in the middle of a word or even between a letter and its vowel. Never use it on Thai.text-align: justifystretches only the spaces, and a Thai paragraph has few of them, so justified lines open up large gaps. Set Thai ragged, withtext-align: start.
ประเทศไทยมีภาษาถิ่นหลายภาษาและมีอักษรของตนเองที่ใช้กันมาหลายร้อยปีแล้ว
word-break: normal (breaks between words)
ประเทศไทยมีภาษาถิ่นหลายภาษาและมีอักษรของตนเองที่ใช้กันมาหลายร้อยปีแล้ว
word-break: break-all (breaks inside words)
The dictionary does not know every name or new word. Where a break matters, add a hint: <wbr> or a zero-width space (U+200B) marks a place a line may break, and a no-break space keeps two words together.
:lang(th) {
word-break: normal; /* never break-all on Thai */
overflow-wrap: anywhere; /* only rescues very long URLs or codes */
text-align: start; /* justify only stretches the few spaces */
}Bold, italic, capitals and spacing
- Italic is not a Thai tradition. Many Thai fonts have no italic, and a browser that cannot find one slants the letters itself. Use weight or colour for emphasis, and set
font-synthesis: noneon Thai text whose font lacks the styles you use. - Capitals do nothing. Thai has no case, so
text-transform: uppercaseleaves it unchanged; a design that relies on capitals for headings needs another device in Thai. - Letter-spacing loosens the fixed relationship between letters and their marks and makes words harder to read. Keep it at zero for Thai body text.
Font features
Thai fonts place vowels and tone marks with OpenType positioning rules, and split ำ into its two parts when a tone mark goes with it, so that in น้ำ the mark sits above the small circle. Browsers apply all of this by default; there is nothing to switch on. Use font-feature-settings only for optional features a font documents, such as tabular numerals (font-variant-numeric: tabular-nums is the clearer way to ask). For lists numbered in Thai digits, CSS has a ready-made counter style: list-style-type: thai.
Smaller font files
A Thai font with many weights and a full Latin character set can be large. Two techniques keep pages light:
unicode-rangetells the browser which characters a file covers, so it downloads the Thai file only when the page has Thai text.- A reduced copy of the font, keeping only the Thai block, basic Latin and the punctuation you need, can be a fraction of the size of the original. Keep U+25CC, the dotted circle, if the font has it: browsers use it to show a mark typed on its own. Making such a copy is a change to the font, so do it only where the licence allows; the OFL does, unless the font has a reserved name, and the DIP/SIPA licence asks you to notify DIP and SIPA first.
@font-face {
font-family: "Sarabun";
src: url("fonts/sarabun-thai.woff2") format("woff2");
unicode-range: U+0E01-0E5B, U+200C-200D, U+25CC;
font-display: swap;
}Every family page on this site gives copy-ready CSS for self-hosting that font, with its licence spelled out, and the catalog marks which fonts include Latin letters.