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 that share a center. Give a child rounded="concentric" and its radius follows its rounded parent.
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. The concentric preset does that math for you.
Add rounded="concentric" to the child. It reads the radius, padding and border from the style props you already wrote
on the parent, subtracts in CSS, and keeps working through every breakpoint, state and nesting level.
import { styled } from "styled-system/jsx"
export function ConcentricCompareDemo() {
return (
<styled.div
display="grid"
gridTemplateColumns={{ base: "1fr", sm: "repeat(2, minmax(0, 1fr))" }}
gap="10"
w="full"
maxW="xl"
>
<styled.figure display="flex" flexDirection="column" alignItems="center" gap="4">
<styled.div w="full" rounded="36px" p="3" borderWidth="1px" bg="bg.subtle">
<styled.div rounded="36px" p="3" borderWidth="1px" bg="bg.muted">
<styled.div rounded="36px" h="20" borderWidth="1px" bg="bg.emphasized" />
</styled.div>
</styled.div>
<styled.figcaption textStyle="xs" fontFamily="mono" color="fg.muted">
36px · 36px · 36px
</styled.figcaption>
</styled.figure>
<styled.figure display="flex" flexDirection="column" alignItems="center" gap="4">
<styled.div w="full" rounded="36px" p="3" borderWidth="1px" bg="bg.subtle">
<styled.div rounded="concentric" p="3" borderWidth="1px" bg="bg.muted">
<styled.div rounded="concentric" h="20" borderWidth="1px" bg="bg.emphasized" />
</styled.div>
</styled.div>
<styled.figcaption textStyle="xs" fontFamily="mono" color="fg.muted">
36px · 23px · 10px
</styled.figcaption>
</styled.figure>
</styled.div>
)
}Same markup on both sides, three levels with rounded="36px" p="3" borderWidth="1px". The left copies the parent's
radius onto every child, the right uses rounded="concentric".
Two 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 style props 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.
Add the preset to your project.
pnpm add -D @isbatak/panda-concentric-presetAdd it to presets in your Panda config, next to the design system. There is no JavaScript at runtime.
import { defineConfig } from "@pandacss/dev"
export default defineConfig({
designSystem: "@isbatak/panda-ds",
presets: ["@isbatak/panda-concentric-preset"],
})Then add one prop to any child of a rounded element.
<Box rounded="3xl" borderWidth="1px" p="3">
<Box rounded="concentric" /> {/* 24 − (12 + 1) = 11px */}
</Box>24px − (12px + 1px) = 11px
import { styled } from "styled-system/jsx"
export function ConcentricCardImageDemo() {
return (
<styled.div w="64" rounded="3xl" borderWidth="1px" p="3" bg="bg.panel">
<styled.div rounded="concentric" aspectRatio="4/3" bg="colorPalette.muted" />
<styled.p mt="3" px="1" textStyle="sm" fontWeight="medium">
24px − (12px + 1px) = 11px
</styled.p>
</styled.div>
)
}Every radius below comes from the preset. The root takes its radius, padding and border from CSS variables, and 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 | Expected | Formula |
|---|---|---|---|
| L1 | … | 64px | set on the root |
| L2 | … | 44px | 64 − 20 = 44 |
| L3 | … | 24px | 44 − 20 = 24 |
| L4 | … | 4px | 24 − 20 = 4 |
import { Slider } from "@isbatak/react-ui/slider"
import { Switch } from "@isbatak/react-ui/switch"
import { Table } from "@isbatak/react-ui/table"
import { type CSSProperties, type RefObject, useLayoutEffect, useRef, useState } from "react"
import { styled } from "styled-system/jsx"
interface Settings {
radius: number
paddingX: number
paddingY: number
border: number
levels: number
elliptical: boolean
floor: boolean
}
const FLOOR = 4
function ValueSlider({
label,
value,
max,
min = 0,
unit = "px",
onChange,
}: {
label: string
value: number
max: number
min?: number
unit?: string
onChange: (value: number) => void
}) {
return (
<Slider.Root value={[value]} min={min} max={max} onValueChange={(details) => onChange(details.value[0] ?? 0)}>
<styled.div display="flex" justifyContent="space-between" textStyle="sm">
<Slider.Label>{label}</Slider.Label>
<styled.span fontFamily="mono" color="fg.muted">
{value}
{unit}
</styled.span>
</styled.div>
<Slider.Control>
<Slider.Track>
<Slider.Range />
</Slider.Track>
<Slider.Thumb index={0}>
<Slider.HiddenInput />
</Slider.Thumb>
</Slider.Control>
</Slider.Root>
)
}
function Toggle({ label, checked, onChange }: { label: string; checked: boolean; onChange: (value: boolean) => void }) {
return (
<Switch.Root checked={checked} onCheckedChange={(details) => onChange(details.checked)}>
<Switch.Control>
<Switch.Thumb />
</Switch.Control>
<Switch.Label>{label}</Switch.Label>
<Switch.HiddenInput />
</Switch.Root>
)
}
function Level({
depth,
settings,
refs,
}: {
depth: number
settings: Settings
refs: RefObject<(HTMLDivElement | null)[]>
}) {
if (depth > settings.levels) return null
const innermost = depth === settings.levels
return (
<styled.div
ref={(element) => {
refs.current[depth] = element
}}
rounded="concentric"
concentricElliptical={settings.elliptical ? true : undefined}
px="var(--lab-px)"
py="var(--lab-py)"
minH={innermost ? "12" : undefined}
bg="colorPalette.solid/16"
>
<Level depth={depth + 1} settings={settings} refs={refs} />
</styled.div>
)
}
function expected(settings: Settings) {
const floor = settings.floor ? FLOOR : 0
const rows = [{ x: settings.radius, y: settings.radius, formula: "set on the root" }]
for (let depth = 1; depth <= settings.levels; depth++) {
const parent = rows[depth - 1]!
const border = depth === 1 ? settings.border : 0
const gapX = settings.paddingX + border
const gapY = settings.paddingY + border
if (settings.elliptical) {
const x = Math.max(floor, parent.x - gapX)
const y = Math.max(floor, parent.y - gapY)
rows.push({ x, y, formula: `${parent.x} − ${gapX} / ${parent.y} − ${gapY}` })
} else {
const gap = Math.min(gapX, gapY)
const r = Math.max(floor, Math.max(parent.x, parent.y) - gap)
rows.push({ x: r, y: r, formula: `${Math.max(parent.x, parent.y)} − ${gap} = ${r}` })
}
}
return rows
}
const format = (x: number, y: number) => (x === y ? `${x}px` : `${x}px ${y}px`)
export function ConcentricPlaygroundDemo() {
const [settings, setSettings] = useState<Settings>({
radius: 64,
paddingX: 20,
paddingY: 20,
border: 0,
levels: 3,
elliptical: false,
floor: false,
})
const refs = useRef<(HTMLDivElement | null)[]>([])
const [computed, setComputed] = useState<string[]>([])
const update =
<K extends keyof Settings>(key: K) =>
(value: Settings[K]) =>
setSettings((current) => ({ ...current, [key]: value }))
useLayoutEffect(() => {
setComputed(
Array.from({ length: settings.levels + 1 }, (_, depth) => {
const element = refs.current[depth]
return element ? getComputedStyle(element).borderTopLeftRadius : ""
}),
)
}, [settings])
const rows = expected(settings)
const vars = {
"--lab-r": `${settings.radius}px`,
"--lab-px": `${settings.paddingX}px`,
"--lab-py": `${settings.paddingY}px`,
"--lab-bw": `${settings.border}px`,
} as CSSProperties
return (
<styled.div
display="grid"
gridTemplateColumns={{ base: "1fr", md: "16rem minmax(0, 1fr)" }}
gap="8"
w="full"
alignItems="start"
>
<styled.div display="grid" gap="5">
<ValueSlider label="Outer radius" value={settings.radius} max={96} onChange={update("radius")} />
<ValueSlider label="Padding x" value={settings.paddingX} max={40} onChange={update("paddingX")} />
<ValueSlider label="Padding y" value={settings.paddingY} max={40} onChange={update("paddingY")} />
<ValueSlider label="Border width" value={settings.border} max={8} onChange={update("border")} />
<ValueSlider
label="Nested levels"
value={settings.levels}
min={1}
max={5}
unit=""
onChange={update("levels")}
/>
<Toggle label="Elliptical" checked={settings.elliptical} onChange={update("elliptical")} />
<Toggle label={`Floor ${FLOOR}px`} checked={settings.floor} onChange={update("floor")} />
</styled.div>
<styled.div display="grid" gap="6" minW="0">
<styled.div
ref={(element) => {
refs.current[0] = element
}}
style={vars}
concentricFloor={settings.floor ? "sm" : undefined}
rounded="var(--lab-r)"
px="var(--lab-px)"
py="var(--lab-py)"
borderWidth="var(--lab-bw)"
borderColor="colorPalette.solid"
bg="colorPalette.solid/8"
>
<Level depth={1} settings={settings} refs={refs} />
</styled.div>
<Table.Root size="sm">
<Table.Header>
<Table.Row>
<Table.ColumnHeader>Level</Table.ColumnHeader>
<Table.ColumnHeader>Computed</Table.ColumnHeader>
<Table.ColumnHeader>Expected</Table.ColumnHeader>
<Table.ColumnHeader>Formula</Table.ColumnHeader>
</Table.Row>
</Table.Header>
<Table.Body>
{rows.map((row, depth) => (
<Table.Row key={depth}>
<Table.Cell>L{depth + 1}</Table.Cell>
<Table.Cell fontFamily="mono">{computed[depth] ?? "…"}</Table.Cell>
<Table.Cell fontFamily="mono">{format(row.x, row.y)}</Table.Cell>
<Table.Cell color="fg.muted">{row.formula}</Table.Cell>
</Table.Row>
))}
</Table.Body>
</Table.Root>
</styled.div>
</styled.div>
)
}The same settings as style props:
<Box rounded="64px" p="5">
<Box rounded="concentric" p="5">
<Box rounded="concentric" p="5">
<Box rounded="concentric" p="5">
…
</Box>
</Box>
</Box>
</Box>import { styled } from "styled-system/jsx"
export function ConcentricNestedDemo() {
return (
<styled.div w="full" maxW="sm" rounded="64px" p="5" bg="bg.subtle">
<styled.div rounded="concentric" p="5" bg="bg.muted">
<styled.div rounded="concentric" p="5" bg="bg.emphasized">
<styled.div rounded="concentric" h="16" bg="colorPalette.solid" />
</styled.div>
</styled.div>
</styled.div>
)
}Each example keeps the props you would already write on the parent. Only the child changes, from a copied radius to
rounded="concentric". Change any of the parent's props, at any breakpoint, and the child follows.
The image uses roundedTop="concentric": 28px minus 6px padding and a 1px border is 21px.
Field notes · 6 min read
Notes, routes and a packing list from three days on the ridge.
import { styled } from "styled-system/jsx"
export function ConcentricCardDemo() {
return (
<styled.article w="72" rounded="28px" p="1.5" borderWidth="1px" bg="bg.panel">
<styled.div roundedTop="concentric" aspectRatio="16/10" bg="colorPalette.muted" />
<styled.div px="3.5" pt="4" pb="3.5">
<styled.p textStyle="xs" color="fg.subtle">
Field notes · 6 min read
</styled.p>
<styled.h3 mt="1.5" textStyle="md" fontWeight="semibold">
A weekend above the tree line
</styled.h3>
<styled.p mt="1" textStyle="sm" color="fg.muted">
Notes, routes and a packing list from three days on the ridge.
</styled.p>
</styled.div>
</styled.article>
)
}The buttons sit in a plain grid inside a 24px dialog with 8px of padding, so they get 16px.
Its 14 deployments and their logs go with it. This can't be undone.
import { Button } from "@isbatak/react-ui/button"
import { styled } from "styled-system/jsx"
export function ConcentricDialogDemo() {
return (
<styled.div w="80" rounded="24px" p="2" bg="bg.panel" boxShadow="lg">
<styled.div px="4" pt="2" pb="5">
<styled.h3 textStyle="md" fontWeight="semibold">
Delete this project?
</styled.h3>
<styled.p mt="1.5" textStyle="sm" color="fg.muted">
Its 14 deployments and their logs go with it. This can't be undone.
</styled.p>
</styled.div>
<styled.div display="grid" gridTemplateColumns="1fr 1fr" gap="2">
<Button rounded="concentric" colorPalette="gray" variant="subtle">
Cancel
</Button>
<Button colorPalette="gray" rounded="concentric">
Delete
</Button>
</styled.div>
</styled.div>
)
}A SegmentGroup with rounded="20px" p="1.5" on the root. Its indicator is absolutely positioned 6px inside the track,
so it gets 14px wherever it moves. The root's radius normally comes from the recipe, so the rounded prop is what makes
it the rounded parent.
import { SegmentGroup } from "@isbatak/react-ui/segment-group"
export function ConcentricSegmentedDemo() {
return (
<SegmentGroup.Root defaultValue="day" orientation="horizontal" aria-label="Range" rounded="20px" p="1.5">
<SegmentGroup.Indicator rounded="concentric" />
<SegmentGroup.Item value="day" rounded="concentric">
<SegmentGroup.ItemText>Day</SegmentGroup.ItemText>
<SegmentGroup.ItemHiddenInput />
</SegmentGroup.Item>
<SegmentGroup.Item value="week" rounded="concentric">
<SegmentGroup.ItemText>Week</SegmentGroup.ItemText>
<SegmentGroup.ItemHiddenInput />
</SegmentGroup.Item>
<SegmentGroup.Item value="month" rounded="concentric">
<SegmentGroup.ItemText>Month</SegmentGroup.ItemText>
<SegmentGroup.ItemHiddenInput />
</SegmentGroup.Item>
<SegmentGroup.Item value="year" rounded="concentric">
<SegmentGroup.ItemText>Year</SegmentGroup.ItemText>
<SegmentGroup.ItemHiddenInput />
</SegmentGroup.Item>
</SegmentGroup.Root>
)
}The button sits inside a 20px field with 6px of padding and a 1px border, so it gets 13px.
import { Button } from "@isbatak/react-ui/button"
import { styled } from "styled-system/jsx"
export function ConcentricSearchDemo() {
return (
<styled.form
display="flex"
alignItems="center"
gap="2"
w="full"
maxW="sm"
rounded="20px"
p="1.5"
borderWidth="1px"
bg="bg.panel"
>
<styled.input flex="1" minW="0" ps="3" h="9" bg="transparent" outline="none" placeholder="Search docs" />
<Button rounded="concentric" colorPalette="gray">
Search
</Button>
</styled.form>
)
}Two levels. The card is 36px with 8px of padding and a 1px border, so the panel gets 27px. The panel has 16px of padding, so its button gets 11px.
Pro
$24 / month
For teams shipping every day.
import { styled } from "styled-system/jsx"
export function ConcentricPricingDemo() {
return (
<styled.div w="72" rounded="36px" p="2" borderWidth="1px" bg="bg.muted">
<styled.div rounded="concentric" p="4" bg="bg.panel" boxShadow="xs">
<styled.p textStyle="sm" fontWeight="medium" color="fg.muted">
Pro
</styled.p>
<styled.p mt="2" textStyle="3xl" fontWeight="semibold">
$24
<styled.span textStyle="sm" fontWeight="normal" color="fg.muted">
{" "}
/ month
</styled.span>
</styled.p>
<styled.p mt="1" textStyle="sm" color="fg.muted">
For teams shipping every day.
</styled.p>
<styled.button
type="button"
mt="5"
w="full"
h="10"
rounded="concentric"
bg="colorPalette.solid"
color="colorPalette.contrast"
textStyle="sm"
fontWeight="medium"
>
Start free trial
</styled.button>
</styled.div>
<styled.ul display="grid" gap="1.5" px="4" pt="4" pb="3" textStyle="sm" color="fg.muted">
<li>Unlimited projects</li>
<li>Preview deployments</li>
<li>Priority support</li>
</styled.ul>
</styled.div>
)
}A negative margin makes the radius larger, for media that bleeds to the card's edge.
The negative margin cancels the padding, so the media keeps the card's full 24px.
import { styled } from "styled-system/jsx"
export function ConcentricBleedDemo() {
return (
<styled.div w="72" rounded="3xl" p="4" bg="bg.muted">
<styled.div mx="-4" mt="-4" roundedTop="concentric" aspectRatio="16/9" bg="colorPalette.muted" />
<styled.p mt="4" textStyle="sm" color="fg.muted">
The negative margin cancels the padding, so the media keeps the card's full 24px.
</styled.p>
</styled.div>
)
}An absolute or fixed child is measured by its inset plus the parent's border width.
Selected plan
The ring is inset 4px inside a 1px border: 16 − 1 − 4 = 11px.
import { styled } from "styled-system/jsx"
export function ConcentricRingDemo() {
return (
<styled.div position="relative" w="64" rounded="2xl" borderWidth="1px" p="4" bg="bg.panel">
<styled.span
position="absolute"
inset="1"
rounded="concentric"
borderWidth="2px"
borderStyle="dashed"
borderColor="colorPalette.solid"
pointerEvents="none"
/>
<styled.p textStyle="sm" fontWeight="medium">
Selected plan
</styled.p>
<styled.p mt="1" textStyle="sm" color="fg.muted">
The ring is inset 4px inside a 1px border: 16 − 1 − 4 = 11px.
</styled.p>
</styled.div>
)
}Every radius prop accepts "concentric", in the same shapes as the other radius values.
| Prop | Effect |
|---|---|
rounded="concentric" | All four corners follow the nearest rounded ancestor: its radius minus the gap between the edges. |
roundedTop, roundedRight, roundedBottom, roundedLeft | One side only. The other corners keep whatever radius they already have. |
roundedTopLeft, roundedTopRight, roundedBottomRight, roundedBottomLeft | One corner only. |
roundedStart, roundedEnd, roundedStartStart, roundedStartEnd, … | Logical sides and corners. They flip with the nearest dir attribute. |
concentricGap="3" | An explicit gap that replaces the measured one, for spacing the preset can't read. |
concentricElliptical | Keeps one radius per axis when the horizontal and vertical gaps differ. |
concentricSquircle | Sets corner-shape: squircle and scales the gap to match, where the browser supports it. |
concentricFloor="sm" | A minimum radius for every concentric element inside. |
Nothing new is needed on the parent: rounded, p, borderWidth, border, m and inset props are read as they
are. Tokens, raw values and CSS variables all work, and so do conditions like md and _hover.
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 concentricFloor to stop small radii from collapsing to a sharp corner. It sets --concentric-floor, which
inherits like any custom property, so you can set it once on the page or per element.
<Box concentricFloor="sm" rounded="lg" p="4">
<Box rounded="concentric" /> {/* 4px instead of 0 */}
</Box>import { styled } from "styled-system/jsx"
export function ConcentricFloorDemo() {
return (
<styled.div display="flex" flexWrap="wrap" justifyContent="center" gap="8">
<styled.figure display="flex" flexDirection="column" alignItems="center" gap="3">
<styled.div w="40" rounded="lg" p="4" borderWidth="1px" bg="bg.subtle">
<styled.div rounded="concentric" h="16" bg="colorPalette.emphasized" />
</styled.div>
<styled.figcaption textStyle="xs" fontFamily="mono" color="fg.muted">
8 − 16 → 0px
</styled.figcaption>
</styled.figure>
<styled.figure display="flex" flexDirection="column" alignItems="center" gap="3">
<styled.div w="40" concentricFloor="sm" rounded="lg" p="4" borderWidth="1px" bg="bg.subtle">
<styled.div rounded="concentric" h="16" bg="colorPalette.emphasized" />
</styled.div>
<styled.figcaption textStyle="xs" fontFamily="mono" color="fg.muted">
floor → 4px
</styled.figcaption>
</styled.figure>
</styled.div>
)
}rounded="concentric", as shown below.concentricGap. A recipe's
root is not a rounded ancestor either: add a rounded prop to it.auto margins count
as 0.dir attributes, including dir="auto" at any depth in browsers with @scope. Older browsers
read up to three nested levels.The first two have a one-prop 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.
import { styled } from "styled-system/jsx"
export function ConcentricWrapperDemo() {
return (
<styled.div
display="grid"
gridTemplateColumns={{ base: "1fr", sm: "repeat(2, minmax(0, 1fr))" }}
gap="10"
w="full"
maxW="lg"
>
<styled.figure display="flex" flexDirection="column" alignItems="center" gap="3">
<styled.div w="full" rounded="3xl" borderWidth="1px" bg="bg.subtle">
<styled.div p="2">
<styled.div p="1" display="grid" gridTemplateColumns="1fr 1fr" gap="1">
<styled.div rounded="concentric" h="14" bg="colorPalette.emphasized" />
<styled.div rounded="concentric" h="14" bg="colorPalette.emphasized" />
</styled.div>
</styled.div>
</styled.div>
<styled.figcaption textStyle="xs" fontFamily="mono" color="fg.muted">
plain wrapper → 19px
</styled.figcaption>
</styled.figure>
<styled.figure display="flex" flexDirection="column" alignItems="center" gap="3">
<styled.div w="full" rounded="3xl" borderWidth="1px" bg="bg.subtle">
<styled.div rounded="concentric" p="2">
<styled.div p="1" display="grid" gridTemplateColumns="1fr 1fr" gap="1">
<styled.div rounded="concentric" h="14" bg="colorPalette.emphasized" />
<styled.div rounded="concentric" h="14" bg="colorPalette.emphasized" />
</styled.div>
</styled.div>
</styled.div>
<styled.figcaption textStyle="xs" fontFamily="mono" color="fg.muted">
wrapper in the chain → 11px
</styled.figcaption>
</styled.figure>
</styled.div>
)
}Only the inner wrapper's 4px reaches the items on the left. With the outer wrapper in the chain they get 24 − 1 − 8 − 4 = 11px.
Padding the preset can't read needs an explicit gap. concentricGap="3" subtracts 12px wherever the padding comes from.
import { styled } from "styled-system/jsx"
export function ConcentricGapDemo() {
return (
<styled.div
display="grid"
gridTemplateColumns={{ base: "1fr", sm: "repeat(2, minmax(0, 1fr))" }}
gap="10"
w="full"
maxW="lg"
>
<styled.figure display="flex" flexDirection="column" alignItems="center" gap="3">
<styled.div w="full" rounded="3xl" borderWidth="1px" bg="bg.subtle" style={{ padding: 12 }}>
<styled.div rounded="concentric" h="20" bg="colorPalette.emphasized" />
</styled.div>
<styled.figcaption textStyle="xs" fontFamily="mono" color="fg.muted">
measured → 24px
</styled.figcaption>
</styled.figure>
<styled.figure display="flex" flexDirection="column" alignItems="center" gap="3">
<styled.div w="full" rounded="3xl" borderWidth="1px" bg="bg.subtle" style={{ padding: 12 }}>
<styled.div rounded="concentric" concentricGap="3" h="20" bg="colorPalette.emphasized" />
</styled.div>
<styled.figcaption textStyle="xs" fontFamily="mono" color="fg.muted">
concentricGap="3" → 12px
</styled.figcaption>
</styled.figure>
</styled.div>
)
}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. The preset adds registered custom properties, a few global rules and the radius value to your Panda config. The browser does the rest in CSS.
Chrome 111, Safari 16.4 and Firefox 128 or newer, the browsers with @property. 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 the floor if you set one. A rounded="full" parent stays a pill all the way
down.
Padding, border, margin, inset and position props still output the same CSS. They also write a few custom properties, so concentric children can measure them.