Skip to content

Radius

Seven steps and one nesting rule. Corner radius is a shape language: it tells the user what a thing is before they read it.

Live preview

Everything below is the real component. Change the controls, tab through it, and turn on Inspector Mode to read any value off the screen.

The scale
--radius-xs4pxCheckbox, tag, code chip, focus ring on inline text.
--radius-sm6pxMenu item, extra-small button, nested item inside an 8px parent.
--radius-md8pxButtons, inputs, selects. The interactive default.
--radius-lg12pxPopovers, alerts, large buttons, nested cards.
--radius-xl16pxCards, panels, preview surfaces. The container default.
--radius-2xl20pxDialogs, command palette, hero surfaces.
--radius-3xl28pxBottom sheets and anything anchored to a screen edge.
full∞Pills, avatars, badges, switches — shapes that are never pressed like a button.

The nesting rule

Inner radius equals outer radius minus the padding between them. Get it wrong and the corners pinch — visible even to people who cannot name what is bothering them.

Correct — inner = outer − padding16px card, 8px padding, 8px inner
Wrong — inner matches outer16px inside 16px with 8px padding

Shape as category

Three radii, three meanings. The user never reads a legend — they infer the rule from consistent exposure.

8px
Pressablebutton, input
16px
Containercard, panel
9999px
Labelbadge, chip

Edge anchoring

A surface attached to a viewport edge rounds only the corners that are not touching it. Rounding all four makes it look detached and floating.

Bottom sheet — top corners only
All four corners

Interaction states

Every state a user can put this component into, rendered side by side. If a state is missing here, it is missing in production too.

4pxxs · checkbox
6pxsm · menu item
8pxmd · button
12pxlg · popover
16pxxl · card
20px2xl · dialog
28px3xl · sheet
fullpill · badge

Anatomy

Every part, every measurement, and the reason it is that number.

4px

Four levels of nesting: 20 → 16 → 8 → 4, with 16px, 12px and 8px of padding between them. Every inner radius is its parent minus the padding.

  1. Outermost20px · --radius-2xl

    Dialog level. The largest radius that still reads as a rectangle rather than a lozenge at typical dialog widths.

  2. First child16px = 20 − 4

    With 4px of padding the curves stay parallel. Any larger and the child’s corner starts crowding the parent’s.

  3. Second child8px = 16 − 8

    Padding here is 12px, so strictly the child could be 4px. 8px is within tolerance — the rule is a floor, not a formula to two decimal places.

  4. Innermost4px · --radius-xs

    The floor of the scale. Below 4px a corner reads as square, so there is no reason to have a 2px step.

  5. Border alignment1px inside the radius

    Borders are drawn inside the radius, so a bordered element’s visible corner is 1px tighter than its declared radius. Ignore it below 12px; compensate above.

Design tokens used

Values are read live from the running stylesheet, so this table can never drift from the code. Click any value to copy it.

Radius

TokenValueUsed for
--radius-xsCheckboxes, code chips, inline focus rings
--radius-smMenu items, xs buttons, nested rows
--radius-mdButtons, inputs, selects
--radius-lgPopovers, alerts, lg buttons
--radius-xlCards and panels
--radius-2xlDialogs and the command palette
--radius-3xlBottom sheets
rounded-fullBadges, chips, avatars, switches

Recommended sizes

Pick a size from this table. Do not invent a new one — a fourth height is how a design system starts dying.

SizeRadiusWhen to use
Extra small4pxElements under 20px tall: checkboxes, tags, keyboard hints.
Small6pxElements 24–28px tall, and anything nested inside an 8px parent.
Medium8pxElements 32–40px tall. Every standard control.
Large12pxElements 44px+ tall, and floating surfaces under ~320px wide.
Extra large16pxContent containers of any size.
2XL20pxModal surfaces 400px+ wide.
3XL28pxEdge-anchored sheets. Applied to two corners only.
Full9999pxAny element whose height defines its shape: pills, dots, avatars.

Do

16 → 8 → 4
Shrink the radius as you nestConcentric curves need to stay parallel. Subtracting the padding from the parent radius is the formula, and it works at every scale.
Round only the corners that are not touching an edgeA radius against the viewport edge produces a sliver of background behind it, which reads as a bug rather than a design choice.
overflow-hidden on the parent
Use overflow-hidden when content meets a rounded edgeAn image or a table inside a rounded card will paint over the corner unless the container clips it. This is the most common radius bug in production.
8px8px8px
Keep one radius per category across the whole productEvery button at 8px and every card at 16px is what makes the shape language legible. Two buttons with different radii on one screen destroys the signal completely.

Don't

Do not match inner and outer radiusThe two curves converge at the corner and the padding visually collapses to nothing. It is the tell-tale sign of a layout assembled without a shape rule.
Save changes
Do not fully round a buttonPill shapes belong to badges and chips. A fully rounded button reads as a tag, and users hesitate before clicking things they have categorised as labels.
h32 · r20
Do not use a radius larger than half the heightPast 50% of the shorter dimension the corners merge and you get an accidental pill. A 32px-tall element cannot have a 20px radius and still look rectangular.
Do not mix radii within one componentA card with a 16px top and an 8px bottom looks like two components glued together. Asymmetric radius is reserved for edge anchoring, where it carries meaning.

Accessibility

Not a checklist to run at the end. These are the requirements the component was built from.

1.4.11Non-text ContrastAA

Contrast

  • Radius does not affect contrast, but it does affect how much of a border is visible. At 28px, roughly 12% of the perimeter is curve — a low-contrast border becomes harder to trace.
  • A focus ring follows the element’s radius. On a fully rounded element the ring is a circle, which needs 3:1 against both the element and the page just like any other.

Keyboard

TabFocus rings inherit border-radius automatically via outline, so no per-shape work is needed.

Screen readers

  • Radius is purely visual and is never announced. It must never be the only way a category is communicated.

Focus & touch

  • outline follows border-radius in every modern browser, which is one more reason to use outline for focus rather than a box-shadow ring.
  • A large radius reduces the effective corner hit area slightly, because the corners are outside the shape. It is negligible below 20px; on a 28px sheet handle, keep interactive targets away from the corners.
AttributeApplied toNotes
forced-colors: activeAll surfacesHigh Contrast Mode keeps the radius but replaces colours. Shape becomes the primary categorical signal, which is an argument for keeping the language consistent.

Code

Example usage

tsx
1// Always reference the token, never a literal2<Card className="rounded-[var(--radius-xl)]" />3<Button className="rounded-[var(--radius-md)]" />4<Badge className="rounded-full" />56// Nesting: derive the inner radius from the outer minus padding7const OUTER = 168const PAD = 89<div style={{ borderRadius: OUTER, padding: PAD }}>10  <div style={{ borderRadius: OUTER - PAD }} />11</div>1213// Or in CSS, so it survives a token change14<div className="rounded-[var(--radius-xl)] p-2">15  <div className="rounded-[calc(var(--radius-xl)-8px)]" />16</div>1718// Edge-anchored surfaces round two corners only19<BottomSheet className="rounded-t-[var(--radius-3xl)]" />2021// Clip children that reach the edge22<div className="overflow-hidden rounded-[var(--radius-xl)]">23  <img src={cover} alt="" />24</div>

CSS

css
:root {
  --radius-xs: 4px;
  --radius-sm: 6px;
  --radius-md: 8px;
  --radius-lg: 12px;
  --radius-xl: 16px;
  --radius-2xl: 20px;
  --radius-3xl: 28px;
}

/* Nesting expressed in CSS so it tracks the token */
.card {
  border-radius: var(--radius-xl);
  padding: 8px;
}
.card > .card__media {
  border-radius: calc(var(--radius-xl) - 8px);
}

/* Clip anything that reaches a rounded edge */
.card--media { overflow: hidden; }

/* Edge-anchored: only the free corners */
.sheet {
  border-start-start-radius: var(--radius-3xl);
  border-start-end-radius: var(--radius-3xl);
}

/* Logical properties, so RTL mirrors correctly without a second rule */
.notice {
  border-start-start-radius: var(--radius-md);
  border-end-start-radius: var(--radius-md);
}

Notes

Professional tips

  • When in doubt between two steps, take the smaller one. Under-rounding reads as restrained; over-rounding reads as a toy.
  • A border is drawn inside the radius, so a bordered element looks marginally tighter than an unbordered one at the same value. Above 16px, bump the bordered version by 1px if they sit side by side.
  • Radius and elevation should move together. A surface that floats higher usually wants a slightly larger radius — compare our 12px popover with our 20px dialog.
  • For images inside cards, prefer overflow-hidden on the card to setting a radius on the image. One rule instead of one per child, and it survives content changes.

Performance

  • border-radius on a scrolling container forces the browser to clip on every frame. On long lists, round the wrapper rather than the scroll area itself.
  • A rounded element with a shadow and a backdrop-filter is three separate compositing steps. That combination is fine on a dialog and expensive on 200 table rows.
  • Avoid animating border-radius; it triggers a repaint of the whole element. Cross-fade two elements or animate a transform instead.

Common mistakes

  • Forgetting overflow-hidden on a card with a full-bleed image. The image squares off the corners and nobody notices until a screenshot goes into a deck.
  • Applying a radius to a full-width mobile banner. On a 360px screen the curve is visible against the edge and looks like a rendering artefact.
  • Setting a radius on a <table>. The cells ignore it; you must round the wrapper and clip.
  • Using rounded-full on a variable-height element. The moment the content wraps to two lines the radius follows the height and the shape changes.

Real-world recommendations

  • Pick the container radius first — it is the most visible surface in the product — then derive the rest of the scale from it. 16px containers imply 8px controls.
  • If a designer hands over a Figma file with corner radii of 10, 14 and 18, snap them to 8, 12 and 16 before building. The difference is invisible; the consistency is not.
  • On marketing pages you can afford one step larger everywhere. Marketing surfaces are bigger and softer shapes read as friendlier; product surfaces are dense and need tighter geometry.
  • Audit for radius drift the same way you audit colour: grep for rounded-[ and border-radius: and check every literal against the scale.