CSS border-radius: rounded corners, circles, pills and blobs

One CSS property rounds every corner of a box. This guide covers the values, the slash form for oval corners, and why a child sometimes pokes out of the curve.

To round corners in CSS, set border-radius on the element: border-radius: 12px; rounds all four corners by 12 pixels. There is no HTML tag or attribute for it. Without a stylesheet, use the style attribute: <div style="border-radius: 12px">.

The background, the border and the box shadow all follow the curve. Try it below: move a slider, then tick the checkbox for oval corners.

Live exampletry it here, then copy the code
Share it as a link
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>border-radius generator</title>
<style>
  body {
    margin: 0; padding: 16px;
    font-family: system-ui, sans-serif; background: #f4f5f7; color: #1d2330;
  }
  .stage { display: grid; place-items: center; height: 170px; }
  #box {
    width: 240px; height: 140px; max-width: 100%;
    background: linear-gradient(135deg, #6366f1, #06b6d4);
    border: 3px solid #1e3a8a;
    box-sizing: border-box;
  }
  .sliders { display: grid; grid-template-columns: 1fr 1fr; gap: 6px 16px; margin-top: 10px; }
  label { display: grid; font-size: 13px; gap: 2px; }
  input[type=range] { width: 100%; }
  .v { display: none; }            /* vertical sliders: shown in ellipse mode */
  .ellipse .v { display: grid; }
  .toggle { display: flex; align-items: center; gap: 6px; margin-top: 10px; font-size: 14px; }
  pre {
    margin: 12px 0 0; padding: 10px 12px; border-radius: 8px;
    background: #1d2330; color: #e5f0ff; font-size: 14px; white-space: pre-wrap;
  }
</style>
</head>
<body>
<div class="stage"><div id="box"></div></div>

<label class="toggle"><input type="checkbox" id="ellipse"> Elliptical corners (slash syntax)</label>

<div class="sliders" id="sliders">
  <label>Top-left <input type="range" min="0" max="120" value="24" data-i="0"></label>
  <label>Top-right <input type="range" min="0" max="120" value="24" data-i="1"></label>
  <label>Bottom-left <input type="range" min="0" max="120" value="24" data-i="3"></label>
  <label>Bottom-right <input type="range" min="0" max="120" value="24" data-i="2"></label>
  <label class="v">Top-left, vertical <input type="range" min="0" max="120" value="12" data-i="4"></label>
  <label class="v">Top-right, vertical <input type="range" min="0" max="120" value="12" data-i="5"></label>
  <label class="v">Bottom-left, vertical <input type="range" min="0" max="120" value="12" data-i="7"></label>
  <label class="v">Bottom-right, vertical <input type="range" min="0" max="120" value="12" data-i="6"></label>
</div>

<pre id="out"></pre>

<script>
  const box = document.getElementById('box');
  const out = document.getElementById('out');
  const ellipse = document.getElementById('ellipse');
  const sliders = document.getElementById('sliders');

  // Shortest form of four values: top-left, top-right, bottom-right, bottom-left
  function short(v) {
    const [a, b, c, d] = v.map(n => n + 'px');
    if (a === b && b === c && c === d) return a;
    if (a === c && b === d) return a + ' ' + b;
    if (b === d) return a + ' ' + b + ' ' + c;
    return a + ' ' + b + ' ' + c + ' ' + d;
  }

  function update() {
    const r = [];
    sliders.querySelectorAll('input').forEach(s => { r[s.dataset.i] = +s.value; });
    let css = short(r.slice(0, 4));
    if (ellipse.checked) css += ' / ' + short(r.slice(4, 8));  // horizontal / vertical
    box.style.borderRadius = css;
    out.textContent = 'border-radius: ' + css + ';';
  }

  sliders.addEventListener('input', update);
  ellipse.addEventListener('change', () => {
    sliders.classList.toggle('ellipse', ellipse.checked);
    update();
  });
  update();
</script>
</body>
</html>
Four corner sliders and a slash toggle. The CSS line underneath is the shortest form of what you see.

The general border property, including width, style and colour, is covered in the CSS border guide. This page goes deeper into the corners.

One to four values

border-radius is a shorthand. Like margin, it takes one to four values, and they go clockwise from the top-left corner.

Left: four values, clockwise. Right: the slash sets how far the curve runs across and down.
Left: four values, clockwise. Right: the slash sets how far the curve runs across and down.
Value Top-left Top-right Bottom-right Bottom-left
12px 12px 12px 12px 12px
12px 4px 12px 4px 12px 4px
12px 4px 20px 12px 4px 20px 4px
12px 4px 20px 0 12px 4px 20px 0

With two values, the first covers the top-left and bottom-right, the second the other two. With three, the middle value covers both the top-right and the bottom-left.

To change one corner only, use the longhand property. It is clearer than a four-value line when you only need one sharp corner:

.bubble { border-radius: 18px; border-bottom-right-radius: 4px; }

The four longhands are border-top-left-radius, border-top-right-radius, border-bottom-right-radius and border-bottom-left-radius.

Oval corners with the slash

Each corner is really a quarter of an ellipse, with a horizontal radius and a vertical radius. A single value sets both to the same length, so the corner is a quarter circle.

A slash sets them separately. Values before the slash are the horizontal radii, values after it are the vertical radii:

.tab  { border-radius: 40px / 16px; }          /* wide, shallow corners */
.egg  { border-radius: 50% / 60% 60% 40% 40%; } /* taller at the top */

Each side of the slash follows the same one-to-four value rules. The longhand takes two values with a space and no slash: border-top-left-radius: 40px 16px.

Circles, ovals and pills

Percentages are measured from the element's own box. The horizontal radius is a percentage of the width, and the vertical radius is a percentage of the height.

The same 50% gives a circle on a square and an oval on a rectangle. A huge length gives a pill.
The same 50% gives a circle on a square and an oval on a rectangle. A huge length gives a pill.
  • Circle: equal width and height, then border-radius: 50%. A small aspect-ratio: 1 helps when only the width is set.
  • Oval: 50% on a rectangle. This is correct CSS, and a common surprise when a circle was the goal.
  • Pill: a very large length such as 9999px. When the radii are too big to fit, the browser scales them all down by the same factor. Every corner ends up at half the height, which leaves round ends.

Use a length, not a percentage, for pills. 50% on a wide button makes an oval, not round ends.

Children poke out of the corners

border-radius rounds the element it is set on. It does not clip what is inside. A header, image or coloured child with square corners still paints over the curve.

Live exampletry it here, then copy the code
Share it as a link
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Nested rounded corners</title>
<style>
  body {
    margin: 0; padding: 16px;
    font-family: system-ui, sans-serif; background: #f4f5f7; color: #1d2330;
  }
  h3 { margin: 0 0 8px; font-size: 15px; }
  .row { display: flex; flex-wrap: wrap; gap: 16px; }
  .part { flex: 1 1 250px; }
  .btns { display: flex; flex-wrap: wrap; gap: 6px; margin: 10px 0 6px; }
  button {
    font: inherit; font-size: 13px; padding: 6px 10px; cursor: pointer;
    border: 1px solid #c9cdd4; border-radius: 999px; background: #fff;
  }
  button[aria-pressed=true] { background: #1d2330; color: #fff; border-color: #1d2330; }
  code { font-size: 13px; background: #e7eaf0; padding: 1px 4px; border-radius: 4px; }
  p { font-size: 13px; margin: 4px 0 0; color: #4b5260; }

  /* Part 1: a card with an image inside it */
  .card {
    width: 220px; padding: 12px; border-radius: 24px;   /* outer radius 24px, padding 12px */
    background: #fff; box-shadow: 0 4px 14px rgba(0, 0, 0, .12);
  }
  .card .img {
    height: 110px; border-radius: 24px;
    background: linear-gradient(135deg, #f59e0b, #ef4444);
  }
  .card b { display: block; margin-top: 10px; font-size: 14px; }

  /* Part 2: a rounded box with a square header inside */
  .panel {
    width: 220px; border-radius: 24px;
    background: #fff; border: 2px solid #1d2330;
  }
  .panel .head { height: 56px; background: #2563eb; }   /* no radius of its own */
  .panel .text { padding: 12px; font-size: 14px; }
</style>
</head>
<body>
<div class="row">
  <div class="part">
    <h3>1. Inner radius</h3>
    <div class="card"><div class="img" id="img"></div><b>Card title</b></div>
    <div class="btns" id="inner">
      <button data-r="24px" aria-pressed="true">Same: 24px</button>
      <button data-r="12px">24 - 12 = 12px</button>
    </div>
    <p id="innerNote"></p>
  </div>

  <div class="part">
    <h3>2. Overflow</h3>
    <div class="panel" id="panel"><div class="head"></div><div class="text">The header has no radius.</div></div>
    <div class="btns" id="overflow">
      <button data-o="visible" aria-pressed="true">visible</button>
      <button data-o="hidden">hidden</button>
      <button data-o="clip">clip</button>
    </div>
    <p id="overNote"></p>
  </div>
</div>

<script>
  const img = document.getElementById('img');
  const panel = document.getElementById('panel');

  // Make one button in a group look pressed
  function press(group, btn) {
    group.querySelectorAll('button').forEach(b => b.setAttribute('aria-pressed', b === btn));
  }

  document.getElementById('inner').addEventListener('click', (e) => {
    const btn = e.target.closest('button');
    if (!btn) return;
    press(e.currentTarget, btn);
    img.style.borderRadius = btn.dataset.r;
    document.getElementById('innerNote').innerHTML =
      'Image: <code>border-radius: ' + btn.dataset.r + '</code>';
  });

  document.getElementById('overflow').addEventListener('click', (e) => {
    const btn = e.target.closest('button');
    if (!btn) return;
    press(e.currentTarget, btn);
    panel.style.overflow = btn.dataset.o;
    document.getElementById('overNote').innerHTML =
      'Panel: <code>overflow: ' + btn.dataset.o + '</code>';
  });

  // Show the starting state
  document.querySelector('#inner button').click();
  document.querySelector('#overflow button').click();
</script>
</body>
</html>
Left: the inner radius, same as the outer or reduced by the padding. Right: the panel's overflow setting decides whether the square header is clipped.

Add overflow: hidden or overflow: clip to the rounded element. Both clip the content to the inside edge of the curve.

The difference is scrolling. overflow: hidden still lets a script scroll the box. overflow: clip forbids all scrolling and does not make the element a scroll container, so position: sticky children keep working against the page.

Either one also clips anything meant to stick out, such as a dropdown or a tooltip inside the card. If that happens, round the child itself instead of clipping.

Nested corners: inner = outer - padding

A card with an image inset by padding looks off when both use the same radius. The gap at the corners is wider than along the sides, so the inner corners look too round.

Same radius inside looks wrong. Subtract the padding and the two curves share a centre.
Same radius inside looks wrong. Subtract the padding and the two curves share a centre.

A rule of thumb that works: inner radius = outer radius - padding. If the card has a visible border, subtract its width too:

.card     { border-radius: 24px; padding: 12px; }
.card img { border-radius: 12px; } /* 24 - 12 */

When the padding is as large as the outer radius, the inner radius reaches 0. Square inner corners are then correct.

Rounding images and videos

Put border-radius straight on the <img> or <video>. The picture is clipped to the curve, and no wrapper is needed.

img.avatar {
  width: 64px; height: 64px;
  object-fit: cover;    /* crop a non-square photo to the square */
  border-radius: 50%;
}

Round avatars need a square box first. A rectangular photo with 50% becomes an oval. object-fit crops the photo to fill the square without stretching it.

If the image sits inside a rounded card with no radius of its own, the card needs overflow: hidden, as in the example above.

Blob shapes with eight values

Use all four values on both sides of the slash, and the corners stop matching. The result is a soft, uneven shape, often called a blob:

.blob { border-radius: 30% 70% 70% 30% / 30% 30% 70% 70%; }

border-radius can be animated, so two blob shapes in a @keyframes rule make it morph slowly. The finished example below does this, and turns the animation off for readers who ask their system to reduce motion.

Live exampletry it here, then copy the code
Share it as a link
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>border-radius in a real page</title>
<style>
  body {
    margin: 0; padding: 16px;
    font-family: system-ui, sans-serif; background: #f4f5f7; color: #1d2330;
  }
  h3 { margin: 18px 0 8px; font-size: 14px; color: #5b6270; }
  h3:first-child { margin-top: 0; }

  /* Avatars: a square plus 50% makes a circle */
  .avatars { display: flex; gap: 10px; }
  .avatar {
    width: 48px; height: 48px; border-radius: 50%;
    display: grid; place-items: center;
    color: #fff; font-weight: 700;
  }

  /* Pill tags: a huge radius is scaled down to half the height */
  .tags { display: flex; flex-wrap: wrap; gap: 8px; }
  .tag {
    padding: 5px 12px; border-radius: 9999px;
    background: #e0e7ff; color: #3730a3; font-size: 13px;
  }

  /* Chat bubbles: round everywhere except the corner near the speaker */
  .chat { display: flex; flex-direction: column; gap: 8px; max-width: 360px; }
  .bubble { max-width: 75%; padding: 9px 13px; font-size: 14px; line-height: 1.4; }
  .them { align-self: flex-start; background: #fff; border-radius: 18px 18px 18px 4px; }
  .me   { align-self: flex-end; background: #2563eb; color: #fff; border-radius: 18px 18px 4px 18px; }

  /* Blob: eight values, animated */
  .blob {
    width: 150px; height: 150px;
    background: linear-gradient(135deg, #22c55e, #06b6d4);
    border-radius: 30% 70% 70% 30% / 30% 30% 70% 70%;
    animation: morph 6s ease-in-out infinite alternate;
  }
  @keyframes morph {
    to { border-radius: 60% 40% 30% 70% / 60% 30% 70% 40%; }
  }
  /* Respect the "reduce motion" system setting */
  @media (prefers-reduced-motion: reduce) {
    .blob { animation: none; }
  }
</style>
</head>
<body>
<h3>Avatars: border-radius: 50%</h3>
<div class="avatars">
  <div class="avatar" style="background:#6366f1">AK</div>
  <div class="avatar" style="background:#f59e0b">MJ</div>
  <div class="avatar" style="background:#10b981">SL</div>
</div>

<h3>Pill tags: border-radius: 9999px</h3>
<div class="tags">
  <span class="tag">Design</span><span class="tag">CSS</span><span class="tag">Beginner friendly</span>
</div>

<h3>Chat bubbles: one sharp corner</h3>
<div class="chat">
  <div class="bubble them">Did the rounded corners work?</div>
  <div class="bubble me">Yes. One sharp corner points at the speaker.</div>
</div>

<h3>Blob: eight values with a slash</h3>
<div class="blob" id="blob"></div>
</body>
</html>
Circle avatars, pill tags, chat bubbles with one sharp corner, and a blob that morphs unless reduced motion is on.
  • Avatars: a 48px square and border-radius: 50%.
  • Tags: border-radius: 9999px on inline padding.
  • Chat bubbles: 18px 18px 18px 4px for incoming messages and 18px 18px 4px 18px for outgoing ones. The sharp corner points at the speaker.
  • Blob: eight values, with @media (prefers-reduced-motion: reduce) { .blob { animation: none; } }.

For shapes that corners cannot make, such as triangles, stars or arrows, clip-path cuts the element to any polygon.

When it does not work

What you see Cause Fix
A child pokes out of the rounded corner border-radius does not clip content overflow: hidden or overflow: clip on the rounded element
An image stays square inside a rounded card The card is rounded, the image is not clipped overflow: hidden on the card, or a radius on the image
A table or its cells keep square borders border-collapse: collapse border-collapse: separate with border-spacing: 0
50% gives an oval, not a circle The box is not square Equal width and height, or aspect-ratio: 1
A wide button has oval ends 50% on a rectangle Use a length such as 9999px
The focus outline is a square around a round button Some browsers, mainly older versions, draw outline without the curve A ring with box-shadow: 0 0 0 3px, which always follows the radius
Nothing is rounded at all A typo, or a later rule sets border-radius: 0 Check the element's computed styles in the browser's developer tools

The table fix is covered step by step in border-collapse for HTML tables.

Rounded corners, shadows and a morphing blob are hard to judge from a screenshot, and an .html attachment may open as plain code on a phone.

To send the working page, paste it into a NOS document and choose Create share link. HTML to link walks through it.

The page renders as written and its scripts run, so the people you send it to can move the sliders themselves. If you change the code later, the same link shows the new version.

Questions people ask

Is there an HTML tag or attribute for rounded corners?

No. Rounded corners come from the CSS border-radius property. Without a stylesheet you can still write it inline, for example <div style="border-radius: 12px">.

How do I make a perfect circle with border-radius?

Give the element the same width and height, then set border-radius: 50%. On a box that is wider than it is tall, 50% gives an oval, because the horizontal radius comes from the width and the vertical radius from the height.

Why use 9999px instead of 50% for a pill?

When the radii are too big for the box, the browser scales them all down by the same factor until they fit. A huge length ends up as half the height on every corner, which gives round ends. 50% would give an oval on a wide button.

Why does the image inside my rounded card still have square corners?

border-radius rounds the element it is set on, not its children. Add overflow: hidden (or overflow: clip) to the card, or give the image its own border-radius.

Can I animate border-radius?

Yes. border-radius takes lengths and percentages, so it works with transition and @keyframes. The blob in the finished example morphs between two eight-value shapes. Wrap long animations in a prefers-reduced-motion check.

Keep reading