Sizing

The semantic token scales, and the precedence ladder an icon's size resolves through.

Sizes are semantic tokens, not raw utilities. tokens.css owns the values in Tailwind's own namespaces, so they compile to ordinary utilities.

TokenUtilityWhat it sizes
--spacing-button-*h-button-md, w-button-mda button's height, and its square footprint when icon-only
--spacing-icon-*size-icon-mdany glyph — Icon, Spinner, a row's chevron
--text-button-*text-button-mda button's label, paired with its height
--spacing-input-*h-input-md, min-h-input-mda field's height — fixed on one line, a floor when multiline
--text-input-*text-input-mda field's value, paired with its height
--spacing-navbar-rowh-navbar-rowthe navbar's control row, without its safe-area band
--spacing-screen-gutterpx-screen-gutterthe gutter Screen.Header, Screen.Navbar and content share

A token is named for what it sizes, not for an abstract category. A control that shares another's heights gets its own namespace rather than borrowing one — --spacing-input-* names the same 36/44/52 as --spacing-button-* so a field and the button beside it can be retuned independently. They are meant to stay level, and a test asserts that outright.

Icon and Spinner share one scale

SPINNER_SIZES is ICON_SIZESxs through 2xl, 14/16/18/20/24/32pt — so size="md" is the same edge length in both and one can stand in for the other with nothing moving. That is what makes a button's loading swap free: both glyphs are drawn at the button's own size-icon-* token, so replacing one with the other costs no layout.

A component indexes that scale at its own step name rather than restating a number. A Checkbox's square reads it two steps above its own tick — 18/14, 20/16, 24/18 — and the test pins the offset rather than the points, so the icon scale can be retuned without the test becoming a transcript of it.

An icon's size is a class that drives a prop

CentralIconBase spreads its props onto <Svg> before setting its own width/height from size, and react-native-svg then merges {...style, ...props} with props winning. A size-4 that resolved to style.width is overridden every single time.

So Icon runs the class through withUniwind in manual mode to recover the width, and hands that number to the glyph. The indirection is forced, not stylistic.

Precedence, weakest first

The first four are one cn() chain, so the last size-* wins. The fifth is a different mechanism entirely, which is what makes "inherited class plus caller-supplied number" resolve correctly.

SourceHow it wins
ICON_FALLBACK_SIZE_CLASSfirst in the chain
the enclosing IconDefaultsProvider's classNamesecond
a named size="lg"third
the caller's classNamelast in the chain
a numeric size={18}bypasses the chain — the manual mapping is skipped when the target prop is already defined

Adding a token means editing two files

Put the value in tokens.css and the name in src/styles/tokens.ts. Miss the second and tailwind-merge stops recognising the utility, so a caller's override quietly stops working. tokens.test.ts asserts the two lists match and reads the CSS to check the scale still ascends.

On this page