Colors, Units and Value Functions

Every declaration has a value, and values have their own grammar: colors, lengths, and functions that compute. Choosing the right unit is the difference between a layout that scales and one that breaks on the first non-standard screen. This lesson is the toolkit the rest of the track builds with.

Color notation

Four ways to write one color

.swatches {
  color: #38bdf8;              /* hex - compact, the most common in codebases */
  color: rgb(56 189 248);      /* rgb - same thing, decimal channels */
  color: hsl(198 93% 60%);     /* hsl - hue in degrees, saturation, lightness in % */
  color: oklch(0.75 0.14 232); /* oklch - perceptually even, made for design systems */
}

/* Modern syntax: spaces, optional alpha after a slash - both are 60% opaque */
.veil {
  color: rgb(56 189 248 / 0.6);
  color: #38bdf899;
}

Why HSL and OKLCH earn their keep

Hex and RGB describe how a screen mixes light; HSL and OKLCH describe how humans see. For derived colors — a hover state, a muted variant, a full palette — you want to adjust lightness without hue drifting. HSL does this acceptably; OKLCH does it measurably better because its lightness axis matches perceived brightness. Plain CSS computes variants directly, no preprocessor required:

:root {
  --brand: oklch(0.62 0.19 260);
  --brand-hover: oklch(0.55 0.19 260);        /* same hue, actually darker */
  --brand-soft: oklch(0.62 0.19 260 / 0.15);  /* same color, transparent */
}

/* Mixing two colors with a proportion - replaces a whole palette file */
.badge-soft {
  background: color-mix(in oklch, var(--brand) 15%, white);
}

Absolute and relative units

The unit you pick is a design decision

UnitRelative toBest for
pxNothing (device-independent pixel)Borders, hairlines, shadows
remRoot <html> font sizeSpacing and typography that must scale globally
emThe element's own font sizeComponent-relative sizing (icon next to text)
%Parent box dimensionFluid widths and heights
chWidth of the "0" glyphLine-length limits (max-width: 70ch)
vw / vhViewport width / heightFull-bleed sections; use with care on mobile
dvh, svh, lvhDynamic / small / large viewport heightMobile-safe full-height layouts

rem and the user's font size

rem is anchored to the root font size — which the user controls in their browser settings. Sizing typography and spacing in rem means a user who sets a 20px base gets a proportionally larger interface, not a broken one. This is an accessibility feature, and it is why html { font-size: 16px; } is an anti-pattern: it stamps out the user's choice.

/* Anti-pattern: overwrites the user's preferred font size */
html { font-size: 16px; }

/* Correct: leave the root alone, scale everything relative to it */
body { font-size: 1rem; }   /* = whatever the user chose */
h1   { font-size: 2rem; }   /* exactly twice the user's choice */

Value functions: the math you actually need

calc(), min(), and max()

/* calc() mixes units - impossible with any single unit */
.sidebar { width: calc(18rem + 2vw); }

/* min() and max() are a media query you can put on one property */
.page {
  width: min(92%, 72rem);        /* fluid, but never wider than 72rem */
  padding-inline: max(1.5rem, 4vw);  /* at least 1.5rem, more on big screens */
}

clamp() is three functions in one

clamp(min, preferred, max) equals max(min, min(preferred, max)). The preferred value is usually a mix of rem (respects user settings) and vw (tracks the screen). The result is fluid type with hard floors and ceilings — the standard technique for headings:

h1 {
  /* never below 1.5rem, never above 2.75rem, fluid in between.
     The rem part keeps it tied to the user's font size. */
  font-size: clamp(1.5rem, 1rem + 2.5vw, 2.75rem);
}

Where functions fit with custom properties

:root {
  --measure: 70ch;         /* readable line length */
  --gutter: clamp(1rem, 3vw, 2.5rem);
}

.prose { max-width: var(--measure); }
.layout { gap: var(--gutter); }   /* one token, every breakpoint */

Custom properties are introduced fully in the Custom Properties lesson; here, note only that they store computed values, so a function result can be defined once and reused.

Practice: build a fluid token set

The task

Open demo/foundations/07_units_and_functions.html and demo/foundations/08_color_spaces.html from the Lab Examples page. Resize the preview and watch which values move and which hold still.

The checklist

  • Write a container width that is fluid but never exceeds 64rem — without a media query.
  • Convert a fixed font-size: 24px heading to rem and verify it at a 20px user base.
  • Produce hover, soft, and border variants of one brand color using only oklch() and color-mix().
  • Explain to yourself why dvh exists by opening any 100vh page on a phone with a URL bar.