Typography
Font families, type scale, variant presets, and typography components.
Overview
Unified UI's typography system is designed for web interfaces — not marketing pages. It favors compact, readable sizes. The largest heading is 30px; body text is 14–16px.
The system provides:
- 5 core font family slots (
sans,display,serif,mono,inherit) mapped to CSS custom properties. - 32 font presets available via the theme customizer — 28 Google Fonts, 3 generic stacks, and 1 inherit option.
- 7 font sizes from 12px to 30px on a curated scale.
- 10 typography presets — pre-composed combinations of size, weight, line height, tracking, and font family.
- React components —
<Heading>,<Body>,<Caption>,<Label>,<Overline>,<Subheading>,<InlineCode>, and the base<Typography>component.
Font Families
The typography system is organized into two layers:
- Core font family slots — 5 semantic slots (
sans,display,serif,mono,inherit) that map to CSS custom properties and Tailwind utilities. Components reference these slots, never specific typefaces. - Font presets — 32 concrete typefaces that can be swapped into the
--font-sansslot at runtime via the theme customizer. This lets users change the entire UI typeface without touching component code.
Core Font Family Slots
These are the 5 semantic slots used by components and typography presets:
| Slot | Default Typeface | CSS Variable | Tailwind Class | Use Case |
|---|---|---|---|---|
sans | Outfit | --font-sans | font-sans | Primary UI typeface — all interface text |
display | Inter | --font-display | font-display | Hero headings, marketing text |
serif | Lora | --font-serif | font-serif | Long-form reading, editorial content |
mono | JetBrains Mono | --font-mono | font-mono | Code blocks, technical values |
inherit | inherit | --font-inherit | font-inherit | Escape hatch for third-party integration |
The sans slot is the workhorse — it's the default font for every
typography preset except code. The theme customizer overrides
--font-sans at runtime, so changing it updates the entire UI at once.
All Available Fonts
Unified UI loads 32 font presets via next/font/google. The 4 built-in fonts power the core slots; the remaining 28 are available through the theme customizer to swap the --font-sans slot.
These 4 fonts are loaded by default and power the core font family slots:
| Font | CSS Variable | Default Slot | Weights Loaded | Character |
|---|---|---|---|---|
| Outfit | --font-outfit | sans | 300–700 (variable) | Geometric sans-serif, clean UI feel |
| Inter | --font-inter | display | 300–700 (variable) | Humanist sans-serif, excellent legibility |
| Lora | --font-lora | serif | 400–700 (variable) | Modern serif, editorial quality |
| JetBrains Mono | --font-jetbrains | mono | 400–700 (variable) | Developer-focused monospace |
These 28 additional fonts are pre-loaded and available via the theme customizer. Any of them can replace Outfit as the primary --font-sans typeface:
| Font | CSS Variable | Weights Loaded | Character |
|---|---|---|---|
| Geist Sans | --font-geist-sans | 300–700 (variable) | Vercel's design system font, neutral & modern |
| DM Sans | --font-dm-sans | 300–700 (variable) | Geometric, low-contrast, Google-designed |
| Plus Jakarta Sans | --font-plus-jakarta-sans | 300–700 (variable) | Modern geometric, slightly rounded |
| Open Sans | --font-open-sans | 300–700 (variable) | Humanist, highly legible at small sizes |
| Poppins | --font-poppins | 300, 400, 500, 600, 700 | Geometric, friendly, widely popular |
| Montserrat | --font-montserrat | 300–700 (variable) | Urban, strong headings |
| Lato | --font-lato | 300, 400, 700, 900 | Warm humanist, stable at all sizes |
| Nunito | --font-nunito | 300–700 (variable) | Rounded, soft & approachable |
| Raleway | --font-raleway | 300–700 (variable) | Elegant, thin weights for display |
| Rubik | --font-rubik | 300–700 (variable) | Slightly rounded, balanced geometry |
| Source Sans 3 | --font-source-sans-3 | 300–700 (variable) | Adobe's open-source workhorse |
| Work Sans | --font-work-sans | 300–700 (variable) | Optimized for screen, mid-century vibe |
| Manrope | --font-manrope | 300–700 (variable) | Modern geometric, semi-rounded |
| Space Grotesk | --font-space-grotesk | 300–700 (variable) | Proportional companion to Space Mono |
| Figtree | --font-figtree | 300–700 (variable) | Friendly geometric, GitHub-inspired |
| IBM Plex Sans | --font-ibm-plex-sans | 300, 400, 500, 600, 700 | Corporate, neutral, IBM's design language |
| Quicksand | --font-quicksand | 300–700 (variable) | Rounded, display-oriented |
| Cabin | --font-cabin | 300–700 (variable) | Humanist, inspired by Edward Johnston |
| Barlow | --font-barlow | 300, 400, 500, 600, 700 | Slightly rounded, industrial grotesk |
| Josefin Sans | --font-josefin-sans | 300–700 (variable) | Elegant, vintage geometric |
| Karla | --font-karla | 300–700 (variable) | Grotesque, quirky character shapes |
| Mulish | --font-mulish | 300–700 (variable) | Minimalist, very clean at small sizes |
| Noto Sans | --font-noto-sans | 300–700 (variable) | Google's universal coverage font |
| Ubuntu | --font-ubuntu | 300, 400, 500, 700 | Canonical's brand font, humanist warmth |
| Sora | --font-sora | 300–700 (variable) | Modern, perfect for tech products |
| Lexend | --font-lexend | 300–700 (variable) | Designed for reading proficiency |
These 3 presets use system/generic font stacks and don't require loading any Google Font:
| Preset | CSS Value | Use Case |
|---|---|---|
| System | system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif | Native platform feel, zero-latency |
| Serif | var(--font-lora), Georgia, "Times New Roman", serif | Editorial / long-form content |
| Mono | var(--font-jetbrains), "Fira Code", "SF Mono", Consolas, "Liberation Mono", Menlo, monospace | Code-heavy interfaces |
Loading Fonts
All fonts are loaded in the root layout using next/font/google. Each font exposes a CSS variable that the design system and theme customizer reference:
import {
Outfit,
Inter,
Lora,
JetBrains_Mono, // Built-in
Geist,
DM_Sans,
Plus_Jakarta_Sans, // Customizer presets
Open_Sans,
Poppins,
Montserrat,
Lato,
Nunito,
Raleway,
Rubik,
Source_Sans_3,
Work_Sans,
Manrope,
Space_Grotesk,
Figtree,
IBM_Plex_Sans,
Quicksand,
Cabin,
Barlow,
Josefin_Sans,
Karla,
Mulish,
Noto_Sans,
Ubuntu,
Sora,
Lexend,
} from "next/font/google";
// Built-in fonts (power the core slots)
const outfit = Outfit({ subsets: ["latin"], variable: "--font-outfit" });
const inter = Inter({ subsets: ["latin"], variable: "--font-inter" });
const lora = Lora({ subsets: ["latin"], variable: "--font-lora" });
const jetbrainsMono = JetBrains_Mono({
subsets: ["latin"],
variable: "--font-jetbrains",
});
// Additional fonts (available via theme customizer)
const geistSans = Geist({ subsets: ["latin"], variable: "--font-geist-sans" });
const dmSans = DM_Sans({ subsets: ["latin"], variable: "--font-dm-sans" });
const poppins = Poppins({
subsets: ["latin"],
weight: ["300", "400", "500", "600", "700"],
variable: "--font-poppins",
});
// ... 25 more fonts loaded similarly
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html
className={cn(
// Built-in fonts
outfit.variable,
inter.variable,
lora.variable,
jetbrainsMono.variable,
// Additional fonts
geistSans.variable,
dmSans.variable,
poppins.variable,
// ... all other font variables
)}
>
<body>{children}</body>
</html>
);
}All 32 fonts are loaded with next/font/google's automatic subsetting and
self-hosting. Only the glyphs actually used on each page are downloaded, so
loading all fonts adds minimal overhead.
CSS Variable Mapping
The design system CSS (styles.css) maps the next/font variables to the core --font-* slots:
:root {
--font-sans:
var(--font-outfit), system-ui, -apple-system, BlinkMacSystemFont,
"Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
--font-display:
var(--font-inter), system-ui, -apple-system, BlinkMacSystemFont,
"Segoe UI", Roboto, sans-serif;
--font-serif: var(--font-lora), Georgia, "Times New Roman", serif;
--font-mono:
var(--font-jetbrains), "Fira Code", "SF Mono", "Cascadia Code",
Consolas, "Liberation Mono", Menlo, monospace;
--font-inherit: inherit;
}When the theme customizer changes the font, it overrides --font-sans with the selected preset's CSS variable (e.g., var(--font-dm-sans), system-ui, ...). All components that use font-sans automatically update.
Using Font Families
// In Tailwind classes
<p className="font-sans">Interface text in Outfit</p>
<h1 className="font-display">Hero heading in Inter</h1>
<blockquote className="font-serif">Editorial quote in Lora</blockquote>
<code className="font-mono">const x = 42;</code>
// In CSS
.custom-element {
font-family: var(--font-sans);
}
// In TypeScript
import { fontFamily } from "@work-rjkashyap/unified-ui/tokens";
console.log(fontFamily.sans);
// "'Outfit', system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif"Programmatic Access to Font Presets
import { FONT_PRESETS, getFontPreset } from "@work-rjkashyap/unified-ui/theme";
// Get all available font presets
console.log(FONT_PRESETS.length); // 32
// Look up a specific preset by key
const dmSans = getFontPreset("dm-sans");
console.log(dmSans.name); // "DM Sans"
console.log(dmSans.value); // 'var(--font-dm-sans), system-ui, ...'Font Sizes
A curated scale designed for density and readability:
| Token | Value | Tailwind | Typical Use |
|---|---|---|---|
xs | 12px | text-xs | Captions, helper text, badges |
sm | 14px | text-sm | Body small, labels, code |
base | 16px | text-base | Default body text |
lg | 18px | text-lg | Subheadings |
xl | 20px | text-xl | Heading 3 |
2xl | 24px | text-2xl | Heading 2 |
3xl | 30px | text-3xl | Heading 1 — page titles |
Avoid text-4xl and above. Per project guidelines, headings should not
exceed text-3xl (30px). If you need larger text for a marketing hero, use
the display font family with text-3xl for visual distinction instead of
a larger size.
Font Weights
| Token | Value | Use Case |
|---|---|---|
regular | 400 | Body text, descriptions |
medium | 500 | Labels, subheadings, emphasis |
semibold | 600 | Section titles, card headings |
bold | 700 | Page titles, strong emphasis |
Line Heights
Relative Values
| Token | Value | Use Case |
|---|---|---|
none | 1 | Single-line display text |
tight | 1.25 | Headings |
snug | 1.375 | Compact body text |
normal | 1.5 | Default body text |
relaxed | 1.625 | Long-form reading |
Fixed Values
For precise control aligned to the 4px grid:
| Token | Value | Paired With |
|---|---|---|
16 | 16px | Caption, overline |
20 | 20px | Body small, label |
24 | 24px | Body default |
28 | 28px | Subheading, heading3 |
32 | 32px | Heading 2 |
36 | 36px | Heading 1 |
Fixed line heights are preferred in the preset system because they keep
vertical rhythm aligned to the 4px grid. Relative values (tight, normal,
etc.) are available for custom text styling outside the preset system.
Letter Spacing
| Token | Value | Use Case |
|---|---|---|
tighter | -0.05em | Display headings |
tight | -0.025em | Headings (H1, H2) |
normal | 0em | Body text, subheadings |
wide | 0.025em | Captions |
wider | 0.05em | Overlines, uppercased text |
Typography Presets
Pre-composed variants are the canonical text styles for the system. Components should use these presets rather than assembling individual tokens ad hoc.
| Preset | Size | Weight | Line Height | Tracking | Font | Use Case |
|---|---|---|---|---|---|---|
heading1 | 30px | bold | 36px | tight | sans | Page titles |
heading2 | 24px | semibold | 32px | tight | sans | Section titles |
heading3 | 20px | semibold | 28px | normal | sans | Subsection titles |
subheading | 18px | medium | 28px | normal | sans | Card titles, sidebar headings |
body | 16px | regular | 24px | normal | sans | Default body text |
bodySm | 14px | regular | 20px | normal | sans | Compact body text, table cells |
caption | 12px | regular | 16px | wide | sans | Helper text, timestamps, footnotes |
label | 14px | medium | 20px | normal | sans | Form labels, badge text |
overline | 12px | semibold | 16px | wider | sans | Eyebrow text (typically uppercased) |
code | 14px | regular | 20px | normal | mono | Inline code, technical values |
import { typographyVariants } from "@work-rjkashyap/unified-ui/tokens";
const h1 = typographyVariants.heading1;
// {
// fontSize: "30px",
// lineHeight: "36px",
// fontWeight: "700",
// letterSpacing: "-0.025em",
// fontFamily: "sans"
// }Typography Components
Unified UI provides semantic React components for every typography preset. All components support forwardRef, the font prop for font family override, and standard HTML attributes.
<Heading>
Renders heading text at levels 1–3. Automatically maps to the correct HTML element (h1, h2, h3).
import { Heading } from "@work-rjkashyap/unified-ui";
<Heading level={1}>Page Title</Heading> // → <h1> with heading1 preset
<Heading level={2}>Section Title</Heading> // → <h2> with heading2 preset
<Heading level={3}>Subsection Title</Heading> // → <h3> with heading3 preset
// Override font family
<Heading level={1} font="display">Hero Title</Heading> // Uses Inter instead of Outfit
<Heading level={1} font="serif">Editorial Title</Heading>Props
| Prop | Type | Default | Description |
|---|---|---|---|
level | 1 | 2 | 3 | — | Heading level (required) |
font | "sans" | "display" | "serif" | "mono" | "inherit" | Preset default | Override font family |
color | "default" | "muted" | "primary" | "danger" | "success" | "warning" | "inherit" | "default" | Text color |
align | "left" | "center" | "right" | — | Text alignment |
truncate | boolean | false | Truncate with ellipsis |
lineClamp | number | — | Limit visible lines |
className | string | — | Additional CSS classes |
<Subheading>
For card titles, sidebar headings, and secondary emphasis.
import { Subheading } from "@work-rjkashyap/unified-ui";
<Subheading>Card Title</Subheading> // 18px medium
<Subheading font="display">Feature Title</Subheading> // Inter 18px medium<Body>
Default body text at 16px.
import { Body } from "@work-rjkashyap/unified-ui";
<Body>This is the default body text at 16px.</Body>
<Body color="muted">Secondary description text.</Body>
<Body font="serif">Editorial body text in Lora.</Body>
<Body lineClamp={3}>Long text that will be clamped to 3 lines...</Body>Props (shared across Body, Caption, Label, Overline)
| Prop | Type | Default | Description |
|---|---|---|---|
font | "sans" | "display" | "serif" | "mono" | "inherit" | Preset default | Override font family |
color | "default" | "muted" | "primary" | "danger" | "success" | "warning" | "inherit" | "default" | Text color |
align | "left" | "center" | "right" | — | Text alignment |
truncate | boolean | false | Truncate with ellipsis |
lineClamp | number | — | Limit visible lines |
<Caption>
Small helper text at 12px. Ideal for timestamps, footnotes, and descriptions.
import { Caption } from "@work-rjkashyap/unified-ui";
<Caption>Last updated 2 hours ago</Caption>
<Caption color="muted">Optional field</Caption><Label>
Form labels at 14px medium weight. Pairs with Input, Select, and other form controls.
import { Label } from "@work-rjkashyap/unified-ui";
<Label>Email address</Label>
<Label color="danger">Required field</Label><Overline>
Eyebrow text at 12px semibold. Typically rendered in uppercase.
import { Overline } from "@work-rjkashyap/unified-ui";
<Overline>New Feature</Overline> // Uppercased by default styling
<Overline color="primary">Category</Overline><InlineCode>
Inline code spans at 14px in the mono font. Renders a <code> element with subtle background styling.
import { InlineCode } from "@work-rjkashyap/unified-ui";
<Body>
Use the <InlineCode>cn()</InlineCode> utility for class merging.
</Body>;<Typography> (Base Component)
The low-level component that all typography components are built from. Use it when you need a variant that doesn't have a dedicated component, or for full control over rendering.
import { Typography } from "@work-rjkashyap/unified-ui";
<Typography variant="heading1" as="h1">Custom Heading</Typography>
<Typography variant="bodySm" as="span">Inline small text</Typography>
<Typography variant="code" as="code">const x = 42</Typography>Props
| Prop | Type | Default | Description |
|---|---|---|---|
variant | "heading1" | "heading2" | "heading3" | "subheading" | "body" | "bodySm" | "caption" | "label" | "overline" | "code" | "body" | Typography preset |
as | React.ElementType | "p" | HTML element to render |
font | "sans" | "display" | "serif" | "mono" | "inherit" | Preset default | Override font family |
color | "default" | "muted" | "primary" | "danger" | "success" | "warning" | "inherit" | "default" | Text color |
align | "left" | "center" | "right" | — | Text alignment |
truncate | boolean | false | Truncate with ellipsis |
lineClamp | number | — | Limit visible lines |
The font Prop
Every typography component accepts a font prop that overrides the preset's default font family. This lets you mix typefaces without creating custom components:
import { Heading, Body, Caption } from "@work-rjkashyap/unified-ui";
// Default — all use the "sans" (Outfit) font
<Heading level={1}>Dashboard</Heading>
<Body>Welcome to your dashboard.</Body>
// Override to "display" (Inter) for a different feel
<Heading level={1} font="display">Welcome Back</Heading>
// Mix fonts in a card
<Heading level={2} font="serif">The Story of Design Systems</Heading>
<Body font="serif">Once upon a time, every button was a different shade of blue...</Body>
<Caption font="mono">Published: 2025-01-15T10:30:00Z</Caption>Truncation & Line Clamping
All typography components support text overflow control:
Single-Line Truncation
<Body truncate>
This very long text will be truncated with an ellipsis when it overflows its
container.
</Body>Renders with overflow: hidden; text-overflow: ellipsis; white-space: nowrap.
Multi-Line Clamping
<Body lineClamp={2}>
This text will show at most 2 lines. Any overflow beyond the second line
will be hidden with an ellipsis. This is useful for card descriptions,
preview text, and list items.
</Body>Uses CSS line-clamp for native multi-line truncation.
Color Options
The color prop maps to semantic design tokens:
| Value | CSS Variable | Use Case |
|---|---|---|
default | --foreground | Primary text |
muted | --muted-foreground | Secondary, descriptive text |
primary | --primary | Links, emphasized text |
danger | --danger | Error messages |
success | --success | Success messages |
warning | --warning | Warning messages |
inherit | inherit | Inherit from parent |
<Heading level={2} color="primary">Featured Section</Heading>
<Body color="muted">This section highlights our latest updates.</Body>
<Caption color="danger">This field is required.</Caption>
<Label color="success">Verified</Label>Best Practices
Do
- Use the preset components (
<Heading>,<Body>, etc.) instead of raw HTML elements with manual classes. - Let the
fontprop handle font family switching — don't addfont-*classes manually to typography components. - Use
color="muted"for secondary text instead of applying opacity or custom colors. - Use
truncateorlineClampfor text in constrained containers (cards, list items, table cells).
Don't
- Don't exceed
text-3xl(30px) for headings. Use font family contrast (displayvssans) for visual hierarchy instead of larger sizes. - Don't use
leading-looseorleading-relaxedfor interface text — stick totightthroughnormal. - Don't combine typography components with manual
text-*size classes — this defeats the purpose of the preset system. - Don't use more than 2 font families on a single page. The
sans+ one other (e.g.,monofor code) is the typical pattern.