콘텐츠로 이동

Spin (Spinner) — spec

Citing Ant Design Spin, MUI CircularProgress (used as spinner), shadcn-ui (composition)

Purpose

A simple loading indicator. Indeterminate spinner for "something is happening, no progress %." Different from Progress (percentage-based) and Skeleton (content-shape).

When Spin vs Skeleton vs Progress

Use Spin Use Skeleton Use Progress
Async action with no shape (button submit, modal opening) Page/list initial load (you know the shape) Determinate operation (upload %, multi-step)
Brief (< 2s) waiting Longer (1s+) load Any duration with % known

Anatomy

Standalone:                     With overlay:
   ⟳                            ┌─────────────────┐
                                 │   ⟳             │
                                 │   "로딩 중..."  │
                                 │                  │
                                 └─────────────────┘
                                 (content faded behind)
Slot Required Notes
Spinner yes Rotating circular indicator
Tip / label optional "로딩 중..." text
Overlay optional Fades the wrapped content
Children optional Wrapped element (Spin can wrap content to overlay loading)

API

// Standalone
<Spin size="md" />
<Spin tip="로딩 중..." />

// Wrapping content
<Spin spinning={isLoading}>
  <DataTable data={data} />
</Spin>

// Inside a button (rare — buttons have their own loading)
<Button loading><Spin size="sm" /></Button>
Prop Type Default Description
spinning boolean true Whether to show
size "sm" \| "md" \| "lg" "md" Spinner size
tip string — Optional label
delay number 0 Show only after N ms (avoids flash for fast loads)
children ReactNode — Wrapped content (gets overlay)
indicator ReactNode default circular Custom spinner

Sizes

Size Diameter Use
sm 16px Inline (button, badge)
md (default) 24px Standard inline
lg 40px Page-level loading

Animation

Standard spinner: 360° rotation, 1000ms loop, linear easing.

@keyframes spin {
  from { transform: rotate(0deg); }
  to   { transform: rotate(360deg); }
}
.spinner {
  animation: spin 1000ms linear infinite;
}

For Material 3 style: arc length also varies (10–80%) for a "pulsing" feel. More expensive; default static-arc spinner is fine.

States

State Visual
Spinning (active) Animated rotation
Stopped (spinning={false}) Hidden (or unmounted)
Delayed (delay > 0) Hidden until delay elapses

Accessibility

  • Wrap with aria-busy="true" on the loading container.
  • The spinner itself: aria-hidden="true" (decorative).
  • Provide aria-label on the wrapper if tip is missing: "Loading...".
  • Don't announce "Loading" repeatedly via aria-live — single announcement at start of load.
<div aria-busy="true" aria-live="polite" aria-label="데이터 로딩 중">
  <span class="spin" aria-hidden="true">⟳</span>
</div>

For delay > 0: don't show the spinner OR announce until the delay elapses. Avoids flash for fast loads.

Reduced motion

@media (prefers-reduced-motion: reduce) {
  .spinner { animation: none; }
}

When motion disabled: spinner is static. Provide a text label or different non-animated indicator (dots fading, etc.). Don't leave a static rotated icon — looks broken.

Tokens consumed

--color-primary-default      (default spinner color)
--color-text-secondary       (tip text)
--color-bg-overlay           (overlay when wrapping content)
--space-sm
--motion-default              (rotation period; though linear loop ignores easing)

Code example

// Inline spinner
<div>
  <Spin /> 데이터를 불러오는 중...
</div>

// Wrapping a list
<Spin spinning={isLoading} tip="저장하는 중...">
  <Form>
    {/* form content */}
  </Form>
</Spin>

// With delay (avoid flash)
<Spin spinning={isLoading} delay={200} />
{/* Spinner appears only if loading takes > 200ms */}

Edge cases

  • Delay vs no delay: for sub-200ms loads, don't show spinner at all (avoid flicker). Use delay: 200 to gate.
  • Spin wrapping a tall scrollable: overlay covers visible viewport; spinner centered. Don't anchor to top.
  • Multiple Spin on screen: collapse into one spinner if they all represent the same operation.
  • Reduced motion: spinner is static; text "로딩 중..." carries the meaning.
  • Custom indicator (animated logo, etc.): provide via indicator prop. Don't violate reduced-motion preference.

Don't

  • Don't use Spin for known-shape content. Use Skeleton.
  • Don't use Spin for known-percentage operation. Use Progress.
  • Don't show Spin without context. Pair with text or place it where the content will land.
  • Don't auto-show spinner on every interaction. Sub-200ms loads don't need spinner.
  • Don't omit reduced-motion handling.

References

Cross-reference