What is CSS calc()?

calc() is a CSS function that lets you do math right inside your CSS values. Instead of hard-coding a number, you can combine lengths like px, %, rem, vw, and more.

The big idea: you can express layouts as “this minus that”, “half of this plus a bit”, or “scale with the viewport but keep a minimum”.

 .demo { width: calc(300px + 40px); } 
 .demo { width: calc(300px - 40px); } 
 .demo { width: calc(300px * 1.2); } 
 .demo { width: calc(300px / 2); } 
 *, ::before, ::after { box-sizing: border-box; } .wrap { padding: 20px; display: grid; gap: 12px; font-family: ui-monospace, SFMono-Regular, Menlo; } .demo { height: 70px; border: 3px solid #111; background: #f2f2f2; display: grid; place-items: center; } 
 
I am .demo

Click the snippets to switch the calc() formula.

In real projects, you’ll most often use calc() for sizing, spacing, and positioning. The math runs in the browser, so it stays responsive.

CSS calc() syntax rules you must know

calc() supports four operators: +, -, *, /. It also has some rules that can confuse beginners.

Whitespace matters around + and -

Inside calc(), you should put spaces around + and -. Many browsers are forgiving now, but this is still the safest habit.

  • Good: calc(100% - 24px)
  • Risky: calc(100%-24px)

Mixing units is allowed (if the math makes sense)

You can add or subtract different length units, like % and px. That’s one of the main reasons calc() exists.

Multiplication and division rules

Multiplication and division are more strict:

  • calc(20px * 2) is valid.
  • calc(2 * 20px) is also valid.
  • calc(20px * 2px) is invalid (you can’t multiply lengths together).
  • calc(20px / 2) is valid.
  • calc(20px / 2px) is invalid.
 .card { width: calc(100% - 40px); } 
 .card { width: calc(50% + 80px); } 
 .card { width: calc((100% - 40px) / 2); } 
 *, ::before, ::after { box-sizing: border-box; } .page { padding: 20px; font-family: ui-monospace, SFMono-Regular, Menlo; } .frame { border: 3px solid #111; padding: 20px; background: #f2f2f2; } .card { border: 3px solid #111; background: #fff; padding: 16px; height: 130px; display: grid; place-items: center; } 
 
Resize me by changing snippets

The classic layout use: 100% minus fixed gap

This is the “hello world” of practical calc(): you want something to fill available space, but you need to subtract a fixed value like padding, a sidebar width, or a gap.

Example: two columns with a fixed gap

Here we build two columns that always fit, with a gap between them. Each column width is “half the container minus half the gap”.

 .grid { gap: 24px; }

.col {
width: calc((100% - 24px) / 2);
}
 .grid { gap: 48px; } .col { width: calc((100% - 48px) / 2); } 
 .grid { gap: 12px; } .col { width: calc((100% - 12px) / 2); } 
 *, ::before, ::after { box-sizing: border-box; } .wrap { padding: 20px; font-family: ui-monospace, SFMono-Regular, Menlo; } .grid { border: 3px solid #111; background: #f2f2f2; padding: 20px; display: flex; flex-wrap: wrap; } .col { border: 3px solid #111; background: #fff; height: 100px; display: grid; place-items: center; } 
 
Column A
Column B

Could you do this with flex or grid without calc()? Often yes. But calc() becomes very handy when the “gap math” is part of your design rules.

Responsive typography with calc()

A common beginner-friendly trick: make text scale with the viewport while still respecting a base size. vw responds to the viewport width, and you can combine it with rem for accessibility.

 h2 { font-size: calc(1.2rem + 3vw); } 
 h2 { font-size: calc(1rem + 2vw); } 
 h2 { font-size: calc(1.6rem + 4vw); } 
 *, ::before, ::after { box-sizing: border-box; } .page { padding: 20px; font-family: ui-monospace, SFMono-Regular, Menlo; } .panel { border: 3px solid #111; background: #f2f2f2; padding: 20px; } p { line-height: 1.5; max-width: 60ch; } 
 

Fluid heading

Resize horizontally your browser and watch the heading size adapt.

CSS calc() vs clamp() for font-size

For font sizing, you’ll usually want clamp() over calc(). The reason is simple: fluid type is great, but it can get too small on tiny screens and too large on very wide monitors. clamp() gives you fluid sizing with safety rails.

The syntax is: clamp(min, preferred, max)

  • min is the smallest size you will allow (your “never go below this” value). This is often a rem value for readability.
  • preferred is the fluid formula that can grow and shrink, often something like 1rem + 1vw. This is the “responsive” part.
  • max is the largest size you will allow (your “never go above this” value).

In plain English: the browser evaluates the preferred value, but if it ends up smaller than min, it uses min. If it ends up larger than max, it uses max. Otherwise, it uses the preferred value.

Example: font-size: clamp(1.2rem, 1rem + 1vw, 2rem); means: “Scale the font fluidly with the viewport, but never below 1.2rem and never above 2rem.”

Why this beats calc() for typography:

  • Readable minimum: you won’t accidentally create microscopic text on small screens.
  • Controlled maximum: headings won’t balloon on huge displays.
  • Cleaner intent: the code communicates “fluid, but bounded” immediately.
  • Less tweaking: you spend less time chasing edge cases across device sizes.
h3 {
  font-size: calc(1.2rem + 1vw);
}
  
h3 {
  font-size: clamp(1.2rem, 1rem + 1vw, 2rem);
}
  
h3 {
  font-size: 1.6rem;
}
  
*,
::before,
::after {
  box-sizing: border-box;
}

.box {
  padding: 20px;
  border: 3px solid #111;
  background: #f2f2f2;
  font-family: ui-monospace, SFMono-Regular, Menlo;
}

p {
  max-width: 60ch;
  line-height: 1.5;
}
  

Readable, but flexible

Click the snippets and resize to see the difference.

Spacing systems with calc() and CSS variables

Once you have a base spacing unit, you can derive variations with math. This is especially handy for consistent padding, margins, and gaps.

 .stage { --space: 16px; }

.card {
padding: calc(var(--space) * 1);
}
 .stage { --space: 16px; } .card { padding: calc(var(--space) * 1.5); } 
 .stage { --space: 16px; } .card { padding: calc(var(--space) * 2); } 
 *, ::before, ::after { box-sizing: border-box; } .stage { padding: 20px; font-family: ui-monospace, SFMono-Regular, Menlo; } .card { border: 3px solid #111; background: #f2f2f2; max-width: 520px; } .card h4 { margin: 0 0 10px 0; } .card p { margin: 0; line-height: 1.5; } 
 

Spacing scale

Same base unit, different multipliers.

Notice how calc(var(--space) * 1.5) gives you “design-system-like” spacing without guessing new numbers.

Centering and positioning with CSS calc()

Sometimes you want to place something relative to the middle, but with a deliberate offset. calc() lets you do that in a clean, readable way.

Example: center an element and nudge it

Here we place a sticker-like label in the center, then push it by a fixed amount. This is mostly for learning, because modern CSS also gives you other options like transforms.

 .sticker { left: calc(50% - 80px); top: calc(50% - 20px); } 
 .sticker { left: calc(50% - 80px + 40px); top: calc(50% - 20px - 30px); } 
 .sticker { left: calc(50% - 80px - 40px); top: calc(50% - 20px + 30px); } 
 *, ::before, ::after { box-sizing: border-box; } .stage { padding: 20px; font-family: ui-monospace, SFMono-Regular, Menlo; } .canvas { height: 240px; border: 3px solid #111; background: #f2f2f2; position: relative; overflow: hidden; } .sticker { position: absolute; width: 160px; height: 40px; border: 3px solid #111; background: #fff; display: grid; place-items: center; } 
 
Sticker

CSS calc() with viewport units

Viewport units like vw and vh can make designs feel fluid, but they can also get too small or too large on various screen sizes. calc() helps you “tame” them by mixing in stable units like rem or px.

Example: fluid card width

This card gets wider on larger screens thanks to vw, but still keeps a sensible base.

 .card { width: calc(260px + 10vw); } 
 .card { width: calc(220px + 20vw); } 
 .card { width: calc(300px + 5vw); } 
 *, ::before, ::after { box-sizing: border-box; } .wrap { padding: 20px; font-family: ui-monospace, SFMono-Regular, Menlo; } .card { border: 3px solid #111; background: #f2f2f2; padding: 18px; } .card p { margin: 0; line-height: 1.5; } 
 

My width uses calc() with vw.

A small vh note for mobile browsers

On mobile, classic vh can behave oddly because browser UI (address bars) expands and collapses. Modern units like dvh, svh, and lvh exist for that. You can still combine them with calc() the same way.

  • dvh (dynamic viewport height) tracks the viewport as it changes in real time, so the value updates when the browser UI shows or hides.
  • svh (small viewport height) uses the “smallest” possible viewport height (when browser UI is most visible), which helps avoid content being hidden behind bars.
  • lvh (large viewport height) uses the “largest” possible viewport height (when browser UI is minimized), which can make full-screen sections feel more truly full-screen when bars collapse.
 .hero { min-height: calc(100lvh - 60px); } 
 .hero { min-height: calc(100dvh - 60px); } 
 .hero { min-height: calc(100svh - 60px); } 
 *, ::before, ::after { box-sizing: border-box; } .page { padding: 20px; font-family: ui-monospace, SFMono-Regular, Menlo; } .hero { border: 3px solid #111; background: #f2f2f2; display: grid; place-items: center; padding: 20px; } .hero p { margin: 0; max-width: 60ch; line-height: 1.5; } 
 

This box is "viewport height minus 60px". Switch snippets to compare units. This will only make a difference on mobile devices, with the address bar showing or hiding.

Learn more about VH, DVH, SVH, and LVH units in our CSS VH Unit Interactive Tutorial .

Interactive CSS calc()

Now for the fun part: we’ll use sliders to change values inside a calc() expression. Remember the rule: if a playground has sliders, it should have only one CSS snippet.

Example: fluid padding formula

The padding is computed as “base padding + a viewport-based extra. Try playing with both values and see how it changes the feel of the layout.

 .panel { padding: calc(20px + 3vw); } 
 .panel { padding: calc(20px + 6vw); } 
 .panel { padding: calc(20px + 9vw); } 
 *, ::before, ::after { box-sizing: border-box; } .wrap { padding: 20px; font-family: ui-monospace, SFMono-Regular, Menlo; } .panel { border: 3px solid #111; background: #f2f2f2; max-width: 720px; } .panel h4 { margin: 0 0 10px 0; } .panel p { margin: 0; line-height: 1.5; max-width: 60ch; } 
 

Fluid padding

Padding is calc(base + viewport extra).

calc() in transforms and backgrounds

calc() works anywhere a length is accepted, including transforms and background positioning. That means you can create effects like “move half the width plus a little extra”.

Example: translate with calc()

We’ll translate a block by a percentage and add/subtract pixels. This is useful when your design needs a proportional move with a consistent tweak.

 .block { transform: translateX(calc(50% - 20px)); } 
 .block { transform: translateX(calc(50% + 20px)); } 
 .block { transform: translateX(calc(-50% + 20px)); } 
 *, ::before, ::after { box-sizing: border-box; } .stage { padding: 20px; font-family: ui-monospace, SFMono-Regular, Menlo; } .track { border: 3px solid #111; background: #f2f2f2; height: 140px; position: relative; overflow: hidden; } .block { width: 160px; height: 60px; border: 3px solid #111; background: #fff; display: grid; place-items: center; position: absolute; left: 50%; top: 40px; } 
 
Move me

Example: background-position with calc()

Background positioning becomes much easier when you can say “100% minus a little offset”. That’s a common way to place decorative background shapes without adding extra elements.

 .badge { background-position: calc(100% - 12px) calc(0% + 12px); } 
 .badge { background-position: calc(100% - 24px) calc(0% + 24px); } 
 .badge { background-position: calc(100% - 48px) calc(0% + 16px); } 
 *, ::before, ::after { box-sizing: border-box; } .wrap { padding: 20px; font-family: ui-monospace, SFMono-Regular, Menlo; } .badge { border: 3px solid #111; background-color: #f2f2f2; background-image: radial-gradient(circle, #111 0 10px, transparent 11px); background-repeat: no-repeat; width: 320px; padding: 18px; } .badge p { margin: 0; line-height: 1.5; max-width: 45ch; } 
 

My dot is positioned using calc() in background-position.

Common calc() mistakes and how to fix them

1) Missing spaces around + and -

  • Fix: always write calc(100% - 24px), not calc(100%-24px).

2) Trying to multiply two lengths

  • Wrong: calc(10px * 2px)
  • Fix: multiply by a plain number: calc(10px * 2)

3) Forgetting parentheses when it helps readability

Parentheses can prevent confusion, especially when mixing % math with division.

  • Good: calc((100% - 24px) / 2)

4) Mixing units in a way CSS can’t resolve

Most of the time, mixing % with px is fine, because the browser can resolve percentages against something. But if the percentage has no clear reference, the whole property might be ignored.

If something “does nothing”, check DevTools computed styles to see if the property is being dropped.

Practice recipes you can reuse

  • Full width minus padding: width: calc(100% - 40px)
  • Two equal columns with gap: width: calc((100% - gap) / 2)
  • Fluid type: font-size: calc(1rem + 1vw)(although as we saw, clamp() is preferable).
  • Viewport height minus header: min-height: calc(100vh - 60px)
  • Background in corner with offset: background-position: calc(100% - 16px) calc(0% + 16px)

Conclusion

calc() is one of those “small” CSS features that quietly unlocks a ton of layout power. Once you’re comfortable reading and writing expressions like calc(100% - 24px), you’ll start seeing opportunities everywhere: better gutters, smarter sizing, smoother responsive type, and cleaner positioning.

Next time you feel tempted to hard-code a weird number, try asking: Is this really “a number”, or is it a relationship? If it’s a relationship, calc() is probably the tool you want.

Learn even more about CSS Calc() in the CSS Min() Max() Clamp() Interactive Tutorial.