Knit UI
GitHub

Sizing

Every control-shaped component in the kit — buttons, inputs, badges, avatars, chips, selects, switches, loaders — takes size="xxs" … "xxl" and derives its height, font size, padding, gap and radius from one table.

<Group>
  <Button size="lg">Save</Button>
  <TextInput size="lg" placeholder="Email" />
  <Badge size="lg">New</Badge>
</Group>

Those three line up because they read the same row, not because someone matched the numbers by hand.

The table

From the source

THE canonical control-sizing table — the single source of truth for how every control-shaped component scales across the `xxs → xxl` size keys. Each archetype variant in `style-props.ts` (and the input host sizing in `Input/shared.tsx`) is DERIVED from this table, so the relationship between height, font, padding, gap, and radius is defined exactly once. Re-tune a column here and Button, Input, Badge, Avatar, Progress, and Slider all move together. Every value is a TOKEN KEY (`"$md"`), which resolves per-property: `$md` is `size.md` (40) on `height`, `space.md` (16) on `paddingHorizontal`/`gap`, `font.md` (18) on `fontSize`, `radius.md` (8) on `borderRadius`. See `core/config/scales.ts` for the raw numbers and `docs/sizing/README.md` for the design rationale (balanced text-to-height ratio, capped radius curve). key height font ratio padX padX(pill) gap radius xxs 24 12 .50 6 10 6 2 xs 28 12 .43 10 12 10 2 sm 32 14 .44 12 16 12 4 md 40 18 .45 16 20 16 4 lg 48 20 .42 20 24 20 8 xl 56 24 .43 24 24 24 8 xxl 64 28 .44 32 32 32 16 `fontSize` is intentionally CLAMPED at the bottom two steps (xxs/xs borrow the 12px `$xxs` font instead of their same-key font) so tiny controls aren't dominated by text; `sm → xxl` hold a flat ~0.42–0.45 ratio. `borderRadius` is CAPPED at `$lg` (16) so large controls stay rectangles, not accidental pills. `paddingHorizontalPill` is the standard padding bumped one space step — the one sanctioned divergence, used by the pill family (Badge/Chip/Pill).

sizeheightfontSizepaddingHorizontalpaddingHorizontalPillgapborderRadius
xxs$xxs 18px$xxs 12px$xxs 2px$xs 4px$xxs 2px$xs 2px
xs$xs 24px$xxs 12px$xs 4px$sm 8px$xxs 2px$xs 2px
sm$sm 32px$xs 14px$sm 8px$md 12px$xs 4px$sm 4px
md$md 40px$md 18px$md 12px$lg 16px$xs 4px$sm 4px
lg$lg 48px$lg 20px$lg 16px$xl 24px$sm 8px$md 8px
xl$xl 64px$xl 24px$xl 24px$xl 24px$sm 8px$md 8px
xxl$xxl 96px$xxl 28px$xxl 32px$xxl 32px$md 12px$lg 16px

Each cell is a token key, resolved per property (see Tokens); the grey number is what it resolves to.

Reading the design

Two decisions in that table are deliberate and worth knowing:

Font size is clamped at the bottom. xxs and xs borrow the 12px $xxs font instead of their own step, so tiny controls are not dominated by their label. From sm up, the text-to-height ratio holds flat at about 0.42–0.45.

Radius is capped at $lg. Large controls stay rectangles rather than becoming accidental pills. The pill family (Badge, Chip, Pill) opts into a different look through paddingHorizontalPill — the standard padding bumped one space step — which is the one sanctioned divergence.

The default is md

Every sized component defaults to md. If a design calls for something else globally, change it in your theme rather than passing size everywhere.

Nested controls step down

A control inside another control should not be the same size as its host. The kit resolves that with toEmbeddedControlSize, which steps one key down — so the close button in a size="md" Alert comes out at sm, automatically.

Using the table yourself

If you build a control that should size like the kit's, read the same table rather than copying numbers. It is public API:

import { controlMetrics, DEFAULT_SIZE, type SizeKey } from "@knitui/components/control-system";

function MyControl({ size = DEFAULT_SIZE }: { size?: SizeKey }) {
  const metrics = controlMetrics[size];
  return <Box height={metrics.height} px={metrics.paddingHorizontal} br={metrics.borderRadius} />;
}

That export exists precisely so sibling packages (@knitui/media's player chrome, for instance) size against the kit instead of drifting from it.

What is not on this scale

Slider deliberately uses local pixel geometry: a track and thumb sized off the control ladder came out either too chunky or too fiddly at the extremes. Accordion's chevron has its own 10–22px glyph table for the same reason. When a component diverges, its page says so.