Same number, wrong corner

Put a rounded image inside a rounded card, give both the same radius, and something feels off before you can say why. The sides look fine. The corners look puffy, as if the padding got thicker right where it curves.

It isn't your eyes. The gap really is thicker there. Watch the two measurements below, or grab the slider and try it yourself.

copy-pasted

sides 12px·corner 17px

inner radius24px
Same radius, thicker corners.

At the same 24px, the sides keep their 12px but the corner gap stretches to about 17. Those five extra pixels are the whole "something's off" feeling.

One center

Every rounded corner is a slice of a circle. A 24px radius is a quarter of a 24px circle tucked into the corner, with its center 24px in from both edges.

Two corners look parallel when their circles share a center, the way lanes on a running track do. Then the distance between the curves stays the same all the way round, and the gap reads as one even line.

two centers
inner radius24px
Concentric means one shared center.

That's the same corner, zoomed in. The white circle belongs to the card, the blue one to the photo inside it. When their centers meet, the two corners are concentricSharing one center. Concentric curves stay the same distance apart all the way round..

The formula

If both circles share a center and the image starts 12px further in, its circle has to be 12px smaller. That's the whole rule.

24outer12gap12inner

The gap is the distance between the two edges, usually the parent's padding. Change both sides of it below. There's no slider for the inner radius this time; it's worked out for you.

concentric
20outer8gap12inner
outer radius20px
gap8px
The gap decides the inner radius.

Push the gap past the radius and the answer goes negative. There's no such thing as a negative corner, so the inner one goes square, which is right: at that distance there's no curve left to follow. Browsers already treat a negative calc() radius as zero1, but writing max() makes it look deliberate to whoever reads the CSS next.

CSS
.card {
  --radius: 20px;
  --gap: 8px;
  border-radius: var(--radius);
  padding: var(--gap);
}

.card > .media {
  border-radius: max(0px, var(--radius) - var(--gap));
}

Because it's written as a relationship and not a number, it holds at any depth. Below, the gap never stops changing, and every corner keeps up on its own.

concentric at every level
Every level takes the same gap off its radius.

Borders count

The gap is everything between the two curves, not only the padding. A border sits in that space too.

off by 4px

20 − 8 = 12px

border4px
The border is part of the gap.

With a 20px card, a 4px border and 8px of padding, the inner radius is 8, not 12. Subtract only the padding and the centers drift apart by exactly the border's width. On a 1px border it's easy to miss, which is why so many interfaces get it wrong.

CSS
.card > .media {
  border-radius: max(
    0px,
    var(--radius) - var(--border) - var(--gap)
  );
}

Working backwards

Sometimes the inner piece is the one that can't change. Menus are the classic case: every item has a hover background with its own radius, and the menu wraps around them. So flip the formula2.

8item4padding12menu
concentric
menu padding4px
Start from the piece that can't change.

A fixed 12px menu is right at exactly one padding. Change the padding and it's wrong again. Derive it and it follows along. Only the first and last items ever reach the menu's corners, so hover those; they're the ones people notice.

CSS
.menu {
  --item-radius: 8px;
  --pad: 4px;
  padding: var(--pad);
  border-radius: calc(var(--item-radius) + var(--pad));
}

.menu-item {
  border-radius: var(--item-radius);
}

Where the math stops

The formula gets you to concentric. It doesn't get you to finished.

  • Uneven padding. With 8px on the sides and 16px on top, the two curves can't stay parallel all the way round. Pick what looks right.
  • Pieces that don't reach the corner. A centered icon inside a card doesn't share a corner with anything, so there's nothing to match.
  • Small radii. At a few pixels, the exact answer can look a little sharp. A pixel or two rounder often reads better, even when the math disagrees.
  • Squircles. The new corner-shape property3 draws smoother curves than circles, and offsetting those isn't simple subtraction. Treat the formula as a starting point there.

Keep in mind

  1. Inner radius = outer radius − gap. The gap is padding plus border.
  2. When the gap is bigger than the radius, the inner corner is square. Write max(0px, …) so it's on purpose.
  3. When the inner piece is fixed, go the other way: outer = inner + gap.
  4. Keep the relationship in CSS variables, so changing one number keeps every corner right.
  5. Let the math get you close, then trust your eyes.

Resources

  1. A calc() result outside a property's range is clamped to it. CSS Values and Units, range checking. Read more
  2. Gustavo Fior walks through both directions in Nested border radius. Read more
  3. MDN, corner-shape. Read more