Size · Line height · Line breaks · Fallbacks

Thai on the web

Thai sets differently from Latin on screen. Its vowels and tone marks stack above and below the letters, it has no spaces between words, and its letters often look smaller than Latin ones at the same size. A few lines of CSS deal with all of it. Here they are, with live examples in fonts from the collection.

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.

Language on the page and on a passage
<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.

A font stack with Thai fallbacks
/* 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.

Two files, one family, matched sizes
/* 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.

Line height and padding for Thai
: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-all lets 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: justify stretches only the spaces, and a Thai paragraph has few of them, so justified lines open up large gaps. Set Thai ragged, with text-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.

Line breaking settings for Thai
: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: none on Thai text whose font lacks the styles you use.
  • Capitals do nothing. Thai has no case, so text-transform: uppercase leaves 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-range tells 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.
Load the Thai file only for Thai text
@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.

Questions people ask

Do I need lang="th" if the page is all in Thai?

Yes: put it on the html element. Browsers use it to choose fonts for characters a font lacks, screen readers use it to pick a Thai voice, and search engines read it. On a page that mixes languages, mark each Thai passage with lang="th".

Why does Thai text look smaller than English at the same font size?

Font size sets the size of the em box, not of the letters, and Thai fonts divide that box differently from Latin ones; some, like TH Sarabun New, draw their letters noticeably small. Pair fonts by eye, and if you combine two families, scale the Thai one with size-adjust in its @font-face rule.

Can I use Thai fonts from Google Fonts or host them myself?

Both work. Hosting the files yourself avoids a request to another service and lets you control caching. Check the licence first: open licences allow self-hosting, but making a smaller copy of a font is a change to it, which some licences restrict. The licensing guide explains.