A few CSS functions do a surprising amount of work. They replace media queries, magic numbers and small bits of JavaScript. These are the ones we use in almost every stylesheet, each with the mistake that makes it quietly do nothing.
| Function | Use it for |
|---|---|
calc() | Mixing units, like a percentage minus pixels |
var() | Reusing values and theming |
clamp() | Fluid sizes with a floor and a ceiling |
min() / max() | Picking the smaller or larger of two sizes |
cubic-bezier() | Custom animation speed curves |
calc(): mixing units
calc() does maths on values, and its real power is mixing units the browser only knows at render time:
.main {
width: calc(100% - 280px); /* full width minus a sidebar */
}
.full-height {
height: calc(100vh - var(--header-height));
} The trap: spaces around + and - are required. calc(100%-280px) is invalid, because -280px reads as a negative number, and the browser drops the whole line without a warning.
var(): custom properties for themes
Declare a value once as a custom property, and read it with var() everywhere else:
:root {
--brand: #2563eb;
--radius: 8px;
}
.button {
background-color: var(--brand);
border-radius: var(--radius);
}
.link {
color: var(--brand);
} The original version of this article had a bug that we see all the time: color: --brand. Without var(), the browser treats it as an invalid value and ignores the declaration. Always wrap the name.
Custom properties are live, unlike Sass variables. Change one on an element and everything inside it updates, which is how a dark mode can be a single class:
.dark {
--brand: #93c5fd;
--page-bg: #0f172a;
}
.link {
color: var(--brand, #2563eb); /* second value is the fallback */
} clamp(), min() and max(): fluid sizes
clamp(minimum, preferred, maximum) is the one that replaces media queries:
h1 {
font-size: clamp(1.75rem, 1rem + 3vw, 3rem);
} The heading grows with the viewport but never drops below 1.75rem on a phone or goes past 3rem on a wide screen. Mixing a rem value into the preferred size keeps text zoomable, which pure vw sizes break.
min() and max() are the two halves on their own:
.container {
width: min(1200px, 100% - 32px); /* 1200px wide, with a gutter on small screens */
}
.sidebar {
width: max(240px, 20%); /* never narrower than 240px */
} That one min() line replaces the old max-width plus padding plus media query pattern for page containers. It pairs well with the layout techniques in the elements of responsive web design.
cubic-bezier(): animation timing
Timing functions control how an animation speeds up and slows down. The keywords (ease, ease-in-out) are fixed curves; cubic-bezier() lets you draw your own with four numbers, the x and y of two control points:
.panel {
transition: transform 300ms cubic-bezier(0.2, 0.8, 0.2, 1);
} The two x values must be between 0 and 1. The y values can go outside that range, which is how you get an overshoot or bounce, for example cubic-bezier(0.34, 1.56, 0.64, 1). Don't guess the numbers: the curve editor in Chrome and Firefox DevTools lets you drag the points and copy the result.
Respect reduced motion
prefers-reduced-motion media query, so people who turned animations off in their system settings aren't shown them.
Two more worth knowing
minmax()in grid layouts, as inrepeat(auto-fill, minmax(240px, 1fr)), gives responsive card grids with no media queries.color-mix()blends two colours, for hover states from a single brand variable:color-mix(in srgb, var(--brand), black 15%).
MDN keeps a full list of CSS functions, with browser support for each. If you are working on components, styling a parent by its children with :has() and truncating text with three dots solve the next two problems you are likely to hit.
Frequently Asked Questions
What are CSS functions?
Built-in functions you use inside property values, such as calc(), var(), clamp() and rgb(). They compute a value when the browser renders the page, so it can depend on the viewport, other units or custom properties.
Why is my CSS var() not working?
Usually the name is written without var(). A custom property is declared as --name: value, but must be read as var(--name). Writing color: --name is invalid and the browser ignores the whole declaration.
What is the difference between clamp() and min() or max()?
min() picks the smallest of its values and max() the largest. clamp(min, preferred, max) combines them: it uses the preferred value but never goes below the minimum or above the maximum, which is ideal for fluid font sizes.
Do I need spaces around operators in calc()?
Around + and -, yes. calc(100% - 20px) works, calc(100%-20px) does not, because -20px reads as a negative number. * and / don't need spaces, but adding them keeps things consistent.