Skip to main content

CSS Border-Radius Explained: How Circles, Pills, and Asymmetric Corners Actually Work

Understand the math behind CSS border-radius circles, pill buttons, and the slash syntax for asymmetric elliptic corners — with copy-ready examples and a live generator.

Published
#css #border-radius #circles #pills #asymmetric #ui

CSS Border-Radius Explained: How Circles, Pills, and Asymmetric Corners Actually Work

Most developers type border-radius: 8px and move on. That works for cards. But the moment a designer asks for a perfect circle avatar, a pill-shaped tag, or a card with one squared corner and three rounded ones, the one-number mental model breaks down. This guide explains exactly what the property is computing at each step — not as a list of tricks, but as geometry you can reason about.

Why 50% Makes a Circle (and 100% Does Not)

The most reliable way to make a circle in CSS is:

.avatar {
  width: 80px;
  height: 80px;
  border-radius: 50%;
}

The reason 50% is the magic number, not 100%, comes from how the browser resolves percentages. The border-radius percentage is calculated against the element's own dimensions: 50% of an 80 px element is 40 px per corner. When all four corners each consume 40 px of a 80 px side, they meet exactly at the midpoints — producing a smooth ellipse that, on a square element, is a perfect circle.

If you write border-radius: 100%, the spec's "overlap" rule kicks in: adjacent corner radii that sum to more than the side length are scaled down proportionally. So 100% 100% 100% 100% gets scaled to 50% 50% 50% 50% automatically. You end up at the same circle — but only by accident. Browsers have implemented this rule consistently since 2012, according to the CSS Backgrounds and Borders Level 3 spec, so in practice both values produce identical output today. The semantic intent, though, is 50%: you want each corner to reach the midpoint, not exceed it.

For non-square elements, 50% produces an ellipse, not a circle. A 160 × 80 px element with border-radius: 50% gives you an oval with a 80 px horizontal radius and 40 px vertical radius. If you need a circle on a non-square container, set border-radius: calc(min(100%, ...)) or, more practically, constrain the element to equal width and height.

Pill Shapes: Two Approaches and When to Use Each

A pill (or capsule) shape is the rounded rectangle where the corners curve all the way into a semicircle on the short ends. There are two common approaches:

Approach 1 — large fixed value:

.pill {
  border-radius: 9999px;
}

This works because the browser's scaling rule caps each corner at half the shortest side. A 120 × 36 px button gets a 18 px radius on every corner regardless of the 9999px you typed. Designers often call this a "lazy pill" — it's robust because it survives dynamic content resizing without ever looking wrong.

Approach 2 — explicit 50% on the short axis:

.pill {
  border-radius: 50% / 50%;
}

This uses the full slash syntax (explained in the next section) to say: horizontal radius is 50% of width, vertical radius is 50% of height. On a short element, the vertical 50% caps at the element's half-height — which is exactly the semicircle you want. On a taller element this would start to produce egg-like shapes, so this form is mainly useful when you're certain of the aspect ratio.

I tested both approaches on a resizable component where text content could expand from one word to a full sentence. The 9999px version handled it perfectly — the semicircle at each end stayed consistent. The percentage version ballooned into an oval as the button grew. For real UI, 9999px is the correct choice.

The Slash Syntax: Asymmetric and Elliptic Corners

The shorthand most people write — border-radius: 12px — is a condensed form. The full syntax:

border-radius: H1 H2 H3 H4 / V1 V2 V3 V4
               └─ horizontal radii ──┘ └─ vertical radii ──┘

Before the slash: the horizontal radius of each corner (top-left, top-right, bottom-right, bottom-left). After the slash: the vertical radius for those same corners. When both sides are equal, each corner is a quarter-circle. When they differ, each corner becomes a quarter-ellipse.

A real example: a chat bubble where the bottom-right corner is squared off to show message origin.

.bubble {
  border-radius: 16px 16px 4px 16px;
}

Input: border-radius: 16px 16px 4px 16px Output: three fully rounded corners, one nearly-squared bottom-right — the classic chat message shape.

Now add elliptic shaping to make the bubble feel organic:

.bubble-organic {
  border-radius: 20px 20px 8px 20px / 16px 16px 6px 16px;
}

The top corners have a 20 px horizontal radius but only a 16 px vertical radius — a slightly squashed curve. The bottom-right stays tight in both directions. This is closer to what you see in messenger apps than a pure circle-corner approach, because real UI isn't built from perfect circular arcs.

Four Common Asymmetric Patterns

Receipt / coupon notch — rounded top, squared bottom:

border-radius: 16px 16px 0 0;

Dialog with a pointing tab — three standard corners plus one accent:

border-radius: 8px 8px 8px 0;

Leaf / teardrop — one circular corner, the rest sharp:

width: 80px; height: 80px;
border-radius: 0 80px 0 0;

Input: a div 80 × 80 px, border-radius: 0 80px 0 0 Output: a quarter-circle arc in the top-right corner, three sharp corners — a pointed leaf or teardrop silhouette depending on rotation.

Egg / avocado — wide top, narrower bottom via the slash syntax:

border-radius: 50% 50% 40% 40% / 60% 60% 40% 40%;

The horizontal radii stay symmetrical (50% / 50%), but the vertical radii differ: 60% on the top half, 40% on the bottom. That asymmetry pulls the widest point of the curve upward, producing the "fat on top, tapered below" egg shape. No SVG required.

Putting It Together: When to Use a Generator

Hand-calculating eight values while previewing in DevTools is doable for simple shapes, but for freeform blobs or asymmetric shapes with many unknowns it becomes slow. The CSS Border Radius Generator lets you drag each of the eight corner handles independently, see the live shape, and copy the exact CSS. I use it when a designer delivers a Figma blob shape and I need to reverse-engineer border-radius values rather than reaching for SVG.

For buttons specifically — where the pill shape interacts with padding, font size, and hover states — the CSS Button Generator combines border-radius controls with the full button styling pipeline, so you don't have to assemble the CSS from separate properties.

Browser Support and the One Gotcha

border-radius with the full slash syntax has had complete cross-browser support since 2012 (Chrome 5, Firefox 4, Safari 5). There is no modern browser where this is a concern.

The one genuine gotcha: border-radius clips the border, not the content. If you have a 2 px border on a rounded element, the border follows the curve — but overflow content (an image, an absolutely positioned child) is only clipped if you add overflow: hidden. This trips up rounded avatar frames constantly: the image corners stick out past the circle unless the parent also carries overflow: hidden.

.avatar-frame {
  border-radius: 50%;
  overflow: hidden;  /* without this, the image ignores the curve */
}

Understanding what the property actually calculates — quarter-ellipses defined by two independent radii per corner, capped by the element's own dimensions — makes every variant predictable. Once you see the geometry, 50% for circles, 9999px for pills, and the slash syntax for everything in between stops feeling like a lookup table and starts feeling like a consistent tool.


Made by Toolora · Updated 2026-06-26