Type Scale with CSS Custom Properties: A Practical Guide
Embed This Widget
Add the script tag and a data attribute to embed this widget.
Embed via iframe for maximum compatibility.
<iframe src="https://fontfyi.com/iframe/entity//" width="420" height="400" frameborder="0" style="border:0;border-radius:10px;max-width:100%" loading="lazy"></iframe>
Paste this URL in WordPress, Medium, or any oEmbed-compatible platform.
https://fontfyi.com/entity//
Add a dynamic SVG badge to your README or docs.
[](https://fontfyi.com/entity//)
Use the native HTML custom element.
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.
타이포그래피 용어
도구 사용해 보기
언급된 폰트
Christian Robertson이 Google의 Material Design 생태계를 위해 설계한 이 네오 그로테스크 산세리프체는 웹과 Android에서 가장 널리 사용되는 서체입니다. 이중적인 설계 방식이 기계적 정밀함과 자연스러운 읽기 리듬을 균형 있게 결합하여, UI 레이블과 장문 텍스트 모두에 잘 어울립니다. 가변 폰트는 너비 및 굵기 축을 지원하며, 키릴 문자, 그리스 문자, 확장 라틴 스크립트를 함께 포함하고 있습니다.
Rasmus Andersson이 수년에 걸쳐 컴퓨터 화면을 위해 정제한 이 네오 그로테스크체는 디지털 디스플레이의 소형 크기에서 높은 가독성을 위해 자간, x-높이, 획 대비를 최적화했습니다. 광학 크기 축(opsz)을 통해 캡션과 헤드라인에 따라 디자인이 자동으로 조정되며, 굵기 축은 얇은 것부터 블랙까지 전체 범위를 커버합니다. 전 세계 대시보드, 문서화 사이트, 개발자 도구의 사실상 표준 선택으로 자리잡았습니다.