Toggle
A button that stays in - icon plus label, in two variants and three sizes, where Pressed is not a finger held down but the state the control is left in.
Not a finger held down - it is what the source calls data-state=on, the toggle that is currently doing its thing. That is the whole component: a button that stays in after you let go. Everything else on the State axis is the ordinary run of a button.
Default is invisible until you touch it: no fill, no border, just the icon and the label. Outline gives it base.background and a 1px base.input edge, so it reads as a control even when nothing is on. Both fill with base.muted on hover and on, and on Default the label separates them - it dims on hover and stays dark once the toggle is on.
28, 32 and 36 tall, all carrying the same 14/20 label and the same 16px icon. Only the box and its padding change - 10 either side at Default and sm, 12 at lg. A size axis that also changed the type would stop the three from lining up in one toolbar.
Off, on, and the hover in between
The matrix above is five stills. Here the same toggle actually holds its state, which is the one thing a still cannot show: click it and it stays in. Point at it while it is off and watch how close the two look.
Hover an off toggle and it takes the same base.muted fill that on does, so the fill cannot be what tells them apart. The label does it: hover writes base.muted-foreground while on keeps base.foreground, which is exactly what the source's hover:text-muted-foreground and data-[state=on]:text-accent-foreground come to. It matters most in a toolbar, where a hovered off toggle sits inches from an on one.
A toggle changes something you can see
It takes effect the moment you press it, with nothing to save and nothing to confirm. That is the test for reaching for one, and it is what separates it from the two controls it gets confused with.
The toggles above are doing this, right now, with nothing to save.
Two kinds of group in one strip. The three marks are independent - any number can be on at once. The three alignments are exclusive, so turning one on turns the others off. Both are the same Toggle; what differs is the rule around them, which is what Toggle Group is for.
The strip mirrors, some of the glyphs must not
The toggle itself needs nothing: icon before label, even padding, a symmetrical box. What does need a decision is the icons. Alignment glyphs describe a direction, so they have to flip with the text - and bold, italic and underline describe a shape, so they must not.
Icon then label is reading order, and the gap between them is a gap. Setting dir on an ancestor puts the icon on the right of the label with no arithmetic, and the strip of toggles reverses with it.
Align left means align to the reading edge, so under rtl its glyph points the other way. Bold and italic mean nothing about direction, and mirroring them just produces a backwards B. This is the one thing on the page that needs a per-icon decision.
If the glyph flips, so should its label and its aria-label. A mirrored align-left icon announced as "align left" is worse than not mirroring at all - use start and end in the markup and let the translation carry the words.
One component, thirty variants
Read straight out of the Penpot file. The axes below are the property names you will see in the variant dropdown.
Using the Toggle
The on state, not a finger held down. The source calls it data-state=on: the toggle that is currently doing its thing and will stay that way after you let go. That is the entire point of the component - a button that keeps its position - and everything else on the State axis is the ordinary run of any button.
By the label. Both take a base.muted fill, so the box cannot separate them - what changes is the type. On the Default variant hover writes base.muted-foreground while on keeps base.foreground, which is the source's hover:text-muted-foreground against data-[state=on]:text-accent-foreground. Outline overrides that back to the dark label, so on Outline a hovered toggle and an on one really are the same picture; that is the source's own behaviour, and it is a reason to prefer Default inside a toolbar where several sit together.
Default has no fill and no border at rest, so it only works inside something that already reads as a control strip - a toolbar, a Toggle Group. Outline gives it base.background and a 1px base.input edge, which is what a toggle standing on its own needs. A lone Default toggle on a white page is invisible until someone points at it.
The box and its padding, and nothing else. 68 × 28 at sm, 68 × 32 at Default, 72 × 36 at lg, all carrying the same 14/20 label and the same 16px icon. Keeping the type fixed is what lets three different sizes line up in one row without the labels stepping.
Yes, and the source expects it - min-w-9 exists so the box can be square. At Default size that gives you 32 by 32 with the icon centred. It comes with one requirement: an aria-label, and a tooltip whenever the glyph is at all ambiguous, because a pressed icon with no name is a state nobody can read.
Decide by when the change lands. A toggle acts immediately and sits next to the thing it changes - bold on this word, this filter on this list. A switch also acts immediately but reads as a preference, and it wants a label of its own. A checkbox is a value in a form and changes nothing until something is submitted. Picking the one that looks best is how you end up with a settings page that silently saves and a form that silently does not.
With Toggle Group, which is its own component - a bordered container with a 2px inset holding borderless toggles. Butting three Outline toggles together instead gives you doubled borders at every join and a strip a pixel taller than its parts. The group also carries the rule: independent marks like bold and italic, or exclusive ones like the three alignments.
Tabler's bold glyph at 16 × 16, stroke 2, bound to base.foreground - the same weight every other 16px icon in the kit carries. It is a placeholder - swap it on the instance. The label beside it is a plain Text layer, so a toggle that needs no label is a matter of hiding it and letting the box go square.
The toggle needs nothing: icon before label is reading order, the padding is even and the box is symmetrical, so dir on an ancestor does all of it. The icons are the part that needs thought. An alignment glyph describes a direction and has to mirror with the text; bold, italic and underline describe a shape and must not, or you get a backwards B. If a glyph flips, its aria-label should flip with it - write start and end rather than left and right.
No - this page in the file is the Components board and nothing else. The examples on this page are the kit's answer to that: a formatting toolbar, a single filter, the icon-only case, the argument against the two controls it gets confused with, and what changes once several sit together.