Spinner
A 270 degree arc in five sizes, and a second axis that exists only because Penpot cannot spin anything.
The source is one line: an arc with animate-spin. Penpot cannot animate, so the file records four quarter-turn positions of that spin instead. It is the only honest way to put a moving thing in a static file, and it doubles as a keyframe list for whoever builds it.
Every size draws the same arc at a twelfth of its own width: 1 at 12, 2 at 24, 2.667 at 32. That is what keeps a 12px spinner from looking like a hairline and a 32px one from looking like a doughnut. Resize an instance and the stroke does not follow, so pick the size first.
Three quarters of a circle, with round caps. The gap is what tells you it is moving even in a still frame - a full ring would read as a border. shadcn uses lucide's LoaderIcon; the kit is on Tabler throughout, and loader-2 is the arc that matches.
Four frames of one rotation
Side by side the four Spin Degree values are the same arc turned a quarter each time, which is exactly what animate-spin does over its second. Place whichever one sits best in a mockup - the gap pointing away from the text usually reads calmest.
Ten places, and it belongs to none of them
1392 by 970. The spinner has no layout of its own - it is a 16 or 20 square that other components make room for. Button has a Loading state that holds one, Badge and Input Group take it inline, Empty puts it where an icon would go. All of those are real instances in the file.
Two things the file settles that the source leaves open. Button carries its own Loading state rather than expecting you to drop a spinner in, which is why the six loading buttons in the frame are Button instances and not compositions. And the Empty state's spinner got there by swapping the media component - the slot was already the right size, so nothing needed moving.
The arc does not flip, its position does
A rotating circle has no handedness, so the glyph itself is the same in both directions. What moves is which end of the row it sits on. The docs show this for the spinner; it is one more case where dir does the work and no variant is needed.
One component, twenty variants
Read straight out of the Penpot file. The axes below are the property names you will see in the variant dropdown.
Using the Spinner
It does not, in code - the source is one line, an arc with animate-spin. Five of the twenty are the sizes you actually place. The other axis is four quarter-turn positions of that spin, recorded because a design file cannot animate. It is the honest way to put a moving thing in a static frame, and it doubles as a keyframe list for whoever builds it.
Whichever sits best in the mockup - they are the same drawing turned a quarter each time, and no user will ever see a still one. The gap pointing away from the text beside it usually reads calmest. If you are handing the file to a developer, the degree is noise: they will write animate-spin and the position becomes whatever the page load happens to catch.
Because it is always a twelfth of the box: 1 at 12, 1.333 at 16, 2 at 24, 2.667 at 32. That is what keeps a small spinner from looking like a hairline and a large one from looking like a doughnut. It also means resizing an instance is not the same as picking a size - the stroke does not follow, so choose the variant first.
Because the gap is what says it is moving. A closed circle reads as a border or a badge, and in a still frame there is nothing left to suggest rotation. Three quarters with round caps is enough arc to look like a shape and enough gap to look unfinished, which is the whole trick.
Tabler's loader-2, like every other glyph in the kit. shadcn uses lucide's LoaderIcon, which is twelve separate ticks around a circle rather than one arc. Both spin; the arc reads smoother under a linear animation, and matching the kit's own icon set matters more here than matching the import line.
Inside other components, and it owns none of them. Button carries its own Loading state, so the six loading buttons in the usage frame are Button instances rather than a spinner dropped beside a label. Badge and Input Group take it inline. Empty puts it in the media slot, which it got by swapping the media component - the slot was already the right size, so nothing needed moving.
No. A rotating circle has no handedness, so the glyph is identical in both directions; what changes is which end of the row it sits on, and that is flex order. Set dir on the container and the spinner follows the text it belongs to.