About this snippet

A Generate with AI button made with CSS only. At rest it has a thin border in four colors. Hover it or tab to it and the border starts to turn, a blurred copy of the same gradient glows behind it, and the three sparkle stars in the icon twinkle out of step. The second button shows the busy state, which turns faster and keeps going while your request runs.

The border is a conic-gradient that starts from a registered custom property. Because @property declares that property as an angle, the browser can animate it smoothly from 0deg to 360deg, where a plain custom property would only jump between the two. The button's padding lets the gradient show as a ring around the dark inner label, and a blurred pseudo-element behind it makes the glow. The SVG stars scale and rotate around their own centers because transform-box is fill-box and transform-origin is center.

Set aria-busy and aria-disabled to true from your script when the request starts and remove both when the result comes back. aria-disabled does not block clicks, so your click handler should ignore the button while it is busy. Sizes are in em, so the button follows the font size of the place you put it. The motion stops for visitors who prefer reduced motion, and in a browser without @property the ring still shows but does not turn.

Size
4.5 KB
Uses
HTML, CSS
Loads
Nothing external
Views
64
<main class="stage">
  <div class="demo">
    <button type="button" class="ai-button">
      <span class="ai-button-inner">
        <svg class="ai-button-icon" viewBox="0 0 24 24" aria-hidden="true">
          <path class="spark spark-lg" d="M10 6Q10 14 18 14Q10 14 10 22Q10 14 2 14Q10 14 10 6Z"></path>
          <path class="spark spark-sm" d="M18.5 2Q18.5 5.5 22 5.5Q18.5 5.5 18.5 9Q18.5 5.5 15 5.5Q18.5 5.5 18.5 2Z"></path>
          <path class="spark spark-xs" d="M20 16Q20 18 22 18Q20 18 20 20Q20 18 18 18Q20 18 20 16Z"></path>
        </svg>
        <span>Generate with AI</span>
      </span>
    </button>
    <p class="demo-label">Ready. Hover or tab to it.</p>
  </div>
  <div class="demo">
    <button type="button" class="ai-button" aria-busy="true" aria-disabled="true">
      <span class="ai-button-inner">
        <svg class="ai-button-icon" viewBox="0 0 24 24" aria-hidden="true">
          <path class="spark spark-lg" d="M10 6Q10 14 18 14Q10 14 10 22Q10 14 2 14Q10 14 10 6Z"></path>
          <path class="spark spark-sm" d="M18.5 2Q18.5 5.5 22 5.5Q18.5 5.5 18.5 9Q18.5 5.5 15 5.5Q18.5 5.5 18.5 2Z"></path>
          <path class="spark spark-xs" d="M20 16Q20 18 22 18Q20 18 20 20Q20 18 18 18Q20 18 20 16Z"></path>
        </svg>
        <span>Generating</span>
      </span>
    </button>
    <p class="demo-label">Working, with <code>aria-busy="true"</code></p>
  </div>
</main>
*, *::before, *::after {
  box-sizing: border-box;
}

body {
  font-family: system-ui, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  margin: 0;
  background: #f8fafc;
  color: #0f172a;
}

.stage {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  align-content: center;
  align-items: center;
  gap: 40px 64px;
  min-height: 100vh;
  padding: 40px 20px;
  font-size: clamp(1rem, 0.5rem + 1.6vw, 1.75rem);
}

.demo {
  display: grid;
  justify-items: center;
  gap: 16px;
}

.demo-label {
  margin: 0;
  font-size: 0.875rem;
  color: #475569;
}

.demo-label code {
  font-family: ui-monospace, Consolas, monospace;
  font-size: 0.8125rem;
  color: #0f172a;
}

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

.ai-button {
  --angle: 0deg;
  --ai-blue: #3d7bfa;
  --ai-violet: #9a5cf6;
  --ai-pink: #ef4f98;
  --ai-amber: #f7a531;
  position: relative;
  isolation: isolate;
  display: inline-flex;
  padding: 0.1em;
  border: 0;
  border-radius: 0.8em;
  background: none;
  color: #ffffff;
  font: inherit;
  cursor: pointer;
  animation: ai-spin 4s linear infinite paused;
  transition: transform 0.2s ease;
}

.ai-button::before,
.ai-button::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  border-radius: inherit;
  background: conic-gradient(from var(--angle), var(--ai-blue), var(--ai-violet), var(--ai-pink), var(--ai-amber), var(--ai-blue));
}

.ai-button::after {
  z-index: -2;
  filter: blur(0.75em);
  opacity: 0;
  transition: opacity 0.3s ease;
}

.ai-button-inner {
  display: inline-flex;
  align-items: center;
  gap: 0.6em;
  padding: 0.75em 1.35em 0.75em 1.1em;
  border-radius: 0.7em;
  background: #0f172a;
  font-weight: 600;
  line-height: 1.2;
}

.ai-button-icon {
  width: 1.6em;
  height: 1.6em;
  fill: currentColor;
  overflow: visible;
}

.spark {
  transform-box: fill-box;
  transform-origin: center;
  animation: ai-twinkle 1.8s ease-in-out infinite paused;
}

.spark-sm {
  animation-delay: -0.6s;
}

.spark-xs {
  animation-delay: -1.2s;
}

.ai-button:hover,
.ai-button:focus-visible,
.ai-button:hover .spark,
.ai-button:focus-visible .spark {
  animation-play-state: running;
}

.ai-button:hover::after,
.ai-button:focus-visible::after {
  opacity: 0.6;
}

.ai-button:hover:not([aria-busy="true"]) {
  transform: translateY(-1px);
}

.ai-button:active:not([aria-busy="true"]) {
  transform: scale(0.98);
}

.ai-button:focus-visible {
  outline: 2px solid #1d4ed8;
  outline-offset: 4px;
}

.ai-button[aria-busy="true"] {
  animation-duration: 1.6s;
  animation-play-state: running;
  cursor: progress;
}

.ai-button[aria-busy="true"] .spark {
  animation-play-state: running;
}

.ai-button[aria-busy="true"]::after {
  opacity: 0.6;
}

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

@keyframes ai-twinkle {
  0%, 100% {
    transform: scale(1) rotate(0deg);
    opacity: 1;
  }
  50% {
    transform: scale(0.55) rotate(45deg);
    opacity: 0.7;
  }
}

@media (prefers-reduced-motion: reduce) {
  .ai-button,
  .spark {
    animation: none;
  }

  .ai-button:hover:not([aria-busy="true"]) {
    transform: none;
  }
}

@media (forced-colors: active) {
  .ai-button {
    border: 2px solid ButtonText;
  }
}
Esc
↑↓ moveEnter select

Start from a template

Open an .html file, or HTML, CSS, JS, Markdown, Less or TypeScript files. You can also drop them here. Styles and scripts go into their panes.

Share

Source

Keyboard shortcuts

Save