Skip to content

feat(react-spinner): migrate Spinner animations to motion components#35882

Draft
robertpenner wants to merge 22 commits intomicrosoft:masterfrom
robertpenner:refactor/spinner-motion
Draft

feat(react-spinner): migrate Spinner animations to motion components#35882
robertpenner wants to merge 22 commits intomicrosoft:masterfrom
robertpenner:refactor/spinner-motion

Conversation

@robertpenner
Copy link
Copy Markdown
Collaborator

@robertpenner robertpenner commented Mar 19, 2026

feat(react-spinner): migrate Spinner from CSS to motion components

Previous Behavior

  • The Spinner's rotation, tail sweep, and arc expand/collapse animations were driven by CSS @keyframes with transform: rotate().
  • The tail's two arc segments were rendered via ::before/::after pseudo-elements with animation: inherit.
  • Reduced motion was handled via @media (prefers-reduced-motion: reduce) with animation-iteration-count: 0 and a static arc fallback.

New Behavior

  • All Spinner animations are now powered by custom motion components using the Web Animations API (WAAPI), enabling compositor-thread animation.
  • The ::before/::after pseudo-elements are replaced by explicit <span> elements, each wrapped by its own motion component.
  • Reduced motion is handled via each motion component's reducedMotion option.
  • Four new motion slots are exposed — rotationMotion, tailMotion, arcStartMotion, and arcEndMotion — allowing consumers to customize, replace, or disable (pass null) each part of the animation independently.

Code Changes

Motion Migration

  • Created spinnerMotions.ts with four motion components: SpinnerRotation, SpinnerTailMotion, SpinnerArcStartMotion, and SpinnerArcEndMotion.
  • Updated renderSpinner.tsx to wrap spinner elements with motion components, falling back to React.Fragment when a motion slot is undefined (disabled by the user).
  • Replaced ::before/::after pseudo-elements with explicit <span> elements for the arc segments, with shared styles in a new useSpinnerTailArcBaseClassName.
  • Removed CSS @keyframes animations and related styles from useSpinnerStyles.styles.ts.
  • Added tailArcClassName and tailArcRtlClassName to SpinnerState for arc element styling.
  • Added @fluentui/react-motion as a dependency.

Motion Slots

  • Added rotationMotion, tailMotion, arcStartMotion, and arcEndMotion slots to SpinnerSlots, resolved via motionSlot() in useSpinnerBase_unstable.
  • Excluded motion slots from spinnerClassNames since they are not DOM elements.
  • Added a MotionCustom story demonstrating how to override all four motion slots with slower, "dreamy" variants.

TODO

  • Add a MotionCustom example for Spinner in Storybook.

Testing

  • Tested reduced motion by toggling animations via OS setting and DevTools' Rendering tab.
  • The new motion slots are demonstrated in the MotionCustom Storybook story.

@github-actions
Copy link
Copy Markdown

github-actions bot commented Mar 19, 2026

📊 Bundle size report

Package & Exports Baseline (minified/GZIP) PR Change
react-components
react-components: entire library
1.299 MB
324.701 kB
1.298 MB
324.771 kB
-1.334 kB
70 B
react-spinner
Spinner
23.596 kB
7.838 kB
27.057 kB
9.531 kB
3.461 kB
1.693 kB
Unchanged fixtures
Package & Exports Size (minified/GZIP)
react-accordion
Accordion (including children components)
103.484 kB
31.344 kB
react-avatar
Avatar
48.479 kB
15.359 kB
react-avatar
AvatarGroup
17.482 kB
7.002 kB
react-avatar
AvatarGroupItem
61.867 kB
19.384 kB
react-breadcrumb
@fluentui/react-breadcrumb - package
115.355 kB
31.542 kB
react-charts
AreaChart
412.454 kB
126.449 kB
react-charts
DeclarativeChart
763.178 kB
220.492 kB
react-charts
DonutChart
322.883 kB
97.008 kB
react-charts
FunnelChart
314.436 kB
94.065 kB
react-charts
GanttChart
395.573 kB
119.965 kB
react-charts
GaugeChart
322.314 kB
96.46 kB
react-charts
GroupedVerticalBarChart
403.443 kB
122.537 kB
react-charts
HeatMapChart
397.644 kB
121.826 kB
react-charts
HorizontalBarChart
302.61 kB
89.151 kB
react-charts
HorizontalBarChartWithAxis
63 B
83 B
react-charts
Legends
242.551 kB
71.624 kB
react-charts
LineChart
423.795 kB
128.511 kB
react-charts
PolarChart
351.496 kB
107.404 kB
react-charts
SankeyChart
220.386 kB
67.867 kB
react-charts
ScatterChart
403.17 kB
122.639 kB
react-charts
Sparkline
91.393 kB
28.708 kB
react-charts
VerticalBarChart
439.914 kB
128.239 kB
react-charts
VerticalStackedBarChart
409.439 kB
124.042 kB
react-components
react-components: Button, FluentProvider & webLightTheme
70.397 kB
19.96 kB
react-components
react-components: Accordion, Button, FluentProvider, Image, Menu, Popover
236.743 kB
68.75 kB
react-components
react-components: FluentProvider & webLightTheme
43.612 kB
14.022 kB
react-datepicker-compat
DatePicker Compat
225.158 kB
63.633 kB
react-dialog
Dialog (including children components)
102.117 kB
30.367 kB
react-menu
Menu (including children components)
170.393 kB
51.915 kB
react-menu
Menu (including selectable components)
173.571 kB
52.494 kB
react-message-bar
MessageBar (all components)
23.294 kB
8.63 kB
react-motion
@fluentui/react-motion - createMotionComponent()
4.156 kB
1.818 kB
react-motion
@fluentui/react-motion - createPresenceComponent()
5.908 kB
2.442 kB
react-motion
@fluentui/react-motion - PresenceGroup
1.727 kB
823 B
react-overflow
hooks only
12.117 kB
4.627 kB
react-persona
Persona
55.434 kB
17.299 kB
react-popover
Popover
133.719 kB
41.451 kB
react-portal-compat
PortalCompatProvider
8.386 kB
2.624 kB
react-progress
ProgressBar
20.167 kB
7.855 kB
react-table
DataGrid
159.783 kB
45.012 kB
react-table
Table (Primitives only)
40.997 kB
13.172 kB
react-table
Table as DataGrid
131.005 kB
36.012 kB
react-table
Table (Selection only)
69.391 kB
19.404 kB
react-table
Table (Sort only)
68.034 kB
19.022 kB
react-tag-picker
@fluentui/react-tag-picker - package
186.766 kB
55.907 kB
react-tags
InteractionTag
13.724 kB
5.47 kB
react-tags
Tag
29.648 kB
9.429 kB
react-tags
TagGroup
82.247 kB
24.152 kB
react-teaching-popover
TeachingPopover
112.414 kB
34.219 kB
react-timepicker-compat
TimePicker
108.565 kB
35.848 kB
react-toast
Toast (including Toaster)
102.56 kB
30.608 kB
react-tree
FlatTree
148.099 kB
42.211 kB
react-tree
PersonaFlatTree
149.927 kB
42.585 kB
react-tree
PersonaTree
145.987 kB
41.411 kB
react-tree
Tree
144.165 kB
41.037 kB
🤖 This report was generated against fc37669010c08b57352656569c789974ff61b990

@github-actions
Copy link
Copy Markdown

Pull request demo site: URL

@robertpenner robertpenner force-pushed the refactor/spinner-motion branch from 60025fb to 4e6d1f8 Compare March 28, 2026 00:54
@robertpenner robertpenner force-pushed the refactor/spinner-motion branch from 4e6d1f8 to 390539b Compare April 6, 2026 10:36
Add rotationMotion, tailMotion, arcStartMotion, and arcEndMotion slots
to SpinnerSlots, allowing consumers to override or disable individual
animation segments. The render function now uses these slots instead of
hardcoded motion components. Motion slots are initialized via motionSlot()
in useSpinnerBase_unstable and excluded from spinnerClassNames.
…cMotion/trailArcMotion

Rename motion slots to better describe their role in the animation:
- arcStartMotion → leadArcMotion (leading arc segment)
- arcEndMotion → trailArcMotion (trailing arc segment)
- SpinnerArcStartMotion → SpinnerLeadArcMotion
- SpinnerArcEndMotion → SpinnerTrailArcMotion

Updates types, implementation, styles, tests, API docs, and stories.
Add an exploded-view story that progressively reveals the Spinner's
internal structure — ring track, arc segments, tail mask — then
demonstrates each motion slot in isolation with color coding before
showing the fully composed result.
Rename SpinnerMotionExploded.stories.tsx → SpinnerSlotsBreakdown.stories.tsx
and update the story export accordingly.
@robertpenner robertpenner force-pushed the refactor/spinner-motion branch from 6602f2d to 0b7c30b Compare April 6, 2026 18:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant