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.5forline-height) - colors (
#2563eb,rgb(...),hsl(...)) - functions (
calc(...),min(...),max(...),clamp(...))
Example:
.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
.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:
| Unit | Full English name | Meaning |
|---|---|---|
rem | root em | Relative to the root element font size (usually html) |
em | em (the typographic em unit) | Relative to the current element’s own font size |
% | percent | Relative to a parent / containing-block reference size |
vw | viewport width | Relative to viewport width (visible browser area width) |
vh | viewport height | Relative to viewport height (visible browser area height) |
One-line comparison:
rem: site-wide scale, follows the root font sizeem: follows the current text size, more flexible inside components%: follows the parent container, good for fluid widthsvw/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 sizeem= relative to the current element:1em= this element’s own font size
Example:
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
remfor global spacing and typography scales - use
emfor component-local proportional sizing
Percentages (% / percent)
% means percent. A percentage is relative to a reference context, not a fixed pixel size.
.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 widthpadding: 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 widthvh= viewport height:1vh= 1% of viewport heightdvh= dynamic viewport height: better for mobile browser top/bottom UI changes
.hero {
min-height: 60vh; /* at least 60% of the viewport height */
}Mobile note:
- browser UI bars can affect traditional
vh - newer units like
dvhhelp with dynamic viewport behavior
.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:
p {
/* line height = current font size × 1.7 (e.g. ~27.2px when font-size is 16px) */
line-height: 1.7;
}Meaning:
1.7is 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.
.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)
.box {
/* use whichever is smaller: 900px or 100% */
width: min(900px, 100%);
}Meaning:
- when the parent is wide:
100%may be larger than900px, so you get900px(content does not grow forever) - when the parent is narrow:
100%is smaller than900px, so you get100%(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:
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
.title {
color: #2563eb;
}RGB / RGBA
.overlay {
background: rgba(15, 23, 42, 0.55);
}HSL / HSLA
.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
opacityon the whole element (opacity = how opaque something is) - use an alpha channel in the color value (
rgba,hsla, or modern hex with alpha)
.card {
/* the whole card becomes 90% opaque (~10% transparent), including children */
opacity: 0.9;
}Meaning:
opacityis usually a value from0to11= fully opaque,0= fully invisible0.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:
.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.
.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
#2563ebfor the border - later, change only
colorand 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
remfor scalable typography and spacing - Use
pxfor 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
/* :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.”
.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 write | Roughly 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
:root { --name: value; }— definevar(--name)— use
Common Mistakes
| Mistake | Why it hurts | Better approach |
|---|---|---|
Using only px everywhere | Poor scaling on user font settings | Mix rem and % strategically |
Fixed vh full-screen sections on mobile | Browser UI can cause jumpy height | Test with dvh where supported |
| Low-contrast text color | Hard to read, accessibility issues | Improve contrast ratio |
Using opacity on parent for background effect | Fades text unintentionally | Use alpha in background color |
| Magic numbers without pattern | Inconsistent spacing system | Define spacing scale variables |
Mini Exercise
- Convert a card component from
pxspacing torem - Make heading size fluid using
clamp() - Replace hardcoded repeated colors with custom properties
- Add a transparent overlay with
rgba(...)instead of parentopacity - 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.