diff --git a/.changeset/afraid-buckets-build.md b/.changeset/afraid-buckets-build.md
new file mode 100644
index 00000000000..a58e16be291
--- /dev/null
+++ b/.changeset/afraid-buckets-build.md
@@ -0,0 +1,5 @@
+---
+'@primer/react': minor
+---
+
+Add feature flag to control whether Spinner animations are synchronized
diff --git a/packages/react/src/FeatureFlags/DefaultFeatureFlags.ts b/packages/react/src/FeatureFlags/DefaultFeatureFlags.ts
index a7f8d2efe39..d5c1cf6ab92 100644
--- a/packages/react/src/FeatureFlags/DefaultFeatureFlags.ts
+++ b/packages/react/src/FeatureFlags/DefaultFeatureFlags.ts
@@ -7,4 +7,5 @@ export const DefaultFeatureFlags = FeatureFlagScope.create({
primer_react_select_panel_fullscreen_on_narrow: false,
primer_react_select_panel_order_selected_at_top: false,
primer_react_select_panel_remove_active_descendant: false,
+ primer_react_spinner_synchronize_animations: false,
})
diff --git a/packages/react/src/Spinner/Spinner.examples.stories.tsx b/packages/react/src/Spinner/Spinner.examples.stories.tsx
index 2b43b526711..e0f6fe7fcdd 100644
--- a/packages/react/src/Spinner/Spinner.examples.stories.tsx
+++ b/packages/react/src/Spinner/Spinner.examples.stories.tsx
@@ -1,4 +1,4 @@
-import React from 'react'
+import React, {useEffect, useState} from 'react'
import type {Meta} from '@storybook/react-vite'
import Spinner from './Spinner'
import {Button} from '..'
@@ -95,3 +95,32 @@ export const FullLifecycleVisibleLoadingText = () => {
)
}
+
+export const SynchronizedSpinners = () => (
+ <>
+
+
+
+
+
+
+
+
+
+
+
+
+
+ >
+)
+
+function Delay({children, ms}: {children: React.ReactNode; ms: number}) {
+ const [show, setShow] = useState(false)
+
+ useEffect(() => {
+ const timeout = setTimeout(() => setShow(true), ms)
+ return () => clearTimeout(timeout)
+ }, [ms])
+
+ return show ? <>{children}> : null
+}
diff --git a/packages/react/src/Spinner/Spinner.tsx b/packages/react/src/Spinner/Spinner.tsx
index fffb57684c8..73945964036 100644
--- a/packages/react/src/Spinner/Spinner.tsx
+++ b/packages/react/src/Spinner/Spinner.tsx
@@ -1,10 +1,12 @@
+import {clsx} from 'clsx'
import type React from 'react'
-import {useState, useEffect} from 'react'
+import {useCallback, useEffect, useRef, useState, useSyncExternalStore} from 'react'
import {VisuallyHidden} from '../VisuallyHidden'
import type {HTMLDataAttributes} from '../internal/internal-types'
import {useId} from '../hooks'
import classes from './Spinner.module.css'
-import {clsx} from 'clsx'
+import {useMedia} from '../hooks/useMedia'
+import {useFeatureFlag} from '../FeatureFlags'
const sizeMap = {
small: '16px',
@@ -34,6 +36,8 @@ function Spinner({
delay = false,
...props
}: SpinnerProps) {
+ const syncAnimationsEnabled = useFeatureFlag('primer_react_spinner_synchronize_animations')
+ const animationRef = useSpinnerAnimation()
const size = sizeMap[sizeKey]
const hasHiddenLabel = srText !== null && ariaLabel === undefined
const labelId = useId()
@@ -58,6 +62,7 @@ function Spinner({
/* inline-flex removes the extra line height */