Button
The largest page in the kit. Two published components, 264 variants across three axes, plus two hidden icon layers that would have made it four times bigger.
Two hundred and sixty-four variants
Button is six variants across six states across four sizes: 144. Icon Button repeats it without Link, because an icon-only link is a ghost icon button: 120. The grid below is the colour half, at the default size - the sizes are in the table under it.
| Variant | Default | Hover | Focus | Pressed | Disabled | Loading |
|---|---|---|---|---|---|---|
| Default | Button | Button | Button | Button | Button | Button |
| Secondary | Button | Button | Button | Button | Button | Button |
| Outline | Button | Button | Button | Button | Button | Button |
| Ghost | Button | Button | Button | Button | Button | Button |
| Destructive | Button | Button | Button | Button | Button | Button |
| Link | Button | Button | Button | Button | Button | Button |
Height moves, padding mostly does not
Only xs has its own padding. sm, default and lg all sit at spacing.2-5, so the difference between them is height, radius, label size and icon size. lg and default differ by four pixels of height and nothing else at all.
| Size | Height | Padding | Gap | Radius | Label | Icon | Actual |
|---|---|---|---|---|---|---|---|
| xs | 24px | 8px | 4px | radius.md | 12px | 12px | Button |
| sm | 28px | 10px | 4px | radius.md | 12px | 12px | Button |
| default | 32px | 10px | 6px | radius.lg | 14px | 16px | Button |
| lg | 36px | 10px | 6px | radius.lg | 14px | 16px | Button |
Two hidden icons, one visible spinner
Every Button carries leftIcon and rightIcon layers that ship hidden. Reveal either on a copy - the checkboxes above do it. As axes they would have taken 264 variants to 1,056, and an icon at the end of a row moves nothing beside it.
Loading is different, and that is why it is a state: it adds a visible spinner before the label, which widens the button from 64 to 86. Something that changes the width has to be a real variant, or every copy would need resizing by hand.
Neither changes a colour. Disabled drops the whole button to 50% opacity, Pressed to 60%, and everything else stays where it was. One rule for six variants, instead of twelve more colours to keep in step - and it is why Disabled still reads as the variant it is rather than turning into a generic grey.
Two components, 264 variants
Read straight out of the Penpot file. The axes below are the property names you will see in the variant dropdown.
Using the Button
Because the spinner changes the width. Loading widens the default button from 64 to 86, and anything that moves the geometry has to be a real variant or every copy needs resizing by hand. leftIcon and rightIcon sit at the ends of the row and move nothing, so they ship as hidden layers - as axes they would have taken 264 variants to 1,056.
Because an icon-only link is a ghost icon button. Link is a text treatment - it underlines on hover and carries no fill - and there is no text to underline. Dropping it is what makes Icon Button 120 rather than 144.
They change opacity instead: 60% pressed, 50% disabled, and nothing else moves. One rule covers all six variants, so a disabled destructive button still reads as destructive rather than turning into a generic grey - and there are no twelve extra colours to keep in step with the rest.
Four pixels of height, and nothing else. Both sit at spacing.2-5 padding, radius.lg, a 14px label and 16px icons. Only xs has its own padding. It is worth knowing before you reach for lg expecting more room inside it.