calc-size() and round() stop your background pattern getting chopped at the bottom
Dex Halloran
Some ugliness only ever shows up on the last row of a tiled background. The pattern repeats perfectly, the content stops wherever the content stops, and the bottom row of tiles gets sliced in half like a sprite with a broken hitbox. Everyone has shipped it. Most of us shipped it and then just stopped looking at the bottom of that section.
Ahmad Shadeed ran into exactly this on a section whose pattern repeats on both axes. His write-up on ishadeed.com, published October 15, goes from the quick fix to a better one. The better one combines calc-size() with round(), and it's the kind of small, smart CSS move I want to see more of. The big catch goes up front: Shadeed says the solution "works only in Chrome at the time of writing this article."
The setup
The tile size lives in two custom properties, so everything else can refer to it:
.section {
--tile-width: 52px;
--tile-height: 44px;
background-image: url("pattern.png");
background-size: var(--tile-width) var(--tile-height);
}
The content sets the height, and the tile is 44px tall. Those two numbers almost never line up, so you get the cut-off.
Why background-repeat: round loses
The first reflex (Shadeed's too) is background-repeat: round. It does kill the cut-off. It does that by resizing the tiles until a whole number of them fits the box, so your 52 by 44 pattern stops being 52 by 44. Shadeed's objection is that the round keyword forces the pattern to shrink or grow, and the "Make it dance" button in his demo makes the wobble impossible to unsee. If the pattern is part of the brand, or lines up with anything else on the page, a tile that changes size with the length of the copy is a bug dressed up as a fix.
So flip it around. Leave the tile alone and fix the box.
Round the box, not the art
.section {
height: calc-size(auto, round(up, size, var(--tile-height)));
}
Read it from the inside out. calc-size() takes auto as its basis and lets you do math on it, with size standing in for that auto height. Then round(up, size, var(--tile-height)) snaps the result up to the next multiple of the tile height. If the content needs 170px and the tile is 44px, you get 176px. That's four whole rows and no slice.
It looks like a fixed height, and it isn't one. Shadeed points out that it behaves like min-height: the browser works out the auto height first and only then rounds it, so the section still grows with its content. The only input is the tile height, which is why the same line works across different patterns without any retuning.
Quick detour. Doing math on auto in plain CSS still gets me. For years the options were measuring in JavaScript or hard-coding a magic number and hoping the copy never changed. OK, back.
Up, down, and the padding problem
round() also takes down. According to Shadeed, rounding up makes the section taller in most cases. Rounding down never adds height you didn't need, and he says he actually prefers down for the height, while calling the choice "not a big deal."
Padding is where the choice starts to matter. He rounds the vertical padding to the tile as well:
.section {
height: calc-size(auto, round(up, size, var(--tile-height)));
padding-block: round(up, 1rem, var(--tile-height));
}
With up, the 1rem snaps to a full step, so the top and bottom padding each come out equal to the tile height. Round that padding down and the content ends up too close to the edge. Shadeed says that's where it starts to feel odd, so he switches to rounding up. I agree with him: up is the right default for the padding, and whether the height goes up or down is a matter of taste.
The full rule
Here's Shadeed's complete version:
.section {
/* This must match the size of the pattern image. */
--tile-width: 52px;
--tile-height: 44px;
background-image: url("pattern.svg");
background-size: var(--tile-width) var(--tile-height);
padding-block: round(up, 1rem, var(--tile-height));
height: calc-size(auto, round(up, size, var(--tile-height)));
}
That comment is the only maintenance burden here. Change the artwork and you update two custom properties. Nothing else changes.
Who actually sees it
The support widget in Shadeed's post marks calc-size() as Limited availability and not Baseline. It's supported in Chrome and Edge, and not in Firefox or Safari. So treat this as progressive enhancement. A browser that can't parse a value drops the whole declaration, which means it ignores that height line and the section falls back to plain auto height, cut-off and all. Nothing breaks. Those browsers just show what you were shipping yesterday.
Score: 8 out of 10. It fixes the box instead of bending the artwork, which is almost always the right instinct in CSS. It loses two points for running in only one engine family today.
Source: ishadeed.com (ishadeed.com)