Modern CSS math: clamp, type scale, and specificity
Modern CSS expects you to think in formulas. Fluid typography uses clamp() to interpolate between breakpoints. Type scales use a multiplier to derive consistent heading sizes. Specificity is a three-digit score that determines which rule wins. Viewport units, aspect ratios, and color contrast all reduce to math you can do in your head once you know the formula. This guide covers the five most useful CSS calculations with examples you can paste directly into a stylesheet.
CSS clamp() for fluid values
clamp(MIN, PREFERRED, MAX) lets a value scale smoothly between two limits as the viewport changes. It is the cleanest replacement for media query stacks that adjust font size or spacing at every breakpoint.
font-size: clamp(1rem, 0.5rem + 2vw, 2rem);
This font-size starts at 1rem on small screens, scales upward as the viewport widens, and caps at 2rem on large screens. The middle value (0.5rem + 2vw) is the formula that determines the actual size at any viewport width.
The math behind the middle value: at viewport width W, the size becomes 0.5rem + (W × 2 / 100). At 320px (mobile): 0.5 + 6.4 = 6.9px (clamped to 1rem floor). At 1280px: 0.5 + 25.6 = 26.1px (close to the 2rem cap of 32px).
To derive the clamp values from two breakpoint sizes:
\[\text{slope} = \frac{\text{Max Size} - \text{Min Size}}{\text{Max Viewport} - \text{Min Viewport}}\] \[\text{intercept} = \text{Min Size} - \text{slope} \times \text{Min Viewport}\]For 1rem at 320px scaling to 2rem at 1280px (sizes in px: 16 and 32):
- Slope: (32 - 16) / (1280 - 320) = 16 / 960 ≈ 0.0167
- Intercept: 16 - 0.0167 × 320 = 10.67px
Final formula in CSS units: clamp(1rem, 10.67px + 1.67vw, 2rem). The CSS clamp calculator does this conversion automatically.
Type scale ratios
A type scale is a sequence of sizes derived by multiplying or dividing by a fixed ratio. Picking a single ratio (e.g., 1.25) and applying it consistently produces visual harmony across all heading levels.
\[\text{Size}_n = \text{Base Size} \times \text{Ratio}^n\]Common ratios:
| Name | Ratio | Use case |
|---|---|---|
| Minor second | 1.067 | Subtle, body-heavy designs |
| Major second | 1.125 | Subtle, slight contrast |
| Minor third | 1.2 | Default scale |
| Major third | 1.25 | Standard editorial |
| Perfect fourth | 1.333 | Bold contrast |
| Augmented fourth | 1.414 | High contrast (Tailwind default) |
| Perfect fifth | 1.5 | Marketing pages |
| Golden ratio | 1.618 | Display-heavy designs |
For a 1rem (16px) base with a 1.25 ratio:
| Step | Multiplier | Size (rem) | Size (px) |
|---|---|---|---|
| -1 | / 1.25 | 0.8 | 12.8 |
| 0 (base) | × 1 | 1.0 | 16 |
| 1 | × 1.25 | 1.25 | 20 |
| 2 | × 1.25² | 1.563 | 25 |
| 3 | × 1.25³ | 1.953 | 31.25 |
| 4 | × 1.25⁴ | 2.441 | 39 |
| 5 | × 1.25⁵ | 3.052 | 48.8 |
Map these to HTML elements: h6 = step 0, h5 = step 1, h4 = step 2, etc. The type scale calculator generates the full scale at any base size and ratio.
CSS specificity
When two CSS rules target the same element, specificity decides which one wins. Specificity is a three-digit score: (IDs, classes/attributes/pseudo-classes, elements/pseudo-elements). The selector with the higher score wins; ties go to the rule defined later.
Quick scoring:
| Selector type | Score |
|---|---|
Element (div, p) |
0,0,1 |
Pseudo-element (::before) |
0,0,1 |
Class (.btn) |
0,1,0 |
Attribute ([type="text"]) |
0,1,0 |
Pseudo-class (:hover) |
0,1,0 |
ID (#header) |
1,0,0 |
| Inline style | 1,0,0,0 |
!important |
overrides everything |
Examples:
| Selector | Specificity |
|---|---|
p |
0,0,1 |
.btn |
0,1,0 |
#header |
1,0,0 |
nav a:hover |
0,1,2 |
.card .title |
0,2,0 |
#sidebar p.note |
1,1,1 |
body #main .article p |
1,1,2 |
Important rules:
- Specificity is compared digit by digit. (1,0,0) beats (0,9,9). One ID outweighs nine classes.
- The universal selector (
*) and combinators (>,+,~) add zero specificity. :not(),:is(),:where()have special rules.:where()always contributes zero.:not()and:is()use the highest-specificity argument inside.
The CSS specificity calculator parses any selector string and shows the breakdown.
Viewport units (vw, vh, vmin, vmax)
Viewport units measure relative to the browser window:
1vw= 1% of viewport width1vh= 1% of viewport height1vmin= 1% of the smaller dimension1vmax= 1% of the larger dimension
For a 1920 × 1080 desktop viewport:
| Unit | Pixel value |
|---|---|
| 1vw | 19.2px |
| 1vh | 10.8px |
| 1vmin | 10.8px |
| 1vmax | 19.2px |
For a 375 × 812 mobile viewport:
| Unit | Pixel value |
|---|---|
| 1vw | 3.75px |
| 1vh | 8.12px |
| 1vmin | 3.75px |
| 1vmax | 8.12px |
Common patterns:
/* Full-screen hero section */
.hero { min-height: 100vh; }
/* Fluid heading with viewport units */
h1 { font-size: clamp(2rem, 5vw, 4rem); }
/* Responsive padding */
section { padding: 5vmin; }
Modern CSS also includes dvh (dynamic viewport height) which accounts for mobile address bar collapse, and lvh/svh for large/small viewport variants. These solve the long-standing “100vh is too tall on mobile” problem. Use the viewport unit calculator to convert px to vw at any reference width.
Aspect ratio
Aspect ratio is width divided by height, expressed as W:H. The classic 16:9 means a width of 16 units for every 9 units of height.
To find any missing dimension when the ratio is fixed:
\[\text{Height} = \text{Width} \times \frac{H}{W}\]For a 16:9 hero image at 1200px wide: height = 1200 × 9/16 = 675px.
Common ratios:
| Ratio | Decimal | Use case |
|---|---|---|
| 1:1 | 1.000 | Square (Instagram, profile photos) |
| 4:3 | 1.333 | Old TV, iPad |
| 3:2 | 1.500 | DSLR photos |
| 16:10 | 1.600 | Older laptops |
| 16:9 | 1.778 | HD video, modern displays |
| 21:9 | 2.333 | Ultrawide cinema |
The CSS aspect-ratio property reserves space without explicit dimensions:
.video { aspect-ratio: 16 / 9; width: 100%; }
.thumbnail { aspect-ratio: 1; width: 200px; }
This prevents layout shift when the actual content (image, video) loads later. The aspect ratio calculator handles dimension conversion in any direction.
Color contrast (WCAG)
Color contrast is the ratio between the relative luminance of foreground and background:
\[\text{Contrast Ratio} = \frac{L_1 + 0.05}{L_2 + 0.05}\]Where L₁ is the lighter color’s luminance and L₂ is the darker color’s. The result ranges from 1:1 (no contrast) to 21:1 (black on white).
WCAG accessibility thresholds:
| Level | Normal text | Large text (18pt+ or 14pt+ bold) |
|---|---|---|
| AA (minimum) | 4.5:1 | 3:1 |
| AAA (enhanced) | 7:1 | 4.5:1 |
The luminance formula is more involved (it requires gamma correction on each RGB channel), but the ratio comparison is straightforward once you have the luminance values. The color contrast checker handles both calculations.
Putting it together
A reusable CSS pattern for a card heading using all of the above:
.card-title {
/* Type scale step 2 with 1.25 ratio from 1rem base */
font-size: clamp(1.25rem, 1rem + 1vw, 1.563rem);
/* Maintain readable line length */
max-width: 60ch;
/* High contrast for AA */
color: #1a1a1a;
/* on a white background = 18.4:1, exceeds AAA */
}
.card-image {
aspect-ratio: 16 / 9;
width: 100%;
}
Each line ties back to a specific calculation. The CSS specification gives you the math; the calculators do the arithmetic.
CalculateY