CSS 커스텀 속성으로 만드는 타입 스케일: 실용 가이드
Type Scale with CSS Custom Properties: A Practical Guide
A type scale is a curated set of font sizes derived from a consistent mathematical ratio. Rather than choosing sizes arbitrarily — 16px for body, 20px for subheadings, 28px for headings, with no coherent relationship between them — a type scale gives you a system where every size relates harmonically to every other.
CSS custom properties are the ideal implementation layer for a type scale. They let you define the scale in one place, use it everywhere via var() references, and update the entire scale by changing a handful of values.
This guide walks through choosing a scale ratio, implementing it with custom properties, making it fluid and responsive, and integrating it with Tailwind CSS and a design token architecture.
Choosing a Scale Ratio
A modular type scale is built by repeatedly multiplying (or dividing) a base size by a fixed ratio. The ratio you choose determines how dramatically the scale steps progress.
Common Ratios
| Name | Ratio | Use case |
|---|---|---|
| Minor Second | 1.067 | Very tight — almost imperceptible steps |
| Major Second | 1.125 | Subtle — good for large screens with many text levels |
| Minor Third | 1.2 | Balanced — suitable for most interfaces |
| Major Third | 1.25 | Popular choice — distinct but not dramatic |
| Perfect Fourth | 1.333 | Assertive — classic for print-influenced designs |
| Augmented Fourth | 1.414 | Bold — good for few, well-differentiated levels |
| Perfect Fifth | 1.5 | Very bold — striking, limited to 4–5 levels max |
| Golden Ratio | 1.618 | Dramatic — editorial, limited levels |
For most web projects, a Major Third (1.25) or Perfect Fourth (1.333) ratio works well. The Major Third gives you enough visual distinction between levels while keeping the scale usable across mobile viewports where the largest sizes don't become extreme.
Calculating the Scale
Starting from a 1rem (16px) base and using a 1.25 ratio:
xs: 1rem / (1.25 × 1.25) = 0.64rem (10.24px)
sm: 1rem / 1.25 = 0.8rem (12.8px)
base: 1rem = 1rem (16px)
md: 1rem × 1.25 = 1.25rem (20px)
lg: 1rem × (1.25 × 1.25) = 1.563rem (25px)
xl: 1rem × 1.25³ = 1.953rem (31.25px)
2xl: 1rem × 1.25⁴ = 2.441rem (39.06px)
3xl: 1rem × 1.25⁵ = 3.052rem (48.83px)
4xl: 1rem × 1.25⁶ = 3.815rem (61.04px)
With Perfect Fourth (1.333):
xs: 0.563rem (9px)
sm: 0.75rem (12px)
base: 1rem (16px)
md: 1.333rem (21.33px)
lg: 1.777rem (28.43px)
xl: 2.369rem (37.9px)
2xl: 3.157rem (50.52px)
3xl: 4.209rem (67.34px)
Notice how the Perfect Fourth scale gets very large very quickly — the 3xl value exceeds 4rem. On mobile, this requires careful management or a smaller ratio. Many designers use a smaller ratio on mobile (Major Third) and a larger one on desktop (Perfect Fourth).
Implementing with CSS Custom Properties
CSS custom properties make the scale trivially maintainable. Define the values on :root, and every element in the document can reference them.
:root {
/* === Type Scale: Major Third (×1.25) === */
--font-size-xs: 0.64rem; /* 10.24px */
--font-size-sm: 0.8rem; /* 12.8px */
--font-size-base: 1rem; /* 16px */
--font-size-md: 1.25rem; /* 20px */
--font-size-lg: 1.563rem; /* 25px */
--font-size-xl: 1.953rem; /* 31.25px */
--font-size-2xl: 2.441rem; /* 39px */
--font-size-3xl: 3.052rem; /* 48.83px */
--font-size-4xl: 3.815rem; /* 61px */
/* === Font families === */
--font-sans: 'Inter', system-ui, -apple-system, sans-serif;
--font-serif: 'Playfair Display', Georgia, serif;
--font-mono: 'JetBrains Mono', 'Courier New', monospace;
/* === Weights === */
--font-weight-light: 300;
--font-weight-regular: 400;
--font-weight-medium: 500;
--font-weight-semibold: 600;
--font-weight-bold: 700;
--font-weight-black: 900;
/* === Line heights === */
--line-height-tight: 1.15;
--line-height-snug: 1.35;
--line-height-normal: 1.5;
--line-height-relaxed: 1.65;
--line-height-loose: 1.8;
/* === Letter spacing === */
--tracking-tighter: -0.04em;
--tracking-tight: -0.02em;
--tracking-normal: 0em;
--tracking-wide: 0.03em;
--tracking-wider: 0.06em;
--tracking-widest: 0.12em;
}
Applying the Scale to Elements
/* Base document */
html {
font-size: 16px;
}
body {
font-family: var(--font-sans);
font-size: var(--font-size-base);
font-weight: var(--font-weight-regular);
line-height: var(--line-height-normal);
color: #1a1a2e;
}
/* Heading hierarchy */
h1 {
font-size: var(--font-size-4xl);
font-weight: var(--font-weight-bold);
line-height: var(--line-height-tight);
letter-spacing: var(--tracking-tight);
}
h2 {
font-size: var(--font-size-3xl);
font-weight: var(--font-weight-bold);
line-height: var(--line-height-tight);
letter-spacing: var(--tracking-tight);
}
h3 {
font-size: var(--font-size-2xl);
font-weight: var(--font-weight-semibold);
line-height: var(--line-height-snug);
letter-spacing: var(--tracking-tight);
}
h4 {
font-size: var(--font-size-xl);
font-weight: var(--font-weight-semibold);
line-height: var(--line-height-snug);
}
h5 {
font-size: var(--font-size-lg);
font-weight: var(--font-weight-medium);
line-height: var(--line-height-normal);
}
h6 {
font-size: var(--font-size-md);
font-weight: var(--font-weight-medium);
line-height: var(--line-height-normal);
}
/* Prose */
p {
font-size: var(--font-size-base);
line-height: var(--line-height-relaxed);
max-width: 70ch;
}
.lead {
font-size: var(--font-size-md);
line-height: var(--line-height-relaxed);
font-weight: var(--font-weight-light);
}
/* Small text */
small, .text-sm {
font-size: var(--font-size-sm);
line-height: var(--line-height-normal);
}
.label {
font-size: var(--font-size-xs);
font-weight: var(--font-weight-semibold);
letter-spacing: var(--tracking-wider);
text-transform: uppercase;
}
/* Code */
code {
font-family: var(--font-mono);
font-size: 0.875em; /* Relative to context */
}
Responsive Adjustments: Fluid Scale
The straightforward responsive approach is redefining scale variables in media queries:
/* Mobile first: Major Third ratio (gentler) */
:root {
--font-size-xs: 0.64rem;
--font-size-sm: 0.8rem;
--font-size-base: 1rem;
--font-size-md: 1.25rem;
--font-size-lg: 1.563rem;
--font-size-xl: 1.953rem;
--font-size-2xl: 2.441rem;
--font-size-3xl: 3.052rem;
--font-size-4xl: 3.5rem; /* Cap for mobile */
}
/* Tablet: slightly larger scale */
@media (min-width: 640px) {
:root {
--font-size-3xl: 3.5rem;
--font-size-4xl: 4rem;
}
}
/* Desktop: Perfect Fourth ratio for display sizes */
@media (min-width: 1024px) {
:root {
--font-size-lg: 1.777rem;
--font-size-xl: 2.369rem;
--font-size-2xl: 3.157rem;
--font-size-3xl: 4.209rem;
--font-size-4xl: 5.61rem;
}
}
Fully Fluid Scale with clamp()
For zero-breakpoint fluid behavior, replace static values with clamp(). See the fluid typography guide for the math behind generating these values.
:root {
/* Each step grows from mobile min to desktop max */
--font-size-xs: clamp(0.64rem, 0.6rem + 0.2vw, 0.75rem);
--font-size-sm: clamp(0.8rem, 0.75rem + 0.25vw, 0.875rem);
--font-size-base: clamp(1rem, 0.95rem + 0.25vw, 1.125rem);
--font-size-md: clamp(1.25rem, 1.1rem + 0.75vw, 1.5rem);
--font-size-lg: clamp(1.563rem, 1.3rem + 1.3vw, 2rem);
--font-size-xl: clamp(1.953rem, 1.5rem + 2.25vw, 2.75rem);
--font-size-2xl: clamp(2.441rem, 1.75rem + 3.45vw, 3.75rem);
--font-size-3xl: clamp(3rem, 2rem + 5vw, 5rem);
--font-size-4xl: clamp(3.5rem, 2rem + 7.5vw, 7rem);
}
Integrating with Tailwind CSS
Tailwind has its own type scale, but you can extend it with your custom values using CSS custom properties in two ways.
Extending the Tailwind Config
// tailwind.config.js
const plugin = require('tailwindcss/plugin');
module.exports = {
theme: {
extend: {
fontSize: {
// Map your custom scale to Tailwind utilities
'scale-xs': ['var(--font-size-xs)', { lineHeight: 'var(--line-height-normal)' }],
'scale-sm': ['var(--font-size-sm)', { lineHeight: 'var(--line-height-normal)' }],
'scale-base': ['var(--font-size-base)', { lineHeight: 'var(--line-height-relaxed)' }],
'scale-md': ['var(--font-size-md)', { lineHeight: 'var(--line-height-relaxed)' }],
'scale-lg': ['var(--font-size-lg)', { lineHeight: 'var(--line-height-snug)' }],
'scale-xl': ['var(--font-size-xl)', { lineHeight: 'var(--line-height-snug)' }],
'scale-2xl': ['var(--font-size-2xl)', { lineHeight: 'var(--line-height-tight)' }],
'scale-3xl': ['var(--font-size-3xl)', { lineHeight: 'var(--line-height-tight)' }],
'scale-4xl': ['var(--font-size-4xl)', { lineHeight: 'var(--line-height-tight)' }],
},
fontFamily: {
sans: ['Inter', 'system-ui', '-apple-system', 'sans-serif'],
serif: ['Lora', 'Georgia', 'serif'],
mono: ['JetBrains Mono', 'Courier New', 'monospace'],
},
},
},
};
<!-- Using your scale in Tailwind HTML -->
<h1 class="text-scale-4xl font-bold tracking-tight">
Main Headline
</h1>
<p class="text-scale-base leading-relaxed max-w-prose">
Body text paragraph.
</p>
<span class="text-scale-xs font-semibold tracking-wider uppercase">
Category Label
</span>
The @layer Approach
Alternatively, keep everything in CSS and use Tailwind's @layer to add typography utilities:
@layer base {
:root {
--font-size-xs: 0.64rem;
--font-size-sm: 0.8rem;
--font-size-base: 1rem;
--font-size-md: 1.25rem;
--font-size-lg: 1.563rem;
--font-size-xl: 1.953rem;
--font-size-2xl: 2.441rem;
--font-size-3xl: 3.052rem;
--font-size-4xl: 3.815rem;
}
h1 { font-size: var(--font-size-4xl); line-height: 1.1; }
h2 { font-size: var(--font-size-3xl); line-height: 1.15; }
h3 { font-size: var(--font-size-2xl); line-height: 1.25; }
h4 { font-size: var(--font-size-xl); line-height: 1.3; }
h5 { font-size: var(--font-size-lg); line-height: 1.4; }
h6 { font-size: var(--font-size-md); line-height: 1.5; }
}
Design Token Architecture for Type Scale
Design tokens are the bridge between design tools (Figma, Sketch) and code. Typography tokens capture the intent — "this is a heading large" — rather than the specific value.
Three-Tier Token Architecture
A robust token architecture has three tiers:
Tier 1: Primitive tokens — Raw values, no semantic meaning
:root {
/* Primitives: the raw scale */
--primitive-size-0: 0.64rem;
--primitive-size-1: 0.8rem;
--primitive-size-2: 1rem;
--primitive-size-3: 1.25rem;
--primitive-size-4: 1.563rem;
--primitive-size-5: 1.953rem;
--primitive-size-6: 2.441rem;
--primitive-size-7: 3.052rem;
--primitive-size-8: 3.815rem;
--primitive-weight-light: 300;
--primitive-weight-regular: 400;
--primitive-weight-medium: 500;
--primitive-weight-semibold: 600;
--primitive-weight-bold: 700;
}
Tier 2: Semantic tokens — Reference primitives, communicate purpose
:root {
/* Semantic: map purpose to primitives */
--text-body-size: var(--primitive-size-2); /* 1rem */
--text-body-weight: var(--primitive-weight-regular);
--text-body-leading: 1.65;
--text-heading-xs-size: var(--primitive-size-3); /* 1.25rem */
--text-heading-sm-size: var(--primitive-size-4); /* 1.563rem */
--text-heading-md-size: var(--primitive-size-5); /* 1.953rem */
--text-heading-lg-size: var(--primitive-size-6); /* 2.441rem */
--text-heading-xl-size: var(--primitive-size-7); /* 3.052rem */
--text-heading-2xl-size: var(--primitive-size-8); /* 3.815rem */
--text-heading-weight: var(--primitive-weight-bold);
--text-heading-leading: 1.2;
--text-label-size: var(--primitive-size-0); /* 0.64rem */
--text-label-weight: var(--primitive-weight-semibold);
--text-label-spacing: 0.08em;
--text-code-size: 0.875em; /* Relative to context */
--text-code-family: 'JetBrains Mono', monospace;
}
Tier 3: Component tokens — Reference semantic tokens, component-specific
/* Card component */
.card {
--card-title-size: var(--text-heading-sm-size);
--card-title-weight: var(--text-heading-weight);
--card-body-size: var(--text-body-size);
--card-meta-size: var(--primitive-size-1);
font-size: var(--card-body-size);
}
.card__title {
font-size: var(--card-title-size);
font-weight: var(--card-title-weight);
line-height: var(--text-heading-leading);
}
/* Button component */
.button {
--button-label-size: var(--primitive-size-2);
--button-label-weight: var(--primitive-weight-semibold);
font-size: var(--button-label-size);
font-weight: var(--button-label-weight);
}
.button--sm {
--button-label-size: var(--primitive-size-1);
}
.button--lg {
--button-label-size: var(--primitive-size-3);
}
This architecture means that a redesign (changing the ratio from Major Third to Perfect Fourth) only requires updating the Tier 1 primitives. Every component, heading, and UI element automatically adopts the new scale because they reference through the token chain.
Syncing with Figma Tokens
If your team uses Figma with the Tokens Studio plugin (formerly Figma Tokens), you can export a JSON file that maps directly to this structure:
{
"primitive": {
"size": {
"0": { "value": "0.64rem", "type": "fontSizes" },
"2": { "value": "1rem", "type": "fontSizes" },
"4": { "value": "1.563rem", "type": "fontSizes" }
}
},
"semantic": {
"text": {
"body": {
"size": { "value": "{primitive.size.2}", "type": "fontSizes" }
},
"heading": {
"lg": {
"size": { "value": "{primitive.size.6}", "type": "fontSizes" }
}
}
}
}
}
This creates a living connection between your design files and your CSS — when a designer updates a token value, the CSS output changes automatically through the token pipeline.
A well-implemented type scale is a multiplier for design system quality. The upfront investment of choosing a ratio and mapping it through CSS custom properties pays dividends every time someone adds a new component — the sizes are already defined and consistent by design, not by coincidence.
Typography Terms
Try These Tools
Fonts Mentioned
Designed by Christian Robertson for Google's Material Design ecosystem, this neo-grotesque sans-serif is the most widely used typeface on the web and Android. Its dual-nature design balances mechanical precision with natural reading rhythm, making it equally at home in UI labels and long-form text. The variable font supports width and weight axes alongside Cyrillic, Greek, and extended Latin scripts.
Rasmus Andersson spent years refining this neo-grotesque specifically for computer screens, optimizing letter spacing, x-height, and stroke contrast for high readability at small sizes on digital displays. An optical size axis (opsz) lets the font automatically adjust its design for captions versus headlines, while the weight axis covers the full range from thin to black. It has become the de facto choice for dashboards, documentation sites, and developer tools worldwide.