{"$schema":"https://ui.shadcn.com/schema/registry-item.json","name":"spinner","type":"registry:component","title":"Spinner","description":"Something is happening: a ring with one sweeping arc, three rising dots, or eight petals fading round, in currentColor.","author":"Hairline UI","dependencies":[],"files":[{"path":"registry/spinner.tsx","type":"registry:component","content":"// Hairline UI · spinner · MIT License\n// Copyright (c) 2026 Hairline UI · https://hairlineui.com/license#components\n\n\"use client\";\n\nimport * as React from \"react\";\n\n/* ─────────────────────────────────────────────────────────\n * SPINNER: something is happening\n *\n *   ring     a hairline circle with one arc that sweeps round\n *   dots     three dots that rise in turn\n *   bars     eight petals that fade round like a clock face\n *\n * It takes currentColor and says \"Loading\" (or your label) to\n * screen readers. Reduced motion turns the ring into a calm\n * pulse and holds the dots and petals still.\n * ───────────────────────────────────────────────────────── */\n\nexport interface SpinnerProps extends React.HTMLAttributes<HTMLSpanElement> {\n  variant?: \"ring\" | \"dots\" | \"bars\";\n  /** 12, 16, 20 or 28px. */\n  size?: \"xs\" | \"sm\" | \"md\" | \"lg\";\n  label?: string;\n}\n\nconst PX = { xs: 12, sm: 16, md: 20, lg: 28 };\n\nexport function Spinner({ variant = \"ring\", size = \"sm\", label = \"Loading\", className = \"\", style, ...props }: SpinnerProps) {\n  const px = PX[size];\n  return (\n    <span role=\"status\" className={`inline-flex shrink-0 items-center justify-center text-current ${className}`} style={{ width: px, height: px, ...style }} {...props}>\n      <span className=\"sr-only\">{label}</span>\n      {variant === \"ring\" && (\n        <svg viewBox=\"0 0 24 24\" fill=\"none\" aria-hidden=\"true\" className=\"size-full animate-spin [animation-duration:800ms] motion-reduce:animate-pulse\">\n          <circle cx=\"12\" cy=\"12\" r=\"9.5\" stroke=\"currentColor\" strokeOpacity=\"0.18\" strokeWidth=\"2.5\" />\n          <path d=\"M21.5 12a9.5 9.5 0 0 0-9.5-9.5\" stroke=\"currentColor\" strokeWidth=\"2.5\" strokeLinecap=\"round\" />\n        </svg>\n      )}\n      {variant === \"dots\" && (\n        <span aria-hidden=\"true\" className=\"flex items-center\" style={{ gap: px * 0.14 }}>\n          {[0, 1, 2].map((i) => (\n            <span\n              key={i}\n              className=\"rounded-full bg-current [animation:ui-bounce_1.1s_ease-in-out_infinite] motion-reduce:[animation:none]\"\n              style={{ width: px * 0.24, height: px * 0.24, animationDelay: `${i * 140}ms` }}\n            />\n          ))}\n        </span>\n      )}\n      {variant === \"bars\" && (\n        <svg viewBox=\"0 0 24 24\" aria-hidden=\"true\" className=\"size-full\">\n          {Array.from({ length: 8 }, (_, i) => (\n            <rect\n              key={i}\n              x=\"11\"\n              y=\"2\"\n              width=\"2\"\n              height=\"6\"\n              rx=\"1\"\n              fill=\"currentColor\"\n              transform={`rotate(${i * 45} 12 12)`}\n              className=\"[animation:ui-blink_0.8s_linear_infinite] motion-reduce:[animation:none]\"\n              style={{ animationDelay: `${(i - 8) * 100}ms`, opacity: 0.25 }}\n            />\n          ))}\n        </svg>\n      )}\n    </span>\n  );\n}\n\nexport default Spinner;\n"}],"css":{"@keyframes ui-bounce":{"0%, 80%, 100%":{"transform":"translateY(0)","opacity":"0.35"},"40%":{"transform":"translateY(-3px)","opacity":"1"}},"@keyframes ui-blink":{"0%, 100%":{"opacity":"1"},"50%":{"opacity":"0"}}},"docs":"Docs and live preview: https://hairlineui.com/ui/spinner. MIT licensed: https://hairlineui.com/license#components","meta":{"license":"MIT"}}