-
Notifications
You must be signed in to change notification settings - Fork 41
example spinner
<os-spinner> is a self-contained, animated WordPress-mark loading indicator with five curated presets and full per-attribute overrides. CSS variables drive both the disc color and the W-mark accent so the spinner matches any theme.
Four of the presets are re-tunings of the same mark-and-rings artwork. The fifth, inline, is a different indicator for a different job — see Inline: spinners that sit beside text.
Status: Experimental.
<os-spinner></os-spinner> <!-- classic, 48px, WP blue -->
<os-spinner preset="comet" size="80"></os-spinner>
<os-spinner preset="orbit" color="#0f4c6b"></os-spinner>
<os-spinner preset="pulse" accent="#fff8e7"></os-spinner>
<os-spinner preset="inline"></os-spinner> <!-- 16px bare arc, currentColor -->| Preset | Look |
|---|---|
classic (default) |
Three concentric arcs, no dots, no pulse — the canonical WordPress loader. |
comet |
Long arcs + 5 trailing dots all spinning the same way. Reads as a comet trail. |
orbit |
Half-rings counter-rotating with an opacity breathe. Reads as a planetary orbit. |
pulse |
Short arcs + 8 dots + scale + opacity pulse. Reads as a heartbeat. |
inline |
One track ring, one rotating arc. No WordPress mark, no concentric rings, no dots. Defaults to 16px and currentColor. |
Pick one and stop:
<os-spinner preset="comet"></os-spinner>Want to remix? Every knob from the prototype is overridable on the same element. Attribute-specified values win over the preset's defaults:
<os-spinner preset="comet" sp1="6" dots="8"></os-spinner>The other four presets share one piece of artwork: a filled disc carrying the four-path WordPress "W", ringed by three concentric arcs and optionally a fourth ring of dots. It is built for a viewBox roughly 150 units across, and it needs about 40px of real estate to be recognisable. Below that, every stroke lands under a physical pixel and the whole thing greys out into a smudge — the user sees something moved, not the site is working.
inline is the answer for the other case: a spinner that has to live beside a line of text — inside a button, at the head of a list row, next to a status line.
<os-spinner preset="inline"></os-spinner>
<os-spinner preset="inline" size="14" label="Thinking"></os-spinner>Two differences beyond the artwork, both deliberate:
- It defaults to 16px, not 48px. An inline spinner that has to be told its own size every time is a footgun.
-
It inherits
currentColorinstead of--wp-admin-theme-color. It belongs to the text it interrupts, so it tints itself from that text and can never lose contrast against a surface the component knows nothing about — a glass widget card over an arbitrary wallpaper, say. Passcolorto override.
The tempo and arc-length knobs still apply, so you can slow it down or lengthen the arc like any other preset:
<os-spinner preset="inline" sp1="14" a1="35"></os-spinner>Everything else (dots, gap, pulse, sp2/sp3, a2/a3, accent) is inert here — there is no disc, no mark, and no second or third ring for them to act on.
Two colors, both CSS-variable-driven:
| Variable | Default | Drives |
|---|---|---|
--os-ui-spinner-color |
var(--wp-admin-theme-color, #21759b) |
Disc + ring + dot color |
--os-ui-spinner-accent |
var(--os-ui-fg-on-accent, #fff) — the shell palette resolves this to #fffbff
|
The W mark inside the disc |
--os-ui-spinner-size |
48px |
Host width/height |
Set them via attribute shortcuts (HTML-friendly) or via CSS directly (themeable):
<os-spinner color="#1a5f85" accent="#fff8e7" size="80"></os-spinner>/* Theme override — works without touching markup */
os-spinner.brand {
--os-ui-spinner-color: #6f42c1;
--os-ui-spinner-accent: #ffe;
--os-ui-spinner-size: 64px;
}The accent (W mark) defaults to white because the canonical WP loader is white-on-blue, but it's a real CSS variable — set it to anything for dark-on-light marks, themed brands, or accessibility-driven contrast tweaks.
size accepts a bare number (treated as px) or any CSS length:
<os-spinner size="32"></os-spinner> <!-- 32px -->
<os-spinner size="2em"></os-spinner> <!-- 2em — scales with font-size -->
<os-spinner size="clamp(40px, 6vw, 96px)"></os-spinner>| Attribute | Type | What it does |
|---|---|---|
preset |
"classic" | "comet" | "orbit" | "pulse" | "inline" |
Visual personality. Default classic. |
size |
integer (px) or CSS length | Sets --os-ui-spinner-size. Default 48 — 16 under preset="inline". |
color |
CSS color | Sets --os-ui-spinner-color. |
accent |
CSS color | Sets --os-ui-spinner-accent (the W). |
sp1, sp2, sp3
|
integer (deciseconds) | Per-ring rotation duration; 12 → 1.2s. |
a1, a2, a3
|
integer (0–100) | Per-ring arc length as % of circumference. |
gap |
integer | Gap between concentric rings. |
dir2, dir3
|
"1" | "-1" | "cw" | "ccw" |
Per-ring direction; ring 1 is always CW. |
pulse |
"none" | "scale" | "opacity" | "both" |
Pulse animation on the disc + W mark. |
dots |
integer | Outer trailing dot count. Sensible: 0, 3, 5, 8. |
label |
string | Accessible name. Default "Loading". |
The component renders an <svg role="img" aria-label="Loading">. Customize the label whenever the spinner has a more specific meaning:
<os-spinner label="Saving changes"></os-spinner>
<os-spinner label="Uploading 3 files"></os-spinner>prefers-reduced-motion: reduce disables every animation inside the SVG; the mark + rings still render, just statically.
Inline with text — the host is display: inline-block; vertical-align: middle:
<button disabled>
<os-spinner size="16"></os-spinner>
Saving…
</button>Centered overlay — combine with <os-empty-state> or any container:
<div class="loading-overlay">
<os-spinner preset="orbit" size="120"></os-spinner>
</div>Programmatic preset switching — the component re-paints on attribute change:
spinner.setAttribute( 'preset', isError ? 'pulse' : 'classic' );Need the preset config in JS (e.g. to render a "preset picker" UI)? A frozen record of every config, OS_SPINNER_PRESETS, is exported by the component module — but only through the internal components barrel (src/ui/components/index.ts), not from the openstation package entry, so it is importable by code built inside this repo only:
// In-tree code only — the `openstation` package does not (yet) re-export it.
import { OS_SPINNER_PRESETS, type OsSpinnerPreset } from '../ui/components';
const names: OsSpinnerPreset[] = Object.keys( OS_SPINNER_PRESETS ) as OsSpinnerPreset[];
console.log( OS_SPINNER_PRESETS.comet.dots ); // 5External plugins should treat the preset names in the table above as the contract and set them via the preset attribute.
This wiki is generated from the docs/ directory — edits made here are overwritten by the next sync.
To change a page, open a pull request against docs/.
Guides
- Development guide
- Releasing openstation
- Agents security model
- API Index
- Architecture
- Bridge protocol — wiring overview
- <os-*> component reference
- Native Desktop Host — Experimental
- Desktop themes
- Dock customization — two registries, one mental model
- The event-driven framework
- Files on the Desktop
- Folder sharing
- Getting Started
- Hooks Reference
- Icons
- JavaScript Reference
- The Living Tree — algorithm definition
- Mio
- Native Windows & Framework Interop
- Plugin compatibility layer
- Progressive Web App (PWA)
- Station Home
- Using openstation from your own plugin
Migration notes
- Migration: built-in activity channels move to the os/ namespace
- Migration: window, wallpaper and widget bundles load on demand
- Migration — the navigation model
- Migration: a native window's tabs move to the window chrome
All examples
- AI Agents — extend and invoke from a plugin
- wp.os.ai.ask() — programmatic AI Copilot
- Tune the AI model config
- Custom arrange-menu action
- Open a child window its owner can't cover
- Style a specific admin page inside the iframe
- Code Blue — register your plugin's log file
- Open a file in the Code editor (deep-link from any window)
- Connect to a window — title-bar button + iframe pub/sub
- Content changes — live-refresh every window listing your type
- Custom window chrome (Experimental)
- Register a custom unfocused-window effect
- Example: render a data table
- Real file storage — react to uploads, gate policy, share from PHP
- React to a window being set free onto the real desktop
- Cross-window devtools — instrumentation primitives
- Add a dock item with a badge
- Decorate the dock without forking the renderer
- Replace the dock rail entirely
- Retune the Drafts widget's AI writing assistant
- Gate OpenStation by role
- Iframe-initiated window opens
- Build a feed reader without the bookkeeping
- Inject data into openStationConfig
- Render a list without losing clicks — renderKeyedList()
- Example: layout primitives (body → panel → row → col)
- Use <os-*> components from a plugin that ships as a zip
- Restyle and drive Mio
- Add an action that works on a whole selection
- WP Explorer — custom post types and their folder
- Add an action button to a WP Explorer preview pane
- Example: native Posts window
- Example: native window with tabs
- Native windows
- Customize note → post conversion
- Send a notification
- OAuth relay — connect to an external service
- OS-file drop
- <os-flyout> — window-scoped sliding card
- Plugins window — extras
- Track who's around — wp.os.presence
- Example: progress bar
- PWA install — surface your own button
- React to window events
- Example: extend the Trash
- Register a slash-command
- Register a desktop theme from a plugin
- Register a game
- Example: register a desktop icon (Jorvy)
- Register a wallpaper
- Register a widget
- Related entities — extend the title bar's "Related" menu
- The native-window render ctx
- Programmatic folder sharing
- Share state across multi-bundle plugins — wp.os.createSharedStore()
- Example: loading spinner
- Add an opt-in card to Station Home
- Accept drops on your desktop icon
- Give a tile two icons, one per state
- Add a row to a window's ⋯ menu
- Example: window activity & the status ring
- Window controls
- Subscribe to window lifecycle events
- Window links — relate windows and restyle the ties (Experimental)
- Window loading state — spinner overlay & ready signal
- Show a banner at the top of a window
- Pulse a window's icon — Window.requestAttention()
- Register a custom window reveal
- Window slots
- Window themes
- Native window with bundle-bound config