CSS Functions
CSS includes powerful built-in functions for calculations, dynamic values, responsive sizing, and more. Master these to write flexible, maintainable stylesheets.
calc()
Performs math at render time. The killer feature: you can mix different units in a single expression. Supports +, -, *, and /.
/* 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) */ }
Resize the browser window to see how calc() mixes percentages with pixels.
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).
/* 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); }
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).
/* 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.
/* 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); }
var()
Reads the value of a CSS custom property (variable). Supports fallback values and nesting.
/* 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.
/* 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.
/* 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; }
url()
References external resources such as images, fonts, or SVGs. Used in properties like background-image, @font-face src, list-style-image, and cursor.
.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.
/* 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().
/* 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, ".") " "; }
:rootvar()calc() for dynamic spacingclamp()Nesting Functions
CSS functions can be nested inside each other, creating powerful expressions.
/* 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
The + and - operators in calc() require spaces on both sides. calc(100%-20px) fails silently. Write calc(100% - 20px).
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.
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.
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
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.
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.
width: min(90%, 800px) is equivalent to width: 90%; max-width: 800px; but in a single property. Cleaner and works everywhere widths are accepted.
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.
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 -.