---
title: "Shape & radius"
description: "24px radius, 24px inset. That pairing defines the system's shape. Every other radius derives from it."
canonical: "https://lopifi.com/docs/foundations/shape-and-radius"
---

# Shape & radius

> 24px radius, 24px inset. That pairing defines the system's shape. Every other radius derives from it.

Source: https://lopifi.com/docs/foundations/shape-and-radius
Part of: Lopifi — Planned: US GAAP books on autopilot, checked by a CPA (https://lopifi.com)

---

# Shape & radius

**24px radius, 24px inset.** That pairing defines the system's shape. Every other radius derives from it.

---

## The scale

Radii sit on a 4px grid because the nesting formula (below) needs them to be.

```
--radius-none: 0px      /* rule lines, full-bleed dividers, table cells */
--radius-xs:   4px      /* badges, tags, checkbox */
--radius-sm:   8px      /* inputs, small buttons, list-row hover */
--radius-md:   12px     /* medium controls, inner panels */
--radius-lg:   16px     /* nested cards, sheets on mobile */
--radius-xl:   24px     /* CARD DEFAULT · dialogs · panels */
--radius-2xl:  32px     /* hero surfaces, marketing */
--radius-full: 9999px   /* avatars, pills, presence dots */
```

`--radius-xl: 24px` is the base. Derive every other radius in a composition from it. Don't pick one.

<!-- plate:shape-radius-scale -->

---

## The nesting formula

> **`r_inner = r_outer − gap`**

`gap` is the distance between the outer border and the inner element's border. This is geometry, not taste: it's what keeps two rounded rectangles concentric. Get it wrong and the corners visibly drift apart, which looks sloppy even to people who can't say why.

<!-- plate:shape-nesting -->

### Worked examples

| Outer | Gap (inset) | Inner radius | Where |
|---|---|---|---|
| 24 | 24 | **0** | An element flush against the full card inset is **square**. That's intentional, and it's why full-bleed table rows and dividers in a card have no radius. |
| 24 | 8 | 16 | A nested panel inset 8px from the card edge |
| 24 | 12 | 12 | An inner card at 12px inset |
| 24 | 16 | 8 | An input inset 16px |
| 16 | 8 | 8 | A row inside a nested card |
| 32 | 8 | 24 | A card inside a hero surface |

### The consequence people miss
**24 outer + 24 inset = 0 inner.** An element that fills the whole content box of a 24px-radius card has square corners. So:

- Table rows in a card are square. Their hover background is square too, and `overflow-hidden` on the card clips it to the *card's* corner.
- A full-bleed chart at the bottom of a card inherits the card's bottom corners via `overflow-hidden`, not its own radius.
- A list-row hover background that is inset 8px from the card edge gets `radius-lg` (16), not `radius-sm`.

<!-- plate:shape-flush -->

### When you can't satisfy the formula
If the inner radius comes out negative, make the inner element square (`0`) and rethink the composition. Usually the inner element should be full-bleed, not inset.

---

## Squircle vs. circular corners

At 24px you can see the difference between a circular arc and a superellipse (`squircle`). Apple uses continuous curvature. CSS `border-radius` doesn't.

**Decision: circular arcs.** We ship standard `border-radius`.

Squircles need an SVG mask, `corner-shape: squircle` (Chrome 139+, not yet Baseline as of this writing), or a `paint()` worklet. Each one breaks something. The mask kills subpixel text antialiasing on the clipped edge, the worklet doesn't run in Safari, and `corner-shape` isn't available everywhere. A clean circular corner beats a broken squircle.

**Revisit when** `corner-shape: squircle` reaches Baseline Widely Available, and adopt it *progressively*:
```css
.card { border-radius: 24px; }
@supports (corner-shape: squircle) {
  .card { corner-shape: squircle; }
}
```
No layout changes needed. Add it to [`roadmap.md`](https://lopifi.com/md/docs/roadmap) as a deferred item.

---

## Radius by component class

| Class | Radius | Note |
|---|---|---|
| Card, panel, dialog | 24 | The default |
| Sheet / drawer (mobile) | 16 top corners only | Bottom corners are off-screen, so rounding them does nothing |
| Popover, dropdown, menu | 12 | Smaller surface, smaller radius. 24px on a 160px-wide menu eats the corners |
| Tooltip | 8 | |
| Input, select, textarea | 8 | |
| Button (md, 40px) | 8 | |
| Button (lg, 48px) | 12 | Scales with height |
| Button (sm/xs) | 8 / 4 | |
| Pill / badge / status | full | |
| Avatar | full | Except **bank and merchant logos**, which are `8` — see [imagery](https://lopifi.com/md/docs/foundations/imagery-and-isometric#logo-treatment) |
| Payment card visual | 5.9% of width | ISO 7810 physical spec, ~16px at 272px wide |
| Table row hover | 0 (full-bleed) or 8 (inset) | Per the nesting formula |
| Chart bar | 4 top corners | Only when bar width > 12px; below that, square |
| Skeleton | matches the element it replaces | A rounded skeleton for a square element is a tell |

---

## Corner logic

Rules for the most common shape mistakes.

1. **Corners that touch a viewport edge are square.** For a sheet at the bottom of the screen, a sidebar flush left or a full-width mobile card, round only the visible corners. Rounding an off-screen corner wastes 24px of content width.

2. **Adjacent segmented controls share a radius envelope.** In a button group, the first child rounds left, the last rounds right, and the middle ones are square. Never round every child.

3. **Clip, don't double-round.** When content has to follow a card's corner (an image, a chart, a colored bar), put `overflow: hidden` on the card and give the child `radius: 0`. Two elements rounded separately at the same corner never line up.

4. **`overflow: hidden` has a cost.** It creates a new clipping context, so a shadow, focus ring or tooltip inside the card gets cut off. If you need clipping *and* a child that escapes, clip an inner wrapper instead of the card.

5. **Radius doesn't scale with density.** A `compact` table still sits in a 24px card. Density changes spacing and type, not shape. Otherwise the product would look like a different product when a user flips a setting.

6. **Focus rings sit outside the radius.** `outline: 2px solid var(--focus); outline-offset: 2px;` follows `border-radius` automatically and never gets clipped. Never build a focus ring with `box-shadow` on a clipped element.

---

## Borders and shape

Radius and border are one system. A 1px border on a 24px radius is drawn *on* the shape, not inside it. In practice:

- Both `border` and `box-shadow: 0 0 0 1px` follow radius, but the shadow stacks differently with other shadows. Use `border` for the resting state and a `box-shadow` ring for transient and focus states.
- On a nested element, `border` adds 1px to the outside, and the nesting formula uses the *border box*. For two bordered 24px cards with an 8px gap, the inner radius is still `24 − 8 = 16`.

Elevation is covered in [elevation](https://lopifi.com/md/docs/foundations/elevation-borders-shadows).
