# yaml-language-server: $schema=https://opencomponents.dev/schemas/contract.json
# The Spinner contract, with every rule from its checklist.
# The page explains why each one exists: https://opencomponents.dev/raw/docs/components/spinner.md

component: Spinner
summary: Shows that something is busy, for a wait of unknown length. Visual only.
standard: https://opencomponents.dev/docs/components/spinner
element: svg # aria-hidden="true", with no role or name: whatever shows it says what's loading
props: {} # none: it takes its size and colour from the text around it
parts: [track, indicator] # data-slot, in this order
tokens:
  theme: [] # none: it's drawn in currentColor
  variables: [--spinner--size] # 1em by default
  layer: components # in Tailwind's order: theme, base, components, utilities
rules:
  - id: spinner/em-sized
    layer: ui
    level: should
    scope: component
    requirement: "It's `1em` by default, so it matches the text around it, and `--spinner--size` sets any other size"
    basis: "Open Components"
    check: "Review"
  - id: spinner/current-color
    layer: ui
    level: must
    scope: component
    requirement: "It's drawn in `currentColor`, so it follows the text around it, every colour scheme and forced colours mode"
    basis: "Open Components"
    check: "Emulation"
  - id: spinner/contrast
    layer: ui
    level: must
    scope: both
    requirement: "The indicator has a contrast of 3:1 against what's behind it"
    basis: "WCAG 1.4.11 (AA)"
    check: "Contrast checker"
  - id: spinner/reduced-motion
    layer: ui
    level: must
    scope: component
    requirement: "It doesn't turn when `prefers-reduced-motion: reduce` is set, but still shows that something's happening, like with a pulse"
    basis: "WCAG 2.3.3 (AAA), Open Components"
    check: "Emulation"
  - id: spinner/stable-layout
    layer: ui
    level: must
    scope: both
    requirement: "It keeps a fixed size while it turns, and showing or hiding it doesn't move anything around it"
    basis: "Open Components"
    check: "Visual regression test"
  - id: spinner/decorative
    layer: ux
    level: must
    scope: component
    requirement: "It's hidden from assistive technologies with `aria-hidden`, and has no role or name of its own"
    basis: "WCAG 1.1.1 (A)"
    check: "Unit test"
  - id: spinner/say-what-loads
    layer: ux
    level: must
    scope: usage
    requirement: "Whatever shows it says what's loading, in a status message that's already in the page or with a label it announces, like the Button's `loadingLabel`"
    basis: "WCAG 4.1.3 (AA)"
    check: "ARIA snapshot, screen reader"
  - id: spinner/announce-outcome
    layer: ux
    level: should
    scope: usage
    requirement: "When the wait ends, the page says how it went, in the same status message, or in an alert for an error that needs attention straight away"
    basis: "Open Components, beyond WCAG 4.1.3 (AA)"
    check: "Screen reader"
  - id: spinner/keep-focus
    layer: ux
    level: must
    scope: usage
    requirement: "Showing a spinner never takes focus away, so it never replaces the element that has focus"
    basis: "Open Components"
    check: "Keyboard"
  - id: spinner/remove-when-done
    layer: ux
    level: must
    scope: usage
    requirement: "It's taken out as soon as the wait ends or fails, so it never turns with nothing to wait for"
    basis: "Open Components, beyond WCAG 2.2.2 (A)"
    check: "Review"
  - id: spinner/unknown-waits
    layer: ux
    level: should
    scope: usage
    requirement: "It's for waits of a few seconds and of unknown length, while a progress bar shows longer or measurable ones"
    basis: "Open Components"
    check: "Review"
  - id: spinner/one-per-wait
    layer: ux
    level: should
    scope: usage
    requirement: "There's one spinner for each thing that's loading, shown where it's loading"
    basis: "Open Components"
    check: "Review"
  - id: spinner/root-element
    layer: dx
    level: must
    scope: component
    requirement: "The root element is the `<svg>` itself, so classes and attributes like `data-slot` land on it"
    basis: "Open Components"
    check: "Unit test"
  - id: spinner/stateless
    layer: dx
    level: should
    scope: component
    requirement: "It has no props or state of its own, and it shows for as long as it's rendered"
    basis: "Open Components"
    check: "Review"
  - id: spinner/one-implementation
    layer: dx
    level: should
    scope: usage
    requirement: "Components that show a spinner render the Spinner, rather than drawing one of their own"
    basis: "Open Components"
    check: "Review"
  - id: spinner/deterministic-dom
    layer: ax
    level: must
    scope: component
    requirement: "It always renders the same markup, with nothing generated, like an `id`"
    basis: "Open Components"
    check: "Unit test"
  - id: spinner/data-attributes
    layer: ax
    level: should
    scope: component
    requirement: "Its parts are marked with `data-slot`, in a fixed order"
    basis: "Open Components"
    check: "Unit test"
