CSS Reference Guide

All TopicsPlayground

CSS Functions

CSS includes powerful built-in functions for calculations, dynamic values, responsive sizing, and more. Master these to write flexible, maintainable stylesheets.

calc()clamp()var()min/maxResponsiveDynamic

calc()

Performs math at render time. The killer feature: you can mix different units in a single expression. Supports +, -, *, and /.

CSS
/* Mix percentages with pixels */
.sidebar {
  width: calc(100% - 300px);
}

/* Use with viewport units */
.full-height {
  height: calc(100vh - 80px);  /* viewport minus header */
}

/* With custom properties */
:root {
  --header-h: 64px;
  --gap: 1.5rem;
}
.content {
  height: calc(100vh - var(--header-h));
  padding: calc(var(--gap) * 2);
}

/* Nested calc (valid but often unnecessary) */
.nested {
  width: calc(100% - calc(2 * 20px));
  /* same as: calc(100% - 40px) */
}
Live Example — calc() Mixing Units
width: calc(100% - 80px)
width: calc(50% + 60px)

Resize the browser window to see how calc() mixes percentages with pixels.

Spaces Around + and - Are Required

calc(100%-20px) will not work. You must have spaces around + and - operators: calc(100% - 20px). The * and / operators do not require spaces but including them is best practice.

min()

Returns the smallest value from a comma-separated list. Acts as a dynamic upper-bound (the value will never exceed the smallest option).

CSS
/* Width grows with viewport but caps at 800px */
.container {
  width: min(90vw, 800px);
}

/* Responsive padding */
.section {
  padding: min(5vw, 3rem);
}

/* Can accept more than 2 values */
.box {
  width: min(100%, 50vw, 600px);
}
Live Example — min() as a Max Width
width: min(90%, 500px)

On narrow screens this bar is 90% wide. On wider screens it caps at 500px.

max()

Returns the largest value from a comma-separated list. Acts as a dynamic lower-bound (the value will never go below the largest option).

CSS
/* Font size never below 16px */
.text {
  font-size: max(1vw, 16px);
}

/* Minimum width of 300px */
.card {
  width: max(30%, 300px);
}

/* Ensure padding is at least 1rem */
.padded {
  padding: max(2vw, 1rem);
}

clamp()

The most powerful sizing function. Syntax: clamp(MIN, PREFERRED, MAX). The value scales with the preferred value but never goes below MIN or above MAX.

CSS
/* Fluid typography — scales between 1rem and 2.5rem */
h1 {
  font-size: clamp(1rem, 4vw, 2.5rem);
}

/* Responsive width — fluid between 300px and 800px */
.container {
  width: clamp(300px, 80%, 800px);
}

/* Fluid spacing */
.section {
  padding: clamp(1rem, 5vw, 4rem);
  gap: clamp(0.5rem, 2vw, 2rem);
}
Live Example — clamp() Fluid Typography
This Heading Scales Fluidly
Resize the browser to see both the heading and this body text scale smoothly between their minimum and maximum sizes using clamp().
Live Example — clamp() Fluid Width
clamp(200px, 60%, 500px)

var()

Reads the value of a CSS custom property (variable). Supports fallback values and nesting.

CSS
/* Define variables */
:root {
  --primary: #667eea;
  --radius: 12px;
  --gap: 1.5rem;
}

/* Use with var() */
.card {
  background: var(--primary);
  border-radius: var(--radius);
  margin: var(--gap);
}

/* Fallback value (used if variable is not defined) */
.button {
  color: var(--accent, #f5576c);
  /* If --accent is undefined, #f5576c is used */
}

/* Nested fallback */
.link {
  color: var(--link-color, var(--primary, blue));
  /* Tries --link-color, then --primary, then blue */
}

/* Variables in calc() */
.spacer {
  margin-top: calc(var(--gap) * 2);
}

env()

Access environment variables defined by the user agent. Most commonly used for safe area insets on devices with notches or rounded corners.

CSS
/* Enable safe area insets */
@viewport {
  viewport-fit: cover;
}

/* Also set in the HTML meta tag: */
/* <meta name="viewport" content="viewport-fit=cover"> */

.bottom-bar {
  padding-bottom: env(safe-area-inset-bottom, 0px);
}

.full-bleed {
  padding-left: env(safe-area-inset-left);
  padding-right: env(safe-area-inset-right);
  padding-top: env(safe-area-inset-top);
  padding-bottom: env(safe-area-inset-bottom);
}

/* Combine with calc() */
.navbar {
  height: calc(60px + env(safe-area-inset-top));
  padding-top: env(safe-area-inset-top);
}

attr()

Returns the value of an HTML attribute as a string. Currently only works reliably in the content property (for ::before / ::after). A common pattern for pure-CSS tooltips.

CSS
/* Pure CSS Tooltip using attr() */
[data-tooltip] {
  position: relative;
  cursor: help;
}

[data-tooltip]::after {
  content: attr(data-tooltip);
  position: absolute;
  bottom: 125%;
  left: 50%;
  transform: translateX(-50%);
  background: #1a1a2e;
  color: #fff;
  padding: 0.4rem 0.8rem;
  border-radius: 6px;
  font-size: 0.8rem;
  white-space: nowrap;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.3s;
}

[data-tooltip]:hover::after {
  opacity: 1;
}
Live Example — Pure CSS Tooltip with attr()
Hover me attr() magic How it works

url()

References external resources such as images, fonts, or SVGs. Used in properties like background-image, @font-face src, list-style-image, and cursor.

CSS
.hero {
  background-image: url('images/hero-bg.jpg');
}

.icon {
  background-image: url('data:image/svg+xml,...');  /* inline SVG */
}

@font-face {
  font-family: 'MyFont';
  src: url('fonts/myfont.woff2') format('woff2');
}

.custom-cursor {
  cursor: url('cursor.png'), auto;
}

Color Functions

CSS provides multiple functions for defining colors with different color models.

CSS
/* RGB — red, green, blue (0-255 or 0%-100%) */
.rgb-old { color: rgb(102, 126, 234); }
.rgba    { color: rgba(102, 126, 234, 0.8); }

/* Modern syntax (no commas, / for alpha) */
.rgb-new { color: rgb(102 126 234 / 0.8); }

/* HSL — hue (0-360), saturation (%), lightness (%) */
.hsl  { color: hsl(230, 75%, 66%); }
.hsla { color: hsla(230, 75%, 66%, 0.8); }

/* Modern syntax */
.hsl-new { color: hsl(230 75% 66% / 0.8); }

/* HSL is great for generating shades */
:root {
  --hue: 230;
  --primary-light: hsl(var(--hue), 75%, 80%);
  --primary: hsl(var(--hue), 75%, 66%);
  --primary-dark: hsl(var(--hue), 75%, 45%);
}

counter() & counters()

CSS counters let you auto-number elements without JavaScript. Define with counter-reset, increment with counter-increment, and display with counter().

CSS
/* Basic auto-numbering */
.steps {
  counter-reset: step-counter;
}

.steps li {
  counter-increment: step-counter;
}

.steps li::before {
  content: counter(step-counter) ". ";
  font-weight: 700;
  color: #667eea;
}

/* Nested counters (e.g., 1.1, 1.2, 2.1) */
ol {
  counter-reset: section;
  list-style-type: none;
}

li::before {
  counter-increment: section;
  content: counters(section, ".") " ";
}
Live Example — CSS Counter Auto-Numbering
Define variables with :root
Apply styles with var()
Use calc() for dynamic spacing
Add responsive sizing with clamp()

Nesting Functions

CSS functions can be nested inside each other, creating powerful expressions.

CSS
/* calc() inside clamp() */
.fluid-container {
  width: clamp(300px, calc(100% - 4rem), 1200px);
}

/* var() inside calc() */
.dynamic-padding {
  padding: calc(var(--base-spacing) * 3);
}

/* var() inside clamp() */
.responsive-text {
  font-size: clamp(
    var(--min-font, 1rem),
    calc(0.5rem + 2vw),
    var(--max-font, 2.5rem)
  );
}

/* min()/max() inside calc() */
.safe-width {
  width: calc(min(100vw, 1400px) - 4rem);
}

Gotchas

Spaces in calc() Are Mandatory

The + and - operators in calc() require spaces on both sides. calc(100%-20px) fails silently. Write calc(100% - 20px).

min() and max() Naming Confusion

min() acts like a max-width (caps the upper bound). max() acts like a min-width (sets a floor). The names can feel counterintuitive until you think about what value gets selected.

attr() is Limited to content

Currently, attr() only works reliably inside the content property. The CSS spec defines future support for using attr() in other properties with type-casting (attr(data-width length)), but browser support is not yet available.

Unitless 0 in calc()

While margin: 0; works fine, inside calc() you should be explicit: calc(100% - 0px) is clearer, though calc(100% - 0) also works. The issue arises when doing multiplication — one value must be unitless.

Pro Tips

clamp() Replaces Media Queries for Typography

Instead of writing multiple breakpoints for font sizes, a single font-size: clamp(1rem, 2.5vw, 2rem) scales smoothly across all viewport widths with zero media queries.

Build a Fluid Type Scale

Define your entire type scale with custom properties and clamp: --h1: clamp(2rem, 5vw, 3.5rem), --h2: clamp(1.5rem, 3.5vw, 2.5rem), etc. This gives you a fully responsive typography system in just a few lines.

Use min() as a Simpler max-width

width: min(90%, 800px) is equivalent to width: 90%; max-width: 800px; but in a single property. Cleaner and works everywhere widths are accepted.

Counter Styles Beyond Numbers

You can format counters with counter(name, style) where style is any list-style-type value: counter(step, upper-alpha) produces A, B, C. Use counter(step, upper-roman) for I, II, III.

Debugging calc() Failures

If a calc() expression fails, the entire property is ignored silently. Use browser DevTools — an invalid calc will show the property as crossed out or missing. Check for missing spaces around + and -.

Previous Filters & Effects