Theme navigation

Typography

Typography

This site sets Latin text in Geist, monospace in Geist Mono, and Chinese in Noto Sans SC. All three are OFL-licensed and served from our own origin, with the Chinese face split into 97 unicode-range subsets that load on demand. Sizes follow the Tailwind CSS v4 text-* scale.

Type scale

Each row is rendered at its real size.

Beautiful and useful Aatext-xs
Beautiful and useful Aatext-sm
Beautiful and useful Aatext-base
Beautiful and useful Aatext-lg
Beautiful and useful Aatext-xl
Beautiful and useful Aatext-2xl
Beautiful and useful Aatext-3xl
Beautiful and useful Aatext-4xl
Beautiful and useful Aatext-5xl

Font weights

Beautiful and useful Aa 123Bodyfont-normal
Beautiful and useful Aa 123Secondary emphasis and navigationfont-medium
Beautiful and useful Aa 123Headingsfont-semibold
Beautiful and useful Aa 123Strong emphasisfont-bold

Font stack

Two runtime variables drive every font in the library; no component hard-codes a family. A project that never sets them renders exactly as it did before this token layer existed.

--hl-font-sans: Geist, ui-sans-serif, system-ui, "PingFang SC", "Microsoft YaHei", sans-serif; --hl-font-mono: "Geist Mono", ui-monospace, Menlo, monospace;

Use your own fonts

Set the variables on :root and you are done. The 48 components that use font-mono follow along.

:root { --hl-font-sans: "Your Sans", ui-sans-serif, system-ui, sans-serif; --hl-font-mono: "Your Mono", ui-monospace, monospace; }

Change one region only

Set the same variable on a container. Body text and monospace are not symmetric here: monospace needs only the variable, while body text also needs a font-sans class on the container. Custom properties inherit, but a font-family that reads a variable is re-resolved only on elements that declare it, so ordinary text inherits the already-resolved family from the root and never looks at the variable again.

{/* Monospace: the variable alone is enough, since font-mono elements re-resolve it in their own scope */} <div style={{ "--hl-font-mono": '"IBM Plex Mono", monospace' }}> <Snippet>pnpm add @hulianui/ui</Snippet> </div> {/* Body text: the variable plus a font-sans class, or the text keeps the family already resolved at the root */} <div className="font-sans" style={{ "--hl-font-sans": "Georgia, serif" }}> This block is set in Georgia </div>

How the Chinese face is served

A full Noto Sans SC weighs megabytes and would stall first paint, so it is split into 97 unicode-range subsets and the browser fetches only the ones a page actually uses (15 on this page). Each subset is itself a variable font, so one set covers every weight. In the stack, the CJK family must come before ui-sans-serif and system-ui: those generic families resolve Han characters to the system font, and anything listed after them never gets a turn.

/* The stack this site actually ships: Geist for Latin, Noto Sans SC for Chinese, system fonts for the rest */ --hl-font-sans: Geist, "Noto Sans SC", ui-sans-serif, system-ui, "PingFang SC", "Microsoft YaHei", sans-serif;