Components · Feedback
Spin
A brand-coloured spinner for an indeterminate wait — loading a page, a panel, or results.
Examples
Size
Tip
Indeterminate wait · small · default · large + tip · wrapper overlay
Live results
Votes are being tallied for the current poll.
<!-- Paste-and-run: save as .html and open in a browser. No build step.
<aha-spin> is the SAME shared custom element React and Vue consume — here in its
native form. Theming comes only from the --aha-* tokens in tokens.css. -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/ahaslides-product/ahaslides-design@master/lib/tokens.css">
<script type="module">
import 'https://cdn.jsdelivr.net/gh/ahaslides-product/ahaslides-design@master/lib/aha-spin.js'; // registers <aha-spin>
</script>
<div style="display:flex; gap:32px; align-items:center">
<aha-spin size="small"></aha-spin>
<aha-spin></aha-spin>
<aha-spin size="large" tip="Loading results…"></aha-spin>
</div>
<!-- Wrapper mode — overlay a spinner over content while it loads.
Slot the content; toggle `spinning` to show/hide the dimmed scrim + centred ring. -->
<aha-spin spinning tip="Loading…" style="margin-top:24px">
<div style="width:240px; padding:16px; border:1px solid var(--aha-border); border-radius:8px">
Live results — votes are being tallied.
</div>
</aha-spin>
import '@ahaslides-product/design/aha-spin'; // registers <aha-spin>
// Attributes only — no events — so React 18/19 can render the element directly.
// `spinning` is a boolean attribute: pass it (any value) to show, omit to hide.
function AhaSpin({ size, tip, spinning, children }) {
return <aha-spin size={size} tip={tip} {...(spinning ? { spinning: '' } : {})}>{children}</aha-spin>;
}
// Standalone — a bare waiting region
{loading ? <AhaSpin tip="Loading results…" /> : <Results data={data} />}
// Wrapper — keep the content mounted, overlay a scrim while it loads
<AhaSpin spinning={loading} tip="Loading…">
<ResultsPanel data={data} />
</AhaSpin>
// main.ts — register the element + mark aha-* as custom elements
import '@ahaslides-product/design/aha-spin';
app.config.compilerOptions.isCustomElement = (tag) => tag.startsWith('aha-');
// Component.vue
<script setup>
import { ref } from 'vue';
const loading = ref(true);
</script>
<template>
<!-- Standalone -->
<aha-spin v-if="loading" size="large" tip="Loading results…" />
<!-- Wrapper — slot content, toggle `spinning` (boolean attr) to overlay the scrim -->
<aha-spin :spinning="loading || null" tip="Loading…">
<ResultsPanel :data="data" />
</aha-spin>
</template>
API
| Prop | Type | Default | Notes |
|---|---|---|---|
size | small | default | large | default | Spinner diameter |
tip | string | — | Optional caption shown under the ring |
spinning | boolean | false | Wrapper mode only (with slotted children): show/hide the overlay scrim + centred ring. Toggling it flips a class on a persistent node — the child subtree is never rebuilt. |
Install
# .npmrc — once: point the @ahaslides-product scope at GitHub Packages
@ahaslides-product:registry=https://npm.pkg.github.com
//npm.pkg.github.com/:_authToken=${GITHUB_TOKEN} # a GitHub token with read:packages
npm i @ahaslides-product/design
import '@ahaslides-product/design/tokens.css'; // once, at the app root
import '@ahaslides-product/design/aha-spin'; // registers <aha-spin>
Agent feed for this component (absolute, fetchable anywhere): spin.agent.json · spin.md · spin.llms.txt
When to use
When to use
- Spin — an indeterminate wait with no measurable percentage
- Progress — you can measure completion (a percentage)
- Skeleton — you want to hold the shape of the content that is loading
Reach for Spin only when you cannot show a percentage or hold the layout. Keep one spinner per waiting region; pair it with a short tip for waits over ~1s. To block a region while it loads, wrap the content and toggle `spinning` rather than swapping it for a bare centred spinner — the layout holds and the child stays mounted.
Surfaces
editor dashboard audience
Spec
Ring 20×20 · 2px · track purple-20, head primary #6A1EBB · Sizes small 14 · default 20 · large 32 · Motion rotate 0.9s linear infinite (respects reduced-motion); scrim fade + blur via motion-mid · Tip optional caption below (standalone) or under the centred ring (wrapper), text-secondary · Wrapper slot children; when spinning, scrim = bg-container @60% over blurred child, ring centred