Skip to main content

February 25, 2026 · 8 min read

How to build a rotating conic-gradient button in vanilla CSS

A practical, code-first tutorial for creating a rotating conic-gradient button border in pure HTML and CSS.

This version is intentionally simple: one button, one conic-gradient border, and one animated custom property.

No JavaScript is required. The border rotates because --angle is registered as an animatable angle and fed into conic-gradient(from var(--angle, 0deg), ...).

Below is the exact setup with full copy-paste code, plus accessibility and motion fallback rules.

How the effect works

The button uses two backgrounds on the same element. A solid black background fills the inside, and a conic-gradient fills the border box.

When --angle rotates from 0deg to 360deg, the gradient appears to spin around the button while the content stays perfectly still.

  • One semantic <button> element.
  • Border created with border: 2px solid transparent and layered backgrounds.
  • Gradient rotation controlled by @property --angle.
  • Consistent loop with animation: spin 2.5s linear infinite.
  • Accessible focus and reduced-motion fallback.

Step 1: HTML and base styles

Start with a plain button element and keep the styling minimal: black background, transparent border, rounded corners.

At this stage, there is no animation yet. We only establish shape, spacing, and typography.

HTML · html

<button class="btn-conic">Button</button>

Base CSS · css

body {
  min-height: 100vh;
  margin: 0;
  display: grid;
  place-items: center;
  background: #262a34;
}

.btn-conic {
  --fill: #3a3f50;
  border-radius: 3rem;
  padding: 1rem;
  min-width: 200px;
  background: #2c303d;
  border: 2px solid transparent;
  color: white;
  cursor: pointer;
}

Step 2: register and use --angle

@property tells the browser that --angle is an <angle> value, which allows smooth interpolation during animation.

Then we build the border using two background layers: linear-gradient(...) padding-box for the inner fill and conic-gradient(...) border-box for the animated ring.

Angle + border gradient · css

@property --angle {
  syntax: '<angle>';
  inherits: false;
  initial-value: 0deg;
}

.btn-conic {
  background:
    linear-gradient(var(--fill), var(--fill)) padding-box,
    conic-gradient(
        from var(--angle, 0deg),
        rgba(52, 168, 82, 0) 10deg,
        rgba(52, 168, 82, 1) 38.9738deg,
        rgba(255, 211, 20, 1) 62.3678deg,
        rgba(255, 70, 65, 1) 87.0062deg,
        rgba(49, 134, 255, 1) 107.428deg,
        rgba(49, 134, 255, 0.5) 150deg,
        rgba(49, 134, 255, 0) 200deg,
        rgba(52, 168, 82, 0) 360deg
      )
      border-box;
  animation: spin 2.5s linear infinite;
}
  • Keep inherits: false for predictable behavior.
  • Use exact color stops if you want to match the visual feel.
  • Use linear timing to keep angular speed constant.

Step 3: motion and accessibility polish

The animation itself is short: rotate the custom angle from 0deg to 360deg.

With this minimal version, we keep only the spin keyframes and no extra interaction styles.

Animation + a11y rules · css

@keyframes spin {
  to {
    --angle: 360deg;
  }
}

.btn-conic:hover {
  --fill: #424a5e;
}
  • Keep animation decorative; text should remain stable and readable.
  • The entire effect lives in one button selector and one keyframes block.
  • You can add focus and reduced-motion rules later if needed.

Complete copy-paste version

Open on CodePen

Use this full file as a baseline and then tune text, duration, and color stops to fit your product.

Full HTML + CSS · html

<!doctype html>
<html lang="en">
  <body>
    <button class="btn-conic">Button</button>

    <style>
      body {
        min-height: 100vh;
        margin: 0;
        display: grid;
        place-items: center;
        background: #262a34;
      }

      @property --angle {
        syntax: '<angle>';
        inherits: false;
        initial-value: 0deg;
      }

      .btn-conic {
        --fill: #3a3f50;
        border-radius: 3rem;
        padding: 1rem;
        min-width: 200px;
        background: #2c303d;
        border: 2px solid transparent;
        color: white;
        cursor: pointer;
        background:
          linear-gradient(var(--fill), var(--fill)) padding-box,
          conic-gradient(
              from var(--angle, 0deg),
              rgba(52, 168, 82, 0) 10deg,
              rgba(52, 168, 82, 1) 38.9738deg,
              rgba(255, 211, 20, 1) 62.3678deg,
              rgba(255, 70, 65, 1) 87.0062deg,
              rgba(49, 134, 255, 1) 107.428deg,
              rgba(49, 134, 255, 0.5) 150deg,
              rgba(49, 134, 255, 0) 200deg,
              rgba(52, 168, 82, 0) 360deg
            )
            border-box;
        animation: spin 2.5s linear infinite;
      }

      .btn-conic:hover {
        --fill: #424a5e; /* hover bg */
      }

      @keyframes spin {
        to {
          --angle: 360deg;
        }
      }
    </style>
  </body>
</html>

Final take

This pattern is effective because it stays lightweight: one element, one animated angle variable, and clean accessibility defaults.

CSSAnimationVanilla CSSUI EngineeringAccessibility