diff --git a/packages/ui/src/components/spinner.tsx b/packages/ui/src/components/spinner.tsx
new file mode 100644
index 00000000..10d802ab
--- /dev/null
+++ b/packages/ui/src/components/spinner.tsx
@@ -0,0 +1,36 @@
+/**
+ * Spinner - 3x3 grid spinner based on SpinKit Grid
+ *
+ * Features:
+ * - Uses currentColor (inherits text color from parent, typically theme primary)
+ * - Uses em sizing (scales with font-size)
+ * - 3x3 grid of cubes with staggered scale animation
+ * - Pure CSS animation (no JS state)
+ *
+ * Usage:
+ * // Uses primary theme color
+ * // Uses muted color
+ * // Controls size via Tailwind font-size
+ */
+import { cn } from "@multica/ui/lib/utils"
+
+export interface SpinnerProps {
+ /** Additional className for styling (color via text-*, size via Tailwind text-*) */
+ className?: string
+}
+
+export function Spinner({ className }: SpinnerProps) {
+ return (
+
+
+
+
+
+
+
+
+
+
+
+ )
+}
diff --git a/packages/ui/src/styles/globals.css b/packages/ui/src/styles/globals.css
index 3afc7312..dd74c884 100644
--- a/packages/ui/src/styles/globals.css
+++ b/packages/ui/src/styles/globals.css
@@ -141,3 +141,44 @@
@apply bg-background text-foreground;
}
}
+
+/* SPINNER - SpinKit Grid (3x3) */
+.spinner {
+ display: inline-grid;
+ grid-template-columns: repeat(3, 1fr);
+ width: 1em;
+ height: 1em;
+ gap: 0.08em;
+}
+
+.spinner-cube {
+ background-color: currentColor;
+ animation: spinner-grid 1.3s infinite ease-in-out;
+ transform: scale3d(0.5, 0.5, 1);
+}
+
+.spinner-cube:nth-child(1) { animation-delay: 0.2s; }
+.spinner-cube:nth-child(2) { animation-delay: 0.3s; }
+.spinner-cube:nth-child(3) { animation-delay: 0.4s; }
+.spinner-cube:nth-child(4) { animation-delay: 0.1s; }
+.spinner-cube:nth-child(5) { animation-delay: 0.2s; }
+.spinner-cube:nth-child(6) { animation-delay: 0.3s; }
+.spinner-cube:nth-child(7) { animation-delay: 0s; }
+.spinner-cube:nth-child(8) { animation-delay: 0.1s; }
+.spinner-cube:nth-child(9) { animation-delay: 0.2s; }
+
+@keyframes spinner-grid {
+ 0%, 70%, 100% {
+ transform: scale3d(0.5, 0.5, 1);
+ }
+ 35% {
+ transform: scale3d(0, 0, 1);
+ }
+}
+
+@media (prefers-reduced-motion: reduce) {
+ .spinner-cube {
+ animation: none;
+ opacity: 0.7;
+ }
+}