Field notes · 6 min read
A weekend above the tree line
Notes, routes and a packing list from three days on the ridge.
Nested rounded corners only look right when they share a center. Give a card a 24px radius and 12px of padding, and
the image inside needs a 12px radius, not 24px. tailwindcss-concentric does that math for you.
Add rounded-concentric to the child. It reads the radius, padding and border from the classes you already wrote
on the parent, subtracts in CSS, and keeps working through every breakpoint, state and nesting level.
rounded-[36px] p-3 borderTwo rounded shapes look concentric when their corners share a center point. That happens when the inner radius equals the outer radius minus the space between the two edges.
inner radius=outer radius−gap
The gap is the parent's padding and border width, plus a padded wrapper in between and the child's own margin. For an
absolutely positioned child it is the inset. rounded-concentric reads all of it from the classes you already
wrote, does the subtraction in CSS, and publishes its own radius so the next level down can do the same.
When the gap is bigger than the radius, the corner goes square instead of negative. A rounded-full parent stays
a pill all the way down.
The quickest way is to let your coding agent do it. Add the agent skill at the root of a project that uses Tailwind CSS v4.
npx skills add diip3sh/tailwindcss-concentricThen ask in plain words. The skill tells the agent what the plugin does, so it can install the package, import it, set up tailwind-merge, pick the right classes and run the audit.
Set up tailwindcss-concentric in this project.
Audit nested border radii in src/ and fix them.
Build a pricing card with an inset image and concentric corners.To set it up by hand, add the package.
npm i tailwindcss-concentricImport it after Tailwind. There is no config file and no JavaScript.
@import "tailwindcss";
@import "tailwindcss-concentric";Then add one class to any child of a rounded element.
<div class="rounded-3xl border p-3">
<img class="rounded-concentric" /> <!-- 24 − (12 + 1) = 11px -->
</div>To find the places in an existing codebase that need it, run the audit. It prints each one with a suggested class.
npx tailwindcss-concentric audit src
Every radius below comes from the plugin. The root sets rounded-(--lab-r) and its padding from CSS variables.
Each inner level only adds rounded-concentric.
The table reads the result back with getComputedStyle, so it shows what the browser rendered, next to the formula
it should match.
| Level | Computed | Formula |
|---|---|---|
| L1root | … | set on the root |
| L2concentric | … | 64 − 20 = 44differs |
| L3concentric | … | 44 − 20 = 24differs |
| L4concentric | … | 24 − 20 = 4differs |
The same settings as Tailwind classes:
<div class="rounded-[64px] p-5">
<div class="rounded-concentric p-5">
<div class="rounded-concentric p-5">
<div class="rounded-concentric p-5">…</div>
</div>
</div>
</div>
Each example keeps the classes you would already write on the parent. Only the child changes, from a copied radius to rounded-concentric.
Field notes · 6 min read
Notes, routes and a packing list from three days on the ridge.
Its 14 deployments and their logs go with it. This can't be undone.
Pro
$24/ month
For teams shipping every day.
The parent keeps its rounded-*, p-* and border-* classes. Change any of them, at any breakpoint,
and the child follows.
Every utility comes in the same shapes as Tailwind's own radius utilities.
The math is r = max(floor, R − min(gap x, gap y)). The gap is the parent's padding and border width, plus
the padding and border of a wrapper in between and the child's own margin. For absolute and fixed
children it is the inset plus the parent's border width.
Set --concentric-floor to stop small radii from collapsing to a sharp corner. It inherits like any custom property,
so you can set it once or per element.
:root {
--concentric-floor: 2px;
}<div class="[--concentric-floor:4px] rounded-lg p-4">
<div class="rounded-concentric">…</div> <!-- 4px instead of 0 -->
</div>
Register the concentric classes so they conflict with rounded-* the way any other radius would. This is the only
JavaScript in the package, and it is optional.
import { extendTailwindMerge } from "tailwind-merge";
import { withConcentric } from "tailwindcss-concentric/merge";
export const twMerge = extendTailwindMerge(withConcentric);
twMerge("rounded-xl rounded-concentric"); // "rounded-concentric"The repository ships an agent skill that teaches coding agents the plugin's rules. Ask it to set up the plugin and it installs the package and adds the import. Ask for an audit and it walks your components, reports every nested radius and rewrites the ones it can prove.
npx skills add diip3sh/tailwindcss-concentricRun the command at the root of your project. The skill works with Claude Code, Codex, Cursor, Gemini CLI and other agents that read skills.
Then ask for an audit:
❯ Audit nested border radii in src/ and app/ and fix them.Scanned 214 files · 6 nested radii · 5 fixed · 1 to reviewsrc/components/card.tsx:14 rounded-t-2xl → rounded-t-concentricsrc/components/dialog.tsx:42 rounded-xl → rounded-concentricsrc/components/search.tsx:27 rounded-lg → rounded-concentricsrc/ui/segmented.tsx:19 rounded-md → rounded-concentricapp/pricing/page.tsx:88 rounded-[28px] → rounded-concentricsrc/components/toast.tsx:31 rounded-lg → reviewReview toast.tsx: the padding comes from custom CSS.
It only rewrites a radius when the classes prove the result. Anything else, like the custom CSS padding in toast.tsx, is left for you to review.
rounded-concentric, as shown below.
rounded-concentric-{n}.
inset-x-1/4 fall back to the parent's padding, and auto margins count as 0.
concentric-squircle covers corner-shape: squircle. Other corner shapes have no gap scale yet.
dir attributes, including dir="auto" at any depth in browsers with @scope. Older browsers read up to three nested levels. CSS-only direction is not detected.
rounded counts; deeper ones reuse the last
level.
The first two have a one-class fix. A wrapper in the chain passes its padding on, however many wrappers sit inside it. Without a background or border, its own radius is invisible.
rounded-3xl border › p-2 › p-1 › rounded-concentric
Padding the plugin can't read needs an explicit gap. rounded-concentric-3 subtracts 12px wherever the padding
comes from.
rounded-3xl style="padding: 12px" › panel
A hardcoded smaller radius is only right for one combination of values. It breaks as soon as the padding, the border
or the outer radius changes, and it doesn't compose across nesting levels or breakpoints. rounded-concentric recalculates for every one of them.
No. It is a CSS-first Tailwind v4 plugin made of registered custom properties and utilities. The tailwind-merge helper is the only JavaScript, and it is optional.
The same as Tailwind v4: Chrome 111, Safari 16.4 and Firefox 128 or newer. The plugin relies on @property,
which all of them support. Browsers with @scope (Chrome 118, Safari 17.4, Firefox 146) also follow dir="auto" and direction changes nested at any depth.
The child gets a square corner, or --concentric-floor if you set one. A rounded-full parent stays a pill
all the way down.
About 3.2 KB gzipped in a minified build: the custom properties and the nesting rules, plus the declarations of the utilities you use. Each nesting level costs about 20 bytes, so there is no smaller build to choose.
Run npx tailwindcss-concentric audit src. It reads the class attributes in HTML, JSX, Vue, Svelte and Astro
files and lists nested radii that are off, hard-coded calc() radii and wrappers the plugin can't see. It exits
with 1 when it finds something, so it can run in CI.