Component Contract
Spinner
Loading indicator for asynchronous operations across the CAAT Pension Plan member experience.
Live Demo
Border Spinner
Rendered
Small · Default · Large
Growing Spinner
Rendered
Small · Default · Large
Spinner with Label
Rendered
Button with Spinner
Rendered
1. Component Summary
| Name | Spinner |
| AEM resource type | caat/components/spinner |
| Purpose | Provides visual feedback during asynchronous operations — data fetches, form submissions, and page transitions — so members know the system is working. |
| Variants | Border (default), Growing, With Label, Button Integration |
| Sizes | Small (--sm, 1 rem), Default (2 rem), Large (--lg, 3 rem) |
| Status | New |
2. Design Tokens
| Token | Role | Value (reference) |
|---|---|---|
--caat-blue-900 | Default spinner colour | #003750 |
--caat-blue-700 | Hover / active tint | #0b5a80 |
--caat-blue-100 | Spinner track (light) | #e7f4fb |
--caat-green | Success state colour | #55a546 |
--caat-grey-200 | Reduced-motion fallback border | #dfe6ef |
--caat-focus | Focus ring colour (button integration) | #1A73E8 |
--caat-ink | Label text colour | #102637 |
--caat-radius | Border radius for button wrapper | .75rem |
--caat-font-primary | Label font family | Libre Franklin |
3. Authoring Fields
| Field | Type | Required | Notes |
|---|---|---|---|
| Variant | Select | Yes | Options: border (default), grow |
| Size | Select | No | Options: sm, default, lg. Defaults to default. |
| Accessible label | Text | Yes | Screen-reader text, e.g. "Loading pension details". |
| Visible label | Text | No | If provided, renders beside the spinner as .caat-spinner__label. |
| Colour override | Select | No | Options: default, light. Light uses --caat-blue-100 for dark backgrounds. |
4. Validation Rules
No author-managed content beyond an accessible label, so there are no authoring validation rules. Accessibility requirements are covered in Section 8 (Accessibility).
5. Content Guidance
- Always provide an accessible label — even if the spinner is purely decorative in a button, the button's own label must describe the loading state (e.g. "Processing…").
- Be specific — prefer "Loading your pension details…" over a generic "Loading…" so members know what to expect.
- Keep labels concise — aim for fewer than 40 characters for visible labels.
- Use spinners sparingly — if content loads in under 300 ms a spinner adds visual noise rather than reassurance. Defer rendering until a short delay has elapsed.
- Pair with disabled controls — when a spinner indicates a form submission, disable the submit button to prevent duplicate requests.
6. Semantic HTML
Border spinner (default)
<div class="caat-spinner" role="status">
<span class="visually-hidden">Loading…</span>
</div>
Growing spinner — large
<div class="caat-spinner caat-spinner--grow caat-spinner--lg" role="status">
<span class="visually-hidden">Loading…</span>
</div>
Spinner with visible label
<div class="caat-spinner__label" role="status">
<div class="caat-spinner caat-spinner--sm" aria-hidden="true"></div>
Loading your pension details…
</div>
Button with spinner
<button class="caat-button caat-button--primary" type="button" disabled>
<span class="caat-spinner" aria-hidden="true"></span>
Processing…
</button>
7. CSS Contract
| Selector | Purpose |
|---|---|
.caat-spinner | Base spinning ring — 2 rem, --caat-blue-900 border, 0.75 s rotation. |
.caat-spinner--sm | Small variant — 1 rem. |
.caat-spinner--lg | Large variant — 3 rem. |
.caat-spinner--grow | Growing (pulse) variant — uses scale animation instead of rotation. |
.caat-spinner__label | Wrapper that places visible text inline beside the spinner. |
Reduced motion: When prefers-reduced-motion: reduce is active, all animation stops and the spinner displays a static indicator with reduced opacity.
8. Accessibility
| Requirement | Implementation |
|---|---|
| ARIA role | role="status" on the spinner or its label wrapper so assistive technology announces the loading state as a live region. |
| Screen-reader text | A .visually-hidden <span> inside standalone spinners provides a text alternative. |
| Button integration | Spinner inside a button uses aria-hidden="true"; the button's own text ("Processing…") serves as the accessible label. |
| Reduced motion | @media (prefers-reduced-motion: reduce) stops all animation and shows a static visual indicator. |
| Colour contrast | Spinner border colour meets WCAG 2.1 AA non-text contrast (3 : 1) against white and light backgrounds. |
9. SEO Considerations
Transient / structural UI with no indexable content of its own — no SEO markup required. Ensure any content this component wraps or reveals stays server-rendered and crawlable.
10. Analytics
Analytics are captured through the shared data-analytics-component attribute on the component root (emitted automatically) and reported to the central data layer. Event names and payloads follow the organisation's standard analytics schema — defined centrally with the analytics team, not invented per component — so no bespoke tracking is specified here.
11. AEM Implementation Notes
- Proxy component — create at
/apps/caat/components/spinner. - HTL template — emit a
<div>with BEM classes driven by dialog selections. Usedata-sly-attributeforrole="status". - Client library — category
caat.components.spinner. Includespinner.cssonly; no JS dependency for CSS-only animation. - Sling model — expose
variant,size,accessibleLabel,visibleLabel, andcolourOverride. - Allowed parents — may be embedded inside Button, Card, Modal, and any container component.
- No JavaScript required — spinner animation is pure CSS. Toggle visibility via
hiddenattribute or class in consuming component JS.
12. Dialog Model (JSON)
Not authored directly. The spinner is a sub-element emitted and configured by its host component (or generated from page structure), so it has no standalone cq:dialog — any options are exposed through the host component's dialog.
13. QA Acceptance Checklist
- Border spinner renders a smooth rotating ring in
--caat-blue-900. - All three sizes (sm, default, lg) render at correct dimensions (1 rem, 2 rem, 3 rem).
- Growing spinner pulses with scale animation.
- Visible label sits inline beside the spinner with correct alignment.
- Button-with-spinner variant shows spinner + "Processing…" text while disabled.
-
role="status"is announced by NVDA and VoiceOver. -
.visually-hiddenlabel is read by screen readers for standalone spinners. - Animation stops when
prefers-reduced-motion: reduceis active; static indicator is visible. - Spinner colour meets WCAG 2.1 AA non-text contrast (3 : 1) against white.
- Analytics events (
spinner-shown,spinner-duration) fire correctly. - Cross-browser tested: Chrome, Firefox, Safari, Edge.
14. Definition of Done
| Area | Done means |
|---|---|
| Design | Figma component matches this contract. Border and growing variants documented with all sizes. Reduced-motion fallback included. |
| Development | AEM proxy component created. HTL template emits semantic HTML per Section 6. CSS uses design tokens exclusively. role="status" and visually-hidden labels wired. No JS dependency. |
| QA | All items in the QA Acceptance Checklist (Section 13) pass. Manual accessibility audit completed. Cross-browser tested (Chrome, Firefox, Safari, Edge). |
| Launch | Component deployed to AEM production. Content authors trained on accessible-label requirements. Documentation published. Analytics dashboards confirmed receiving events. |