calc-size()

Limited availability

This feature is not Baseline because it does not work in some of the most widely-used browsers.

Experimental: This is an experimental technology
Check the Browser compatibility table carefully before using this in production.

The calc-size() CSS function allows you to perform calculations on intrinsic size values such as auto, fit-content, and max-content; this is not supported by the regular calc() function.

calc-size() return values can also be interpolated, enabling size keyword values to be used in animations and transitions. In effect, including calc-size() in a property value automatically applies interpolate-size: allow-keywords to the selection.

Note however that interpolate-size is inherited, therefore applying it to an element enables interpolation of intrinsic size keywords for every property applied to that element and its children. As a result, interpolate-size is the preferred solution for enabling intrinsic size animations. You should only use calc-size() to enable intrinsic size animations if they also require calculations.

Syntax

css
/* Pass a value through calc-size() */
calc-size(auto, size)
calc-size(fit-content, size)

/* Perform a calculation */
calc-size(min-content, size + 100px)
calc-size(fit-content, size / 2)

/* Calculation including a function */
calc-size(auto, round(up, size, 50px))

Parameters

The calc-size() function's syntax is as follows:

calc-size(<calc-size-basis>, <calc-sum>)

The parameters are:

<calc-size-basis>

The value (most commonly an intrinsic size) that you want to perform a calculation on.

<calc-sum>

An expression that defines the calculation to be performed on the <calc-size-basis>.

Return value

Returns a value equal to the <calc-size-basis> modified by the <calc-sum> expression. As the <calc-size-basis> value is an intrinsic size value, the return value is a modified intrinsic size value that behaves like the intrinsic size value input into the function.

Description

Certain browser layout algorithms have special behaviors for intrinsic sizing keywords. The calc-size() function is explicitly defined to represent an intrinsic size rather than a <length-percentage>, thereby enforcing correctness. calc-size() enables calculations to be performed on intrinsic size values in a safe, well-defined manner.

Valid values for the first argument (<calc-size-basis>)

The first calc-size() argument can be one of the following intrinsic values:

There are also a few special values that this argument can take:

  • A nested calc-size() value. This isn't something you'd be likely to do very often, but it is available ensuring using a CSS variable as the <calc-size-basis> will always work, provided the variable is a valid value for the property calc-size() is being set on. So for example, this will work:

    css
    section {
      height: calc-size(calc-size(max-content, size), size + 2rem);
    }
    

    As will this:

    css
    :root {
      --intrinsic-size: calc-size(max-content, size);
    }
    
    section {
      height: calc-size(var(--intrinsic-size), size + 2rem);
    }
    
  • Another <calc-sum>, with the same restrictions as the <calc-sum> specified for the second argument, except that the size keyword cannot be included. You likely will not do this, as you are no longer doing a calculation on an intrinsic size value, but if a custom property value is a <calc-sum>, the function will still work. For example, this will work directly or if you use a custom property with a value of 300px + 2rem:

    css
    section {
      height: calc-size(300px + 2rem, size / 2);
    }
    
  • The keyword any, which represents an unspecified definite size. In this case, the size keyword cannot be included in the second argument, and the calc-size() returns the result of the second argument calculation. For example:

    css
    section {
      height: calc-size(any, 300px * 1.5); /* Returns 450px */
    }
    

Mixing different intrinsic sizes together in the same calculation doesn't work. For example, max-content - min-content doesn't make sense. calc-size() only allows a single intrinsic size value in each calculation, avoiding this problem.

Valid values for the second argument (<calc-sum>)

The second calc-size() argument is a <calc-sum> expression.

In this expression:

  • The keyword size represents the <calc-size-basis> specified as the first argument.
  • Operands can include size, and any value types that make sense in the context.
  • The +, -, *, and / operators can be included.
  • Other mathematical functions can be included such as round(), max(), or even a nested calc-size().
  • The overall expression must match <length-percentage>, and resolve to a <length>.

Enabling animation of intrinsic size values

calc-size() return values can be interpolated, enabling animations between a <length-percentage> value and a calc-size() intrinsic size return value.

Note: You should avoid animating box model properties if possible, to cut down on layout events and mitigate the resulting impact on performance (see Critical rendering path > Layout).

For example, you could use a transition to animate a container width between 0 and auto like so:

css
section {
  width: 0;
  transition: width ease 1s;
}

section:hover,
section:focus {
  width: calc-size(auto, size);
}

In the above case, we are not calculating anything — we are putting auto into calc-size() and returning it unchanged. The interpolate-size property makes animations like the above simpler to implement in most cases, especially when there are multiple animations to consider. It is inherited and therefore only needs to be declared once on an ancestor property, meaning we could have transitioned between 0 and auto without using calc-size().

The calc-size() function should only be used to enable intrinsic size animations if they also require calculations. For example, in the following case we are animating the width and applying a calculation to the intrinsic size end state:

css
section {
  width: 0;
  transition: width ease 1s;
}

section:hover,
section:focus {
  width: calc-size(auto, size + 2rem);
}

One case in which calc-size() is useful is when you want to animate between an intrinsic size and a modified version of the same intrinsic size. This is not possible with interpolate-size and calc(). For example, the following @keyframes definition animates a container width between fit-content and 70% of the fit-content.

css
@keyframes narrower {
  from {
    width: fit-content;
  }

  to {
    width: calc-size(fit-content, size * 0.7);
  }
}

Note: Note that calc-size() does not enable animating between two different intrinsic size values.

Formal syntax

<calc-size()> = 
calc-size( <calc-size-basis> , <calc-sum> )

<calc-size-basis> =
<intrinsic-size-keyword> |
<calc-size()> |
any |
<calc-sum>

<calc-sum> =
<calc-product> [ [ '+' | '-' ] <calc-product> ]*

<calc-product> =
<calc-value> [ [ '*' | '/' ] <calc-value> ]*

<calc-value> =
<number> |
<dimension> |
<percentage> |
<calc-keyword> |
( <calc-sum> )

<calc-keyword> =
e |
pi |
infinity |
-infinity |
NaN

Examples

Basic calc-size usage

This example shows basic dimension sizing of a container using calc-size()

HTML

The HTML contains a single <section> element that contains some child content.

html
<section>
  <h2>Favorite quote</h2>

  <p>
    Fashion is something so ugly it has to be changed every fifteen minutes.
  </p>
</section>

CSS

In the CSS, we use flexbox to center the child elements inside the <section>, and set thewidth and height of the <section> to calc-size() functions. The width is set equal to fit-content plus 6rem. The height is set to auto multiplied by two.

css
section {
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;

  width: calc-size(fit-content, size + 6rem);
  height: calc-size(auto, size * 2);
}

The rest of the CSS has been hidden for brevity.

Result

We've created some horizontal and vertical space for the text to be centered in, without the use of padding.

Basic calc-size animations

This example demonstrates how to use calc-size() to animate between a specific size and an intrinsic size. The demo features a character badge/"name tag", which can be hovered or focused to reveal information about the character. The reveal is handled by a height transition between a set length and max-content.

HTML

The HTML contains a single <section> element with tabindex="0" set on it so it can receive keyboard focus. The <section> contains <header> and <main> elements, each with their own child content.

html
<section tabindex="0">
  <header>
    <h2>Chris Mills</h2>
  </header>
  <main>
    <p>Chris is the silent phantom of MDN.</p>
    <ul>
      <li><strong>Height</strong>: 3.03m</li>
      <li><strong>Weight</strong>: 160kg</li>
      <li><strong>Tech Fu</strong>: 7</li>
      <li><strong>Bad Jokes</strong>: 9</li>
    </ul>
  </main>
</section>

CSS

In the CSS, we set the <section>'s height to 2.5rem and overflow to hidden so only the <header> is shown by default, then specify a transition that animates the <section> height over 1 second during state changes. Finally, we set the <section> height to a calc-size() function call on :hover and :focus. The function return value is the equivalent of max-content + 2rem.

css
section {
  height: 2.5rem;
  overflow: hidden;
  transition: height ease 1s;
}

section:hover,
section:focus {
  height: calc-size(max-content, size + 2rem);
}

The rest of the CSS has been hidden for brevity.

Result

Try hovering over the <section> or focusing it via the keyboard — it will animate to its full height + 2rem, revealing all the content with 2rems of extra space at the bottom.

Adjusting reading width based on fit-content

This example shows a container with text inside it, and a button that can be clicked to make the container width narrower or wider depending on reading preference.

HTML

The HTML contains a single <section> element containing child text content, plus a <button> to change the <section> width.

html
<section class="easy-reader">
  <h2>Easy reader</h2>

  <p>
    Eius velit aperiam ipsa. Deleniti eum excepturi ut magni maxime maxime
    beatae. Dicta aperiam est laudantium ut illum facere qui officiis. Sunt
    deleniti quam id. Quis sunt voluptatem praesentium minima dolorum autem
    consequatur velit.
  </p>

  <p>
    Vitae ab incidunt velit aspernatur deleniti distinctio rerum. Et natus sed
    et quos mollitia quia quod. Quae officia ex ea. Ducimus ut voluptatem et et
    debitis. Quidem provident laboriosam exercitationem similique deleniti.
    Temporibus vel veniam mollitia magni unde a nostrum.
  </p>

  <button class="width-adjust">Narrower</button>
</section>

CSS

In the CSS, we set the <section>'s width to a default of fit-content. We then define two sets of @keyframes, narrower, which animates from fit-content to 70% of fit-content (calculated using calc-size()), and wider, which animates the same values but in the opposite direction. Finally, we attach those animations to two classes — .narrower and .wider. Each animation is defined to last one second and to keep the final state applied once finished.

css
section {
  width: fit-content;
}

@keyframes narrower {
  from {
    width: fit-content;
  }

  to {
    width: calc-size(fit-content, size * 0.7);
  }
}

@keyframes wider {
  from {
    width: calc-size(fit-content, size * 0.7);
  }

  to {
    width: fit-content;
  }
}

.narrower {
  animation: narrower 1s ease forwards;
}

.wider {
  animation: wider 1s ease forwards;
}

The rest of the CSS has been hidden for brevity.

JavaScript

The JavaScript provides a narrower/wider toggle that applies the relevant class to the <section> when the button is clicked:

js
const widthAdjustBtn = document.querySelector(".width-adjust");
const easyReader = document.querySelector(".easy-reader");

widthAdjustBtn.addEventListener("click", () => {
  if (easyReader.classList.length === 1) {
    easyReader.classList.add("narrower");
    widthAdjustBtn.textContent = "Wider";
  } else if (easyReader.classList.contains("wider")) {
    easyReader.classList.replace("wider", "narrower");
    widthAdjustBtn.textContent = "Wider";
  } else if (easyReader.classList.contains("narrower")) {
    easyReader.classList.replace("narrower", "wider");
    widthAdjustBtn.textContent = "Narrower";
  }
});

Result

Try clicking the <button> a few times to adjust the <section> between the wide and narrow reading width, achieved by manipulating the width based on the fit-content value.

Using a function inside the calc-size() function

As mentioned earlier, it is possible to use another function inside calc-size(). This example sets field-sizing: content on <input> elements to make them as wide as the entered content, and then uses a max() function inside calc-size() to ensure that the <input>s are at least a minimum size, and only start to grow when the entered text becomes wider than that size — by being set to fit-content plus 20px.

HTML

The HTML contains a <form> element containing three textual <input> types. Each <input> has a <label> associated with it to make the form accessible, and a maxlength applied to stop entered values getting long enough to break the form layout.

html
<form>
  <div>
    <label for="name">Name:</label>
    <input type="text" id="name" name="name" maxlength="48" />
  </div>
  <div>
    <label for="email">Email:</label>
    <input type="email" id="email" name="email" maxlength="48" />
  </div>
  <div>
    <label for="address">Address:</label>
    <input type="text" id="address" name="address" maxlength="60" />
  </div>
</form>

CSS

In the CSS, we set the width of the <label> elements to 100px. We set field-sizing: content on the <input> elements to make them as wide as the entered content — by default they would no width because nothing would be entered into them. To counteract this, we set their width values to calc-size(fit-content, max(100px, size + 20px)). This means that they are a minimum of 100px wide, even with no value entered. When an entered value becomes wider than 100px, their width changes to fit-content plus 20px, which means they start to grow with the content size but keep a 20px gap on the right-hand side.

css
label {
  width: 100px;
}

input {
  field-sizing: content;
  width: calc-size(fit-content, max(100px, size + 20px));
}

The rest of the CSS has been hidden for brevity.

Result

Try entering some text inside the form inputs, and see how they grow when the values start to become as wide as the minimum width enforced by the max() function.

Specifications

Specification
CSS Values and Units Module Level 5
# calc-size

Browser compatibility

BCD tables only load in the browser

See also