Spinner
A spinner shows that something is loading. It is feedback, not a control.
<tk-spinner size="24" label="Loading results"></tk-spinner>
Anatomy
| Part | What it is |
|---|---|
tk-spinner |
the element; decorative, or a role="status" region when it has a label |
| blades | eight blades with a fading trail that steps around the circle |
| label | hidden text from label; the accessible name |
Sizes
<tk-spinner></tk-spinner> <tk-spinner size="20"></tk-spinner> <tk-spinner size="24"></tk-spinner> <tk-spinner size="32"></tk-spinner>
size |
Box | Token | Use it for |
|---|---|---|---|
16 (default) |
16px | icon-size-16 |
inline, next to 14px text |
20 |
20px | icon-size-20 |
next to 16px text |
24 |
24px | icon-size-24 |
a loading card or list section |
32 |
32px | icon-size-32 |
a loading panel or empty area |
There is no size below 16px or above 32px.
Color
The spinner takes the current text color; there is no color prop. On a plain surface it
matches body text. For a quieter spinner, set an icon/* color on its container.
<span style="color: var(--icon-secondary)"><tk-spinner size="24" label="Loading comments"></tk-spinner></span>
Keep the color at least 3:1 against the background.
Motion
- The trail moves one blade at a time, one turn every 0.8s.
- With reduced motion turned on, a turn takes 2s. The spinner slows down but never stops, so it doesn't look finished.
- There is no
loadingprop: show the spinner while loading and remove it when done.
When to use
- Use a spinner for a loading area or an inline wait, such as a panel fetching its content, while the rest of the screen stays visible.
- For a pending button action, use the button's
loading. It keeps the button's size, ignores repeat clicks, and announces that it's busy. A spinner placed beside a button does none of that. - Pair a long wait with text. Motion alone shouldn't be the only sign of loading.
Content
- Name what is loading in
label: "Loading results", not just "Loading". - For a long wait, add visible text next to the spinner.
Do / Don't
| ✅ Do | ❌ Don't |
|---|---|
Use loading on a button for a pending action |
Put a spinner next to or inside a button by hand |
Set label on a standalone spinner |
Leave a standalone spinner silent |
| Add text to a long wait | Rely on motion alone |
| Color the spinner through its container | Restyle the blades |
| Pick a size from 16 to 32 | Scale it below 16px |
Related
Button andIcon button :loadingshows this spinner.Skeleton : a placeholder shaped like the content that is loading.Progress bar : loading with a known amount.
API
Attributes
| Attribute | Type | Default | What it does |
|---|---|---|---|
size |
16 | 20 | 24 | 32 |
16 |
the box size in px |
label |
string | unset | makes the spinner a role="status" region with this name; unset, it's decorative |
Slots
None.
Events
None. A spinner is not interactive.
Tokens
| Part | Token |
|---|---|
| box | icon-size-16, icon-size-20, icon-size-24, icon-size-32 |
| blades | the current text color, from the container |
| one turn | duration-800; duration-2000 with reduced motion |
Accessibility
Built in:
- Without
label, the spinner is hidden from screen readers. Use this inside something that already announces busy, such as a button withloading. - With
label, it's arole="status"region and the label is read. - Not focusable and not clickable.
Your part:
- Set
labelon every spinner that stands alone. - Keep its color at least 3:1 against the background.