Gyroscope

No dependencies
Code format
React + Tailwind
const css = `
  @keyframes btn-gyroscope-spin { to { transform: rotate(360deg); } }
  .btn-gyroscope {
    position: relative;
    display: grid;
    place-items: center;
    width: 180px;
    height: 180px;
    perspective: 700px;
  }
  .btn-gyroscope__gimbal {
    position: absolute;
    inset: 12px;
    transform-style: preserve-3d;
    transition: transform 0.9s cubic-bezier(0.34, 1.4, 0.64, 1);
    pointer-events: none;
  }
  .btn-gyroscope__gimbal:nth-of-type(1) { transform: rotateX(72deg) rotateY(0deg); }
  .btn-gyroscope__gimbal:nth-of-type(2) { transform: rotateX(72deg) rotateY(60deg); inset: 20px; }
  .btn-gyroscope__gimbal:nth-of-type(3) { transform: rotateX(72deg) rotateY(-60deg); inset: 28px; }
  .btn-gyroscope__ring {
    position: absolute;
    inset: 0;
    border: 1px solid light-dark(#6d7179, #7d8087);
    border-radius: 50%;
    animation: btn-gyroscope-spin 6s linear infinite;
  }
  .btn-gyroscope__gimbal:nth-of-type(2) .btn-gyroscope__ring { animation-duration: 4.4s; animation-direction: reverse; }
  .btn-gyroscope__gimbal:nth-of-type(3) .btn-gyroscope__ring { animation-duration: 3.2s; }
  .btn-gyroscope__ring::after {
    content: '';
    position: absolute;
    top: -3px;
    left: calc(50% - 3px);
    width: 6px;
    height: 6px;
    border-radius: 50%;
    background: light-dark(#373a40, #f1f2f3);
    box-shadow: 0 0 8px light-dark(#373a40, #f1f2f3);
  }
  .btn-gyroscope__btn {
    position: relative;
    z-index: 1;
    padding: 0.65rem 1.4rem;
    border: 1px solid light-dark(#cbced3, #393c41);
    border-radius: 999px;
    background: light-dark(#f3f4f7, #1a1c1f);
    color: light-dark(#373a40, #f1f2f3);
    font-family: inherit;
    font-size: 14px;
    font-weight: 600;
    cursor: pointer;
    transition: box-shadow 0.5s ease, border-color 0.5s ease;
  }
  .btn-gyroscope__btn:focus-visible { outline: 2px solid light-dark(#42454c, #e5e6e9); outline-offset: 3px; }
  /* Gimbal lock: every ring tilts flat into one halo around the button. */
  .btn-gyroscope:hover .btn-gyroscope__gimbal,
  .btn-gyroscope:focus-within .btn-gyroscope__gimbal { transform: rotateX(0deg) rotateY(0deg); }
  .btn-gyroscope:hover .btn-gyroscope__ring,
  .btn-gyroscope:focus-within .btn-gyroscope__ring { border-color: light-dark(#50535b, #c6c8cd); }
  .btn-gyroscope:hover .btn-gyroscope__btn,
  .btn-gyroscope:focus-within .btn-gyroscope__btn {
    border-color: light-dark(#50535b, #c6c8cd);
    box-shadow: 0 0 24px color-mix(in oklab, light-dark(#373a40, #f1f2f3) 22%, transparent);
  }
  @media (prefers-reduced-motion: reduce) {
    .btn-gyroscope__ring { animation: none; }
    .btn-gyroscope__gimbal { transition: none; }
  }
`;

export function GyroscopeButton() {
  return (
    <>
      <style>
        {css}
      </style>
      <div className="btn-gyroscope">
        <span className="btn-gyroscope__gimbal" aria-hidden="true">
          <span className="btn-gyroscope__ring" />
        </span>
        <span className="btn-gyroscope__gimbal" aria-hidden="true">
          <span className="btn-gyroscope__ring" />
        </span>
        <span className="btn-gyroscope__gimbal" aria-hidden="true">
          <span className="btn-gyroscope__ring" />
        </span>
        <button type="button" className="btn-gyroscope__btn">
          Stabilise
        </button>
      </div>
    </>
  );
}

Free to use in any project, commercial included. MIT-0, no attribution required. Licence

Install with shadcn

npx shadcn@latest add @movin/button-gyroscope
Source on GitHub

About this component

Three gimbal rings, each with a glowing bead, tumble in 3D around a pill button. Hover or focus locks them: every ring swings flat with a spring into one halo around the button, which lights up.

Dependencies
None
Difficulty
Intermediate
Triggers
hover, 3d, spring, continuous
Techniques
preserve 3d, 3d transform, keyframes, css transition
Reduced motion
Respected
Theme
Light and dark (follows your page's color-scheme)

A real <button> with a visible label; the rings are aria-hidden. Keyboard focus triggers the same lock as hover. Under prefers-reduced-motion the rings stop spinning and the lock is instant.

More buttons

See all
Hologram
Horizon Grid
Iris
Laser Etch