Penpot kit

Components

1,839 addressable components over fifty-nine pages, built to match shadcn/ui rather than to resemble it. This page is about working with them: where things sit, how to change one, and what to watch for when you add your own.

How a page is laid out

Every component has its own page. Most carry the same two frames; thirteen carry only the Components frame, where a usage example would repeat the matrix rather than add to it.

Components. The workbench. Every variant of the component in its variant container, and nothing else.
The named frame. Usage. The component in realistic context, at realistic sizes, light mode only.

The split matters as soon as you start editing. The Components frame holds the mains; the usage frame holds copies. Change a main and every copy in the file follows. Change a copy and you have made an override, which will quietly stop tracking the main from then on.

Editing a component

Almost nothing here is a raw value. Fills, strokes, padding, gaps and radii are bound to tokens, so the edit you want is usually to the binding rather than to the number.

Worked example - giving Badge more room

Badge is 53×20, with 8px of horizontal padding and a fully rounded end. Suppose it should be roomier and squarer.

1
Select the main

Open the Badge page and select the main inside the variant container, not an instance from the usage frame.

2
Rebind the padding

Its horizontal padding is bound to spacing.2. Rebind left and right to spacing.3.

3
Rebind the corners

Its corners are bound to radius.4xl. Rebind them to radius.md.

4
Done

Every badge in the file updates, including the ones nested inside other components.

Bind all four corners, not one. Penpot stores radius per corner, and a token applied to the shorthand leaves the other three behind - you get one square corner and three round ones.

The same edit in code is px-2 to px-3 and rounded-full to rounded-md. The names line up because the tokens are the Tailwind scale.

Variants are properties

A variant is not a separate component. Button is one component with three axes and 144 combinations behind them.

AxisValues
Variantdefault · secondary · outline · ghost · destructive · link
Statedefault · hover · focus · pressed · disabled · loading
Sizexs · sm · default · lg

The State axis is the one that departs from code. In a browser those are pseudo-classes; here every one of them has to be drawn, because a design file has no hover. That is why the counts grow - Switch carries 192 variants for the same reason.

Two rules about optional parts, both learned by getting them wrong first.

If presence changes the layout, make it an axis. Show Label on Switch moves everything beside it, so it is a real property with Yes and No - not something a consumer is expected to hide by hand.
If it does not, a hidden layer is enough. The close button on a toast and the undo action on an alert sit in every variant, hidden, and a copy can reveal one without doubling the matrix.

Adding a variant

Say the kit needs a warning button alongside destructive.

1
Add the colours first

warning and warning-foreground in theme, resolved in both mode sets. The Theming page covers that part.

2
Duplicate the destructive set

Six states across four sizes, twenty-four boards.

3
Rebind fills and text

From the destructive names to the new ones. Nothing else changes, because padding, radius and type already come from tokens.

4
Add them to the container

Add the boards to the variant container and set Variant to Warning on each of them.

Two things go wrong here. Duplicating carries overrides along with it, so check the label and the icon on each copy rather than assuming they are clean. And add the boards in small groups - a large batch of new variants and property assignments at once tends to leave a few unassigned, which surfaces much later as a variant with a blank property.

Making a new component

New components are welcome. The only real constraint is that once built, it should be impossible to tell yours from the rest.

Give it its own page, with the same two frames: Components and a usage frame.
Lay it out with flex, not coordinates. Padding, gaps and sizing come from tokens.
Bind every colour to a semantic name - base.card, base.border, base.muted-foreground. Never a hex.
Cover the states the rest of the kit covers: default, hover, focus, disabled. Focus is an outer ring at reduced opacity, not a colour change.
Build the variants as plain boards first, turn them into a component second, combine them third. In that order, in separate steps.
Give the variant container at least 24px of padding so the outer focus rings are not clipped.
Name the layers. A component whose children are Board, Board 2 and Rectangle is unusable to everyone except the person who built it.

Handing it over

The point of binding everything is that a design change reads as a code change without translation.

Token hereClass there
spacing.2 · spacing.3p-2 · p-3
radius.mdrounded-md
base.primarybg-primary
base.muted-foregroundtext-muted-foreground
text.sm.font-sizetext-sm

So the useful handover note is not a screenshot. It is two token names, before and after, and the component they sit on. A developer can act on that without opening the file.

None of this is ceremony. The kit is only worth using while it stays consistent, and consistency here is almost entirely a matter of editing the main rather than the copy, and the token rather than the value.