CSS Typography

Escala tipográfica con propiedades personalizadas de CSS: guía práctica

Updated febrero 24, 2026
Construye una escala tipográfica armoniosa usando CSS custom properties: ratios modulares, escalado responsivo e integración con design tokens.

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

Related Articles