15 CSS Glowing Border Buttons14 / 15

Pure CSSMIT licensed

Smooth Gradient Hue-Shift Border Button

The 'Most Popular' plan highlighter: the button's border and halo drift continuously through the color wheel — violet to cyan to rose and back — by animating a single registered hue number through oklch(). Because only hue moves (lightness and chroma pinned), every frame is equally vivid and the label never loses contrast.

Published

Live Demo
Try it

The code

<section class="gbb-14" aria-label="Hue shifting gradient border button demo">
  <div class="gbb-14__plan">
    <span class="gbb-14__badge"><span class="gbb-14__dot" aria-hidden="true"></span>Most Popular</span>
    <button class="gbb-14__btn" type="button">Choose Pro &mdash; $19/mo</button>
  </div>
</section>
.gbb-14,
.gbb-14 *,
.gbb-14 *::before,
.gbb-14 *::after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

@property --gbb-h {
  syntax: '<number>';
  inherits: true;
  initial-value: 280;
}

.gbb-14 {
  font-family: 'Segoe UI',system-ui,sans-serif;
  width: 100%;
  min-height: 100vh;
  display: grid;
  place-items: center;
  padding: 48px 24px;
  background: oklch(0.14 0.015 280);
  animation: gbb14-drift 6s linear infinite;
}

.gbb-14 .gbb-14__plan {
  display: grid;
  justify-items: center;
  gap: 14px;
}

.gbb-14 .gbb-14__badge {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  font: 700 11px/1 'Segoe UI',system-ui,sans-serif;
  letter-spacing: .09em;
  text-transform: uppercase;
  color: rgba(255,255,255,.85);
  border: 1px solid oklch(0.7 0.19 calc(var(--gbb-h) * 1deg) / .4);
  border-radius: 999px;
  padding: 6px 13px;
}

.gbb-14 .gbb-14__dot {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: oklch(0.75 0.19 calc(var(--gbb-h) * 1deg));
  box-shadow: 0 0 8px oklch(0.75 0.19 calc(var(--gbb-h) * 1deg));
}

.gbb-14 .gbb-14__btn {
  cursor: pointer;
  border: 2px solid transparent;
  border-radius: 14px;
  padding: 16px 32px;
  font: 700 15px/1 'Segoe UI',system-ui,sans-serif;
  color: #fff;
  background: linear-gradient(oklch(0.18 0.02 280),oklch(0.18 0.02 280)) padding-box,linear-gradient(120deg,oklch(0.7 0.19 calc(var(--gbb-h) * 1deg)),oklch(0.7 0.19 calc((var(--gbb-h) + 60) * 1deg))) border-box;
  box-shadow: 0 0 22px oklch(0.7 0.19 calc(var(--gbb-h) * 1deg) / .3);
  transition: box-shadow .3s ease,transform .25s ease;
}

.gbb-14 .gbb-14__btn:hover,
.gbb-14 .gbb-14__btn:focus-visible {
  transform: translateY(-2px);
  box-shadow: 0 0 38px oklch(0.7 0.19 calc(var(--gbb-h) * 1deg) / .5),0 0 0 4px oklch(0.7 0.19 calc(var(--gbb-h) * 1deg) / .18);
}

.gbb-14:has(.gbb-14__btn:hover),
.gbb-14:has(.gbb-14__btn:focus-visible) {
  animation-duration: 2.5s;
}

.gbb-14 .gbb-14__btn:focus-visible {
  outline: 2px solid #fff;
  outline-offset: 4px;
}

.gbb-14 .gbb-14__btn:active {
  transform: translateY(0) scale(.98);
}

@keyframes gbb14-drift {
  from {
    --gbb-h: 0;
  }

  to {
    --gbb-h: 360;
  }
}

@media (prefers-reduced-motion: reduce) {
  .gbb-14 {
    animation: none;
  }
}
/* No JavaScript — one registered number (--gbb-h) animates 0→360; every oklch() color on the button derives its hue from it via calc(). */
Paste this into ChatGPT, Claude, Cursor, or any coding assistant. The block below is pre-framed with everything the AI needs to integrate this demo into your project — markup, styles, scoping notes, and the source URL. Hit Copy and paste straight into your chat.
Here's a working CSS Glowing Border Button from CodeFronts. Use it as-is or adapt to your framework. All classes are scoped under a unique prefix so the code won't collide with your existing styles. MIT licensed.
Demo: Smooth Gradient Hue-Shift Border Button
Source: https://codefronts.com/components/css-glowing-border-buttons/smooth-gradient-hue-shift-border-button/

The 'Most Popular' plan highlighter: the button's border and halo drift continuously through the color wheel — violet to cyan to rose and back — by animating a single registered hue number through oklch(). Because only hue moves (lightness and chroma pinned), every frame is equally vivid and the label never loses contrast.
## HTML
```html
<section class="gbb-14" aria-label="Hue shifting gradient border button demo">
  <div class="gbb-14__plan">
    <span class="gbb-14__badge"><span class="gbb-14__dot" aria-hidden="true"></span>Most Popular</span>
    <button class="gbb-14__btn" type="button">Choose Pro &mdash; $19/mo</button>
  </div>
</section>
```
## CSS
```css
.gbb-14,
.gbb-14 *,
.gbb-14 *::before,
.gbb-14 *::after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

@property --gbb-h {
  syntax: '<number>';
  inherits: true;
  initial-value: 280;
}

.gbb-14 {
  font-family: 'Segoe UI',system-ui,sans-serif;
  width: 100%;
  min-height: 100vh;
  display: grid;
  place-items: center;
  padding: 48px 24px;
  background: oklch(0.14 0.015 280);
  animation: gbb14-drift 6s linear infinite;
}

.gbb-14 .gbb-14__plan {
  display: grid;
  justify-items: center;
  gap: 14px;
}

.gbb-14 .gbb-14__badge {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  font: 700 11px/1 'Segoe UI',system-ui,sans-serif;
  letter-spacing: .09em;
  text-transform: uppercase;
  color: rgba(255,255,255,.85);
  border: 1px solid oklch(0.7 0.19 calc(var(--gbb-h) * 1deg) / .4);
  border-radius: 999px;
  padding: 6px 13px;
}

.gbb-14 .gbb-14__dot {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: oklch(0.75 0.19 calc(var(--gbb-h) * 1deg));
  box-shadow: 0 0 8px oklch(0.75 0.19 calc(var(--gbb-h) * 1deg));
}

.gbb-14 .gbb-14__btn {
  cursor: pointer;
  border: 2px solid transparent;
  border-radius: 14px;
  padding: 16px 32px;
  font: 700 15px/1 'Segoe UI',system-ui,sans-serif;
  color: #fff;
  background: linear-gradient(oklch(0.18 0.02 280),oklch(0.18 0.02 280)) padding-box,linear-gradient(120deg,oklch(0.7 0.19 calc(var(--gbb-h) * 1deg)),oklch(0.7 0.19 calc((var(--gbb-h) + 60) * 1deg))) border-box;
  box-shadow: 0 0 22px oklch(0.7 0.19 calc(var(--gbb-h) * 1deg) / .3);
  transition: box-shadow .3s ease,transform .25s ease;
}

.gbb-14 .gbb-14__btn:hover,
.gbb-14 .gbb-14__btn:focus-visible {
  transform: translateY(-2px);
  box-shadow: 0 0 38px oklch(0.7 0.19 calc(var(--gbb-h) * 1deg) / .5),0 0 0 4px oklch(0.7 0.19 calc(var(--gbb-h) * 1deg) / .18);
}

.gbb-14:has(.gbb-14__btn:hover),
.gbb-14:has(.gbb-14__btn:focus-visible) {
  animation-duration: 2.5s;
}

.gbb-14 .gbb-14__btn:focus-visible {
  outline: 2px solid #fff;
  outline-offset: 4px;
}

.gbb-14 .gbb-14__btn:active {
  transform: translateY(0) scale(.98);
}

@keyframes gbb14-drift {
  from {
    --gbb-h: 0;
  }

  to {
    --gbb-h: 360;
  }
}

@media (prefers-reduced-motion: reduce) {
  .gbb-14 {
    animation: none;
  }
}
```

## JavaScript
```js
/* No JavaScript — one registered number (--gbb-h) animates 0→360; every oklch() color on the button derives its hue from it via calc(). */
```

How this works

Instead of animating a giant multi-stop gradient background-position (the old way — bandy and memory-hungry), this registers @property --gbb-h { syntax:'<number>' } and animates just that number 0→360. Every color on the button derives from it: border gradient stops at oklch(0.7 0.19 calc(var(--gbb-h) * 1deg)) and a second stop offset +60°, the halo shadow, even the badge dot. One animation, total palette coherence.

The oklch choice is the modern half of the lesson: in HSL, rotating hue at fixed s/l swings wildly through perceived brightness (yellow blinds, blue goes dark). OKLCH is perceptually uniform — pin L=0.7, C=0.19 and every hue lands at the same visual weight, so the border never has a 'weak frame'. The border itself is the padding-box/border-box gradient pair (same trick as demo 07), and hover simply widens the halo and speeds the drift from 6s to 2.5s — urgency through tempo, not size.

Make it yours

  • Constrain the drift to a brand arc by animating 250→330 instead of 0→360 (alternate direction) for an on-palette shimmer.
  • The +60° second stop sets gradient 'spread' — +180° gives a two-tone opposing border.
  • Tempo: 6s rest / 2.5s hover shown; enterprise pricing pages read best at 10s+.
  • Reuse --gbb-h on the parent card's border for a matched set highlighting the whole tier.

Gotchas — read before shipping

  • syntax:'<number>' + calc(var() * 1deg) — animating a raw '<angle>' also works, but numbers let you reuse the value in non-angle contexts.
  • Without @property registration, the hue snaps once per loop instead of drifting — the same failure mode as demo 01.
  • Keep lightness ≥0.65 for dark themes; below that some hues fail 3:1 non-text contrast against near-black.

Browser support

ChromeSafariFirefoxEdge
111+16.4+128+111+

Floor set by :has(), oklch(), @property as this demo is written. The core technique itself goes back further — Chrome 85+.

@property + oklch() gate. Fallback: a static two-color gradient border — set the custom property's initial-value and skip the animation.

Techniques used in this demo

Search CodeFronts

Loading…