WebProCMS gives every install a GUI for setting site-wide typography — body and heading fonts, optional heading accent font, per-heading size/weight/letter-spacing/line-height, text-wrap behaviour (avoid orphans, even line lengths), and font antialiasing. Editors pick from dropdowns; the CMS emits the underlying Tailwind-aligned CSS so the public site stays consistent without anyone hand-writing classes.
What editors get
The Typography settings page (/dashboard/settings/typography) exposes the full typographic surface of the public site without requiring the editor to touch CSS:
- Body font, heading font, and optional heading accent font — picked from a curated font browser
- Antialiasing mode — smooth, subpixel, or auto (subpixel on light, smooth on dark)
- Per-heading defaults for
h1–h4— size, weight, letter spacing, line height, text wrap, and an "extra classes" override field - Body defaults — size, weight, text wrap, and extra classes
Every dropdown maps to a Tailwind-aligned token. The editor never writes utility classes; the CMS turns those choices into @layer base { h1 { … } h2 { … } body { … } } CSS so individual row-level utilities (text-2xl, tracking-tight, etc.) still win where applied.
Tailwind-grade typographic controls
The power of Tailwind's typography utilities — features that the broader web only got reliable browser support for in the last few years — surfaced as plain dropdowns:
Letter spacing (tracking-*)
Six steps from tracking-tighter (−0.05em) through tracking-widest (0.1em), plus a "Default" option that emits no rule and lets the browser/font handle it. Most useful for tightening display headings (tracking-tight on a 60px hero) or loosening short caps labels (tracking-wider).
Line height (leading-*)
Six steps from leading-none (1.0) through leading-loose (2.0), plus "Default". Lets editors tighten heading line height for impact (leading-tight on multi-line h1s) or loosen body-adjacent headings for readability.
Text wrap
Modern CSS line-breaking algorithms, dropdown-exposed:
- Pretty (
text-wrap: pretty) — avoids single-word orphans on the last line. Drop this on body/paragraph defaults and headlines and the site stops finishing paragraphs with a lonely word at the bottom. - Balance (
text-wrap: balance) — distributes words evenly across lines, eliminating long-line/short-line headline ragging without manual<br>tags.
Per-heading + body-default — pick balance on h1s and pretty on body and the whole site benefits without anyone editing a row.
Antialiasing
Controls -webkit-font-smoothing / -moz-osx-font-smoothing on the public site body:
- Smooth — antialiased (Tailwind default). Best for most modern designs.
- Subpixel — browser native rendering. Slightly heavier on Windows ClearType, useful for serif body type.
- Auto — subpixel on light backgrounds, smooth on dark. Adapts to the
.darkclass so dark mode stays crisp without making light mode look washed.
Font browser
Three roles can each be assigned a font: body, heading, and optional heading accent. The accent is for highlighting a single word inside a heading (the page editor's "Aa" toolbar button) — pair Instrument Sans for headings with Caveat for one swooshy word in a hero.
The browser groups fonts by category — sans, serif, mono, display (Bebas, Anton, Abril Fatface), and handwriting (Pacifico, Caveat, Dancing Script, Great Vibes, Sacramento) — and live-loads the chosen font's stylesheet on hover so editors see real previews before saving.
Powered by Bunny Fonts (with system-font defaults)
All web fonts on the public site are served from Bunny Fonts — a privacy-first, GDPR-compliant drop-in replacement for Google Fonts. No third-party cookies, no visitor IP logging, no Google tracking. The font files are delivered from bunny.net's CDN through a standard stylesheet, the same shape as Google Fonts but without the surveillance baggage. The public layout attaches that stylesheet only after the window load event (a small inline loader in partials/head.blade.php, with a <noscript> fallback): display=swap already paints the text in the fallback face at once, but the browser fetches font files at its highest priority, so a stylesheet that is merely non-blocking still puts ~100 KB of top-priority third-party requests level with the hero image and the critical CSS — which Lighthouse counts against FCP and LCP, and which really does slow the hero on a poor connection. Deferring to load keeps the whole chain off the critical path; on a fast connection the swap lands a couple of hundred milliseconds later than before, on a slow one the hero arrives first. Measured on a two-family site: mobile PageSpeed 96 → 100, LCP 2.4 s → 1.35 s.
The default install ships with system fonts selected (Tailwind's font-sans/font-serif/font-mono stacks), which means:
- Zero external font requests
- Zero load penalty — visitors render in their OS's native UI font (San Francisco on macOS/iOS, Segoe UI on Windows, Roboto on Android)
- Zero CLS / FOUT from font loading
Only when an editor explicitly picks a web font does Bunny Fonts get involved. Choosing system fonts everywhere is a valid, performance-optimal answer for many sites.
Output: CSS, not classes
Saving the Typography page writes to branding.typography in settings and busts the branding CSS cache. On the next request, BrandingStyleService::typographyCssBlock() regenerates a CSS block that emits:
@layer base {
h1 { font-size: 3rem; font-weight: 700; letter-spacing: -0.025em; line-height: 1.25; text-wrap: balance; }
h2 { font-size: 2.25rem; font-weight: 600; line-height: 1.375; }
body { font-size: 1rem; font-weight: 400; text-wrap: pretty; }
}
The @layer base wrapper is intentional — unlayered Tailwind utilities (.text-xl, .font-bold, .tracking-tight) beat layered rules regardless of specificity, so a row template that hard-codes <h2 class="text-4xl tracking-wide"> still overrides the global default for that one element. Editors get a global baseline; designers retain per-row control.
Font-family CSS variables (--font-sans, --font-heading, --font-heading-accent) are emitted alongside so font-heading and the accent class on a highlighted word resolve to the right stacks automatically.
What lives where
| Path | Purpose |
|---|---|
resources/views/pages/dashboard/settings/⚡typography.blade.php |
The Typography page — font browser, antialiasing select, per-heading and body dropdowns. |
app/Services/BrandingStyleService.php |
FONTS registry (Bunny slug + weights per face), typographyCssBlock() (cached <style> block for the public layout), buildHeadingElementCss() (size/weight/tracking/leading/text-wrap/antialiasing → CSS). |
resources/views/pages/dashboard/settings/partials/font-browser.blade.php |
The categorised font picker partial used by body / heading / heading-accent. |
Settings reference
| Key | Default | Notes |
|---|---|---|
branding.typography.body_font |
'instrument-sans' |
Slug from BrandingStyleService::FONTS. 'system-sans' for zero load. |
branding.typography.heading_font |
'instrument-sans' |
Same slug pool. |
branding.typography.heading_accent_font |
'' |
Optional. Empty = no accent font. |
branding.typography.antialiasing_mode |
'smooth' |
One of smooth, subpixel, auto. |
branding.typography.heading_defaults.{h1..h4}.size |
text-5xl / text-4xl / text-3xl / text-2xl |
Tailwind size token. |
branding.typography.heading_defaults.{h1..h4}.weight |
font-semibold |
Tailwind weight token (font-thin..font-black). |
branding.typography.heading_defaults.{h1..h4}.letter_spacing |
'' |
Empty = no rule. Otherwise tracking-tighter..tracking-widest. |
branding.typography.heading_defaults.{h1..h4}.line_height |
'' |
Empty = no rule. Otherwise leading-none..leading-loose. |
branding.typography.heading_defaults.{h1..h4}.text_wrap |
'' |
Empty / text-pretty / text-balance. |
branding.typography.heading_defaults.{h1..h4}.classes |
'' |
Free-form Tailwind override field (autocomplete-assisted). |
branding.typography.body_defaults.size |
text-base |
|
branding.typography.body_defaults.weight |
font-light |
|
branding.typography.body_defaults.text_wrap |
'' |
Empty / text-pretty. |
branding.typography.body_defaults.classes |
'' |
Free-form Tailwind override field. |