CSS Units, Values, and Colors

Introduction

CSS properties become useful only when you provide the right values. Choosing between px, rem, %, vw, or clamp() changes how your UI scales across devices. This chapter explains the most practical CSS value types and color formats so your styles are consistent, responsive, and readable.

Prerequisites

  • Basic CSS syntax and selectors
  • Basic understanding of cascade and inheritance
  • A page linked to an external stylesheet

Common CSS Value Types

In daily CSS work, you mostly use:

  • keywords (auto, none, bold, center)
  • lengths (16px, 1rem, 50%)
  • numbers (1.5 for line-height)
  • colors (#2563eb, rgb(...), hsl(...))
  • functions (calc(...), min(...), max(...), clamp(...))

Example:

css
.container {
  width: min(960px, 100% - 2rem);
  margin: 0 auto;
}

Absolute vs Relative Units

Absolute Unit (px)

px is predictable and common for:

  • borders
  • shadows
  • small UI details
css
.card {
  border: 1px solid #e2e8f0;
}

Relative Units (rem, em, %, vw, vh)

Relative units change with a reference—they are not locked to a fixed pixel size. First, remember what each abbreviation means:

UnitFull English nameMeaning
remroot emRelative to the root element font size (usually html)
emem (the typographic em unit)Relative to the current element’s own font size
%percentRelative to a parent / containing-block reference size
vwviewport widthRelative to viewport width (visible browser area width)
vhviewport heightRelative to viewport height (visible browser area height)

One-line comparison:

  • rem: site-wide scale, follows the root font size
  • em: follows the current text size, more flexible inside components
  • %: follows the parent container, good for fluid widths
  • vw / vh: follow the visible screen area, good for full-screen heroes

Relative units adapt better to layout context, viewport size, or root font size. Details below.

rem vs em

These two are frequently confused.

  • rem = root em: 1rem = the root element (html) font size
  • em = relative to the current element: 1em = this element’s own font size

Example:

css
html {
  font-size: 16px;
}
 
.panel {
  font-size: 20px;
  padding: 1em;   /* 20px: relative to .panel’s own font size */
  margin: 1rem;   /* 16px: relative to the html font size */
}

Practical guideline:

  • use rem for global spacing and typography scales
  • use em for component-local proportional sizing

Percentages (% / percent)

% means percent. A percentage is relative to a reference context, not a fixed pixel size.

css
.layout {
  width: 80%;   /* about 80% of the parent container’s width */
}

The reference depends on the property:

  • width: 80% — usually relative to the containing block width
  • padding: 10% — also relative to the container width (this often surprises beginners)

Percentages are useful for fluid layouts, but always test on both narrow and wide screens.

Viewport Units (vw, vh, dvh)

The viewport is the visible area of the browser. Viewport units scale with that area:

  • vw = viewport width: 1vw = 1% of viewport width
  • vh = viewport height: 1vh = 1% of viewport height
  • dvh = dynamic viewport height: better for mobile browser top/bottom UI changes
css
.hero {
  min-height: 60vh;   /* at least 60% of the viewport height */
}

Mobile note:

  • browser UI bars can affect traditional vh
  • newer units like dvh help with dynamic viewport behavior
css
.screen {
  min-height: 100dvh;   /* closer to the real visible height */
}

Unitless Values

Some properties accept a plain number—no px, no rem, just a number.

The most important example is line-height:

css
p {
  /* line height = current font size × 1.7 (e.g. ~27.2px when font-size is 16px) */
  line-height: 1.7;
}

Meaning:

  • 1.7 is a multiplier, not a fixed length
  • actual line height = the paragraph’s current font-size × 1.7
  • when font size grows, line height grows with it; when font size shrinks, line height shrinks too

So unitless line-height scales naturally with font size and is usually safer and easier to maintain than something like line-height: 27px.

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

These functions make values more adaptive: mix units in one calculation, pick the smaller or larger value, or clamp a value into a range.

calc()

calc() = calculate. Use it for add/subtract/multiply/divide in CSS, often combining a percentage with a fixed spacing value.

css
.content {
  /* width = 100% of the parent, minus about 2rem of side space */
  width: calc(100% - 2rem);
}

Meaning:

  • 100%: fill the available width first
  • - 2rem: then subtract a bit of margin/padding space
  • the final width grows and shrinks with the parent, while always leaving that 2rem

Best when a container should nearly fill its parent but still keep a fixed gap.

min() and max()

  • min() = minimum (pick the smaller value)
  • max() = maximum (pick the larger value)
css
.box {
  /* use whichever is smaller: 900px or 100% */
  width: min(900px, 100%);
}

Meaning:

  • when the parent is wide: 100% may be larger than 900px, so you get 900px (content does not grow forever)
  • when the parent is narrow: 100% is smaller than 900px, so you get 100% (shrinks with the parent instead of overflowing)

In one sentence: min(900px, 100%) is a common “fluid width, but never wider than 900px” pattern.

max() does the opposite and picks the larger value—for example, width: max(200px, 30%) means at least 200px, and possibly more.

clamp()

clamp() = clamp (keep a value inside a range). Great for fluid typography:

css
h1 {
  /* clamp(min, preferred, max) */
  font-size: clamp(1.6rem, 1.2rem + 2vw, 2.8rem);
}

Meaning:

  • minimum 1.6rem: never smaller than this (small-screen floor)
  • preferred 1.2rem + 2vw: the “ideal” size that changes with viewport width
  • maximum 2.8rem: never larger than this (large-screen ceiling)

Result: heading size grows with the viewport, but stays between the min and max.

Color Formats in CSS

Common formats:

  • named colors (red, black) — quick but limited
  • hex (#2563eb, #0f172a)
  • rgb() / rgba()
  • hsl() / hsla()

Hex

css
.title {
  color: #2563eb;
}

RGB / RGBA

css
.overlay {
  background: rgba(15, 23, 42, 0.55);
}

HSL / HSLA

css
.badge {
  background: hsl(221 83% 53%);
  color: hsl(210 40% 98%);
}

HSL is often convenient when adjusting hue, saturation, and lightness systematically.

Opacity and Transparency

Two common approaches:

  • set opacity on the whole element (opacity = how opaque something is)
  • use an alpha channel in the color value (rgba, hsla, or modern hex with alpha)
css
.card {
  /* the whole card becomes 90% opaque (~10% transparent), including children */
  opacity: 0.9;
}

Meaning:

  • opacity is usually a value from 0 to 1
  • 1 = fully opaque, 0 = fully invisible
  • 0.9 = still visible, but slightly see-through

Note: opacity affects the entire element, including text and children. Titles and buttons inside the card fade too.

If you only want a transparent background while keeping text sharp, prefer an alpha channel in the background color:

css
.card {
  /* rgba(red, green, blue, alpha): white background at 0.9 opacity */
  background: rgba(255, 255, 255, 0.9);
}

Meaning:

  • 255, 255, 255 = white
  • the final 0.9 = only the background is semi-transparent
  • text and borders are unaffected (unless you also give them transparent colors)

currentColor and Reuse

currentColor = current color. It automatically uses the element’s computed color value.

css
.link {
  color: #2563eb;                       /* set text color to blue first */
  border-bottom: 1px solid currentColor; /* underline border uses that same blue */
}

Meaning:

  • you do not rewrite #2563eb for the border
  • later, change only color and the border follows
  • useful for keeping icon strokes, underlines, and small accents in sync with text

This is helpful for consistent icon, border, and text coloring.

Practical Unit and Color Guidelines

  • Use rem for scalable typography and spacing
  • Use px for thin borders and precision details
  • Use % for flexible container widths
  • Use clamp() for fluid text sizing
  • Keep a small semantic color palette (primary, text, muted, surface, danger)
  • Prefer sufficient contrast for readability and accessibility

Tip

Design Tokens Early

Store key sizes and colors in CSS custom properties. It makes theme changes and consistency much easier.

Example: A Small Token Set

Do not let the word “token” scare you. Think of it as: give common colors and spacing a name, then reuse that name instead of copying raw values everywhere.

In CSS, this naming approach is called custom properties (also CSS variables). Names must start with --.

Step 1: Define the names site-wide

css
/* :root is the document root (usually html); values here are available everywhere */
:root {
  --text: #0f172a;      /* main text color: dark */
  --muted: #475569;     /* secondary text: a bit gray */
  --primary: #2563eb;   /* brand/primary blue for buttons and links */
  --surface: #ffffff;   /* surface/card background: white */
  --space-1: 0.5rem;    /* small spacing */
  --space-2: 1rem;      /* medium spacing */
}

What this does:

  • --text, --primary, and so on are variable names
  • the value after the colon is the real value (color or size)
  • define once, then reuse anywhere on the site

Step 2: Read the names with var()

var() = variable. Write var(--name) to mean “insert the value for this name here.”

css
.card {
  background: var(--surface);  /* background uses --surface, i.e. white */
  color: var(--text);          /* text uses --text, i.e. dark */
  padding: var(--space-2);     /* padding uses --space-2, i.e. 1rem */
}

Compare:

You writeRoughly equals
background: var(--surface);background: #ffffff;
color: var(--text);color: #0f172a;
padding: var(--space-2);padding: 1rem;

Why bother?

  • change a theme color in one place inside :root
  • no hunting through dozens of components for #2563eb
  • meaningful names (--text, --primary) read better than raw hex codes

Tip

Remember Just These Two Lines

  1. :root { --name: value; } — define
  2. var(--name) — use

Common Mistakes

MistakeWhy it hurtsBetter approach
Using only px everywherePoor scaling on user font settingsMix rem and % strategically
Fixed vh full-screen sections on mobileBrowser UI can cause jumpy heightTest with dvh where supported
Low-contrast text colorHard to read, accessibility issuesImprove contrast ratio
Using opacity on parent for background effectFades text unintentionallyUse alpha in background color
Magic numbers without patternInconsistent spacing systemDefine spacing scale variables

Mini Exercise

  1. Convert a card component from px spacing to rem
  2. Make heading size fluid using clamp()
  3. Replace hardcoded repeated colors with custom properties
  4. Add a transparent overlay with rgba(...) instead of parent opacity
  5. Test readability on mobile and desktop widths

FAQ

Should I stop using px completely?

No. px is useful for precise details. Use rem and relative units for scalable typography and layout.

Is rem always better than em?

Not always. rem is stable for global scales; em is useful for component-relative behavior.

Is hex better than rgb()?

Both are valid. Choose based on team preference and workflow. rgb()/hsl() can be easier for dynamic adjustments.

Why does 100vh feel wrong on some phones?

Mobile browser UI bars can change visible viewport height. Newer units like dvh reduce that issue.

What is the easiest way to keep values consistent?

Use CSS custom properties for color and spacing tokens, then reuse them across components.