Penpot kit

Design tokens

Nothing in this kit is styled by hand. Colour, spacing, radius, type size and font weight all come from tokens, so the file can be restyled from one place instead of five hundred.

The tokens follow the Tailwind v4 scale and the shadcn semantic layer, organised into five sets that build on each other.

The five sets

SetWhat it holds
tailwindThe raw scale - colour ramps, spacing steps, radii, font sizes and weights, as Tailwind defines them.
themeThe semantic layer. Names like primary, muted and destructive, each pointing at a Tailwind value rather than a hex.
mode-lightWhat those semantic names resolve to in light mode.
mode-darkThe same names resolved for dark mode.
customAnything this kit needed that shadcn does not define, kept apart so it stays obvious what is ours.

Only one mode set resolves at a time. Switching the theme swaps which one is active, and every component follows without being touched.

How a value reaches a shape

A button's fill is not #171717. It is base.primary, which points at colors.primary-light, which points at a Tailwind neutral. Three hops, and only the last one holds an actual colour.

Change the neutral and every primary surface in the file moves with it - the buttons, the badges, the switch tracks, the sidebar tiles. That indirection is the whole point.

The names worth learning

The semantic set is small enough to hold in your head. These are the ones you will reach for most.

NameWhat it is for
background / foregroundThe page itself and the text sitting on it.
card / card-foregroundA raised surface and its text.
popover / popover-foregroundFloating surfaces - menus, tooltips, dropdowns.
primary / primary-foregroundThe main action, and the text on top of it.
secondary / secondary-foregroundA quieter action that still needs a fill.
muted / muted-foregroundBackgrounds that should recede, and de-emphasised text.
accent / accent-foregroundHover and active surfaces.
destructiveAnything that deletes, fails or refuses.
border / inputEdges. input is the one used on form controls.
ringThe focus ring, always drawn at reduced opacity.

Working with tokens here

Reach for the semantic name. Use base.primary, not the Tailwind neutral it happens to resolve to today. The semantic layer is the contract; the scale underneath it is an implementation detail.
Let the mode sets do the theming. Never draw a dark variant by hand. If something looks wrong in dark mode, the fix belongs in mode-dark, not on the shape.
Add to custom, not to tailwind. Anything the kit invents lives in its own set, so it stays clear which values came from shadcn and which came from us.
Name for the role. danger survives a change of red; red does not.

Where Penpot and Tailwind part ways

Translating the scale into Penpot is not lossless. Four differences are worth knowing before they surprise you - each one was hit while building this kit.

Line height
TailwindLeading is a length. text-sm carries a 20px line height.
PenpotlineHeight is a multiplier, so a token holding 20 renders as twenty times the font size. Sizes and weights are tokenised; line height stays a literal ratio.
Typography tokens
TailwindA text style is size, weight and leading together.
PenpotThe combined typography token carries leading as a length - applying it inflates the text, and correcting the leading detaches the token. Bind font size and weight separately instead.
Colour space
TailwindColours are defined in OKLCH.
PenpotHex only, so the ramps are converted on the way in. The values match at each stop; anything interpolating between them will not.
Arithmetic
TailwindSpacing is computed - p-4 is the spacing unit times four.
PenpotTokens hold fixed values, so every step is written out. Changing the base unit means changing the steps.

None of this blocks the work. It means a handful of values are literals where you might expect a reference, and the component pages say so wherever it changed a decision.