15 CSS Glowing Border Buttons05 / 15

Pure CSSMIT licensed

Dark Mode Glowing Input & Submit Focus States

A checkout-grade mini form where the glow is UX, not decoration: the email field breathes a violet halo on focus, flips to a green glow once the address is valid and red only after you've actually typed something wrong — via :user-valid/:user-invalid — and the submit button answers with its own glow on hover and focus.

Published

Live Demo
Try it

The code

<section class="gbb-05" aria-label="Dark mode glowing form focus states demo">
  <form class="gbb-05__form" novalidate="">
    <label class="gbb-05__label" for="gbb-05-email">Work email</label>
    <input class="gbb-05__input" id="gbb-05-email" type="email" name="email" required placeholder="you@company.com" autocomplete="email">
    <button class="gbb-05__btn" type="button">Create account</button>
    <p class="gbb-05__hint">Glow turns green when the address parses &middot; red only after you type</p>
  </form>
</section>
.gbb-05,
.gbb-05 *,
.gbb-05 *::before,
.gbb-05 *::after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

.gbb-05 {
  --idle: oklch(0.68 0.18 292);
  --ok: oklch(0.78 0.15 158);
  --bad: oklch(0.68 0.2 22);
  --surface: oklch(0.17 0.015 285);
  --ease: cubic-bezier(0.16,1,0.3,1);
  font-family: 'Segoe UI',system-ui,sans-serif;
  width: 100%;
  min-height: 100vh;
  display: grid;
  place-items: center;
  padding: 48px 24px;
  background: radial-gradient(55% 65% at 78% -5%,oklch(0.28 0.08 292 / .45),transparent 60%),radial-gradient(45% 55% at 12% 110%,oklch(0.24 0.06 200 / .3),transparent 60%),oklch(0.12 0.012 280);
}

.gbb-05 .gbb-05__form {
  width: min(360px,100%);
  display: grid;
  gap: 12px;
}

.gbb-05 .gbb-05__label {
  font: 600 11.5px/1 'Segoe UI',system-ui,sans-serif;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: rgba(255,255,255,.5);
}

.gbb-05 .gbb-05__input {
  --glow: var(--idle);
  height: 50px;
  padding: 0 16px;
  border-radius: 12px;
  border: 1px solid rgba(255,255,255,.08);
  background: var(--surface);
  color: #f6f5ff;
  font: 400 15px 'Segoe UI',system-ui,sans-serif;
  caret-color: var(--glow);
  outline: none;
  box-shadow: inset 0 1px 0 rgba(255,255,255,.04),inset 0 0 0 1px rgba(255,255,255,.02);
  transition: border-color .35s var(--ease),box-shadow .35s var(--ease),background .35s var(--ease);
}

.gbb-05 .gbb-05__input::placeholder {
  color: rgba(255,255,255,.28);
}

.gbb-05 .gbb-05__input:focus-visible {
  border-color: color-mix(in oklab,var(--glow) 80%,transparent);
  background: color-mix(in oklab,var(--glow) 4%,var(--surface));
  box-shadow: inset 0 1px 0 color-mix(in oklab,var(--glow) 22%,transparent),0 0 0 1px color-mix(in oklab,var(--glow) 55%,transparent),0 0 0 5px color-mix(in oklab,var(--glow) 14%,transparent),0 6px 24px -4px color-mix(in oklab,var(--glow) 40%,transparent),0 0 44px color-mix(in oklab,var(--glow) 20%,transparent);
}

.gbb-05 .gbb-05__input:user-valid {
  --glow: var(--ok);
  border-color: color-mix(in oklab,var(--glow) 60%,transparent);
  box-shadow: inset 0 1px 0 color-mix(in oklab,var(--glow) 18%,transparent),0 0 0 1px color-mix(in oklab,var(--glow) 40%,transparent),0 0 24px -4px color-mix(in oklab,var(--glow) 35%,transparent);
}

.gbb-05 .gbb-05__input:user-invalid {
  --glow: var(--bad);
  border-color: color-mix(in oklab,var(--glow) 70%,transparent);
  box-shadow: inset 0 1px 0 color-mix(in oklab,var(--glow) 20%,transparent),0 0 0 1px color-mix(in oklab,var(--glow) 50%,transparent),0 0 0 4px color-mix(in oklab,var(--glow) 12%,transparent),0 0 28px -4px color-mix(in oklab,var(--glow) 40%,transparent);
}

.gbb-05 .gbb-05__btn {
  position: relative;
  margin-top: 6px;
  height: 50px;
  border: 1px solid color-mix(in oklab,var(--idle) 45%,transparent);
  border-radius: 12px;
  cursor: pointer;
  font: 600 15px 'Segoe UI',system-ui,sans-serif;
  letter-spacing: .005em;
  color: #fff;
  background: linear-gradient(180deg,color-mix(in oklab,var(--idle) 82%,#000) 0%,color-mix(in oklab,var(--idle) 62%,#000) 100%);
  box-shadow: inset 0 1px 0 rgba(255,255,255,.22),inset 0 -1px 0 rgba(0,0,0,.25),0 1px 0 rgba(0,0,0,.4),0 0 0 1px color-mix(in oklab,var(--idle) 35%,transparent),0 12px 28px -12px color-mix(in oklab,var(--idle) 60%,transparent),0 0 40px -8px color-mix(in oklab,var(--idle) 45%,transparent);
  transition: box-shadow .35s var(--ease),transform .25s var(--ease),background .35s var(--ease);
}

.gbb-05 .gbb-05__btn:hover {
  background: linear-gradient(180deg,color-mix(in oklab,var(--idle) 92%,#000) 0%,color-mix(in oklab,var(--idle) 72%,#000) 100%);
  box-shadow: inset 0 1px 0 rgba(255,255,255,.28),inset 0 -1px 0 rgba(0,0,0,.28),0 1px 0 rgba(0,0,0,.5),0 0 0 1px color-mix(in oklab,var(--idle) 55%,transparent),0 16px 34px -10px color-mix(in oklab,var(--idle) 70%,transparent),0 0 56px -6px color-mix(in oklab,var(--idle) 55%,transparent);
  transform: translateY(-1px);
}

.gbb-05 .gbb-05__btn:focus-visible {
  outline: 2px solid rgba(255,255,255,.85);
  outline-offset: 3px;
}

.gbb-05 .gbb-05__btn:active {
  transform: translateY(0);
  transition-duration: .1s;
}

.gbb-05 .gbb-05__hint {
  font: 400 11.5px/1.5 'Segoe UI',system-ui,sans-serif;
  color: rgba(255,255,255,.36);
  text-wrap: pretty;
  letter-spacing: .005em;
}

@media (prefers-reduced-motion: reduce) {
  .gbb-05 .gbb-05__input,
    .gbb-05 .gbb-05__btn {
    transition: none;
  }
}
/* No JavaScript — :focus-visible drives the halo; :user-valid / :user-invalid recolor it only after real user input (unlike :valid/:invalid). Submit is a no-op button so form doesn't navigate away in preview. */
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: Dark Mode Glowing Input & Submit Focus States
Source: https://codefronts.com/components/css-glowing-border-buttons/dark-mode-glowing-input-submit-focus-states/

A checkout-grade mini form where the glow is UX, not decoration: the email field breathes a violet halo on focus, flips to a green glow once the address is valid and red only after you've actually typed something wrong — via :user-valid/:user-invalid — and the submit button answers with its own glow on hover and focus.
## HTML
```html
<section class="gbb-05" aria-label="Dark mode glowing form focus states demo">
  <form class="gbb-05__form" novalidate="">
    <label class="gbb-05__label" for="gbb-05-email">Work email</label>
    <input class="gbb-05__input" id="gbb-05-email" type="email" name="email" required placeholder="you@company.com" autocomplete="email">
    <button class="gbb-05__btn" type="button">Create account</button>
    <p class="gbb-05__hint">Glow turns green when the address parses &middot; red only after you type</p>
  </form>
</section>
```
## CSS
```css
.gbb-05,
.gbb-05 *,
.gbb-05 *::before,
.gbb-05 *::after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

.gbb-05 {
  --idle: oklch(0.68 0.18 292);
  --ok: oklch(0.78 0.15 158);
  --bad: oklch(0.68 0.2 22);
  --surface: oklch(0.17 0.015 285);
  --ease: cubic-bezier(0.16,1,0.3,1);
  font-family: 'Segoe UI',system-ui,sans-serif;
  width: 100%;
  min-height: 100vh;
  display: grid;
  place-items: center;
  padding: 48px 24px;
  background: radial-gradient(55% 65% at 78% -5%,oklch(0.28 0.08 292 / .45),transparent 60%),radial-gradient(45% 55% at 12% 110%,oklch(0.24 0.06 200 / .3),transparent 60%),oklch(0.12 0.012 280);
}

.gbb-05 .gbb-05__form {
  width: min(360px,100%);
  display: grid;
  gap: 12px;
}

.gbb-05 .gbb-05__label {
  font: 600 11.5px/1 'Segoe UI',system-ui,sans-serif;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: rgba(255,255,255,.5);
}

.gbb-05 .gbb-05__input {
  --glow: var(--idle);
  height: 50px;
  padding: 0 16px;
  border-radius: 12px;
  border: 1px solid rgba(255,255,255,.08);
  background: var(--surface);
  color: #f6f5ff;
  font: 400 15px 'Segoe UI',system-ui,sans-serif;
  caret-color: var(--glow);
  outline: none;
  box-shadow: inset 0 1px 0 rgba(255,255,255,.04),inset 0 0 0 1px rgba(255,255,255,.02);
  transition: border-color .35s var(--ease),box-shadow .35s var(--ease),background .35s var(--ease);
}

.gbb-05 .gbb-05__input::placeholder {
  color: rgba(255,255,255,.28);
}

.gbb-05 .gbb-05__input:focus-visible {
  border-color: color-mix(in oklab,var(--glow) 80%,transparent);
  background: color-mix(in oklab,var(--glow) 4%,var(--surface));
  box-shadow: inset 0 1px 0 color-mix(in oklab,var(--glow) 22%,transparent),0 0 0 1px color-mix(in oklab,var(--glow) 55%,transparent),0 0 0 5px color-mix(in oklab,var(--glow) 14%,transparent),0 6px 24px -4px color-mix(in oklab,var(--glow) 40%,transparent),0 0 44px color-mix(in oklab,var(--glow) 20%,transparent);
}

.gbb-05 .gbb-05__input:user-valid {
  --glow: var(--ok);
  border-color: color-mix(in oklab,var(--glow) 60%,transparent);
  box-shadow: inset 0 1px 0 color-mix(in oklab,var(--glow) 18%,transparent),0 0 0 1px color-mix(in oklab,var(--glow) 40%,transparent),0 0 24px -4px color-mix(in oklab,var(--glow) 35%,transparent);
}

.gbb-05 .gbb-05__input:user-invalid {
  --glow: var(--bad);
  border-color: color-mix(in oklab,var(--glow) 70%,transparent);
  box-shadow: inset 0 1px 0 color-mix(in oklab,var(--glow) 20%,transparent),0 0 0 1px color-mix(in oklab,var(--glow) 50%,transparent),0 0 0 4px color-mix(in oklab,var(--glow) 12%,transparent),0 0 28px -4px color-mix(in oklab,var(--glow) 40%,transparent);
}

.gbb-05 .gbb-05__btn {
  position: relative;
  margin-top: 6px;
  height: 50px;
  border: 1px solid color-mix(in oklab,var(--idle) 45%,transparent);
  border-radius: 12px;
  cursor: pointer;
  font: 600 15px 'Segoe UI',system-ui,sans-serif;
  letter-spacing: .005em;
  color: #fff;
  background: linear-gradient(180deg,color-mix(in oklab,var(--idle) 82%,#000) 0%,color-mix(in oklab,var(--idle) 62%,#000) 100%);
  box-shadow: inset 0 1px 0 rgba(255,255,255,.22),inset 0 -1px 0 rgba(0,0,0,.25),0 1px 0 rgba(0,0,0,.4),0 0 0 1px color-mix(in oklab,var(--idle) 35%,transparent),0 12px 28px -12px color-mix(in oklab,var(--idle) 60%,transparent),0 0 40px -8px color-mix(in oklab,var(--idle) 45%,transparent);
  transition: box-shadow .35s var(--ease),transform .25s var(--ease),background .35s var(--ease);
}

.gbb-05 .gbb-05__btn:hover {
  background: linear-gradient(180deg,color-mix(in oklab,var(--idle) 92%,#000) 0%,color-mix(in oklab,var(--idle) 72%,#000) 100%);
  box-shadow: inset 0 1px 0 rgba(255,255,255,.28),inset 0 -1px 0 rgba(0,0,0,.28),0 1px 0 rgba(0,0,0,.5),0 0 0 1px color-mix(in oklab,var(--idle) 55%,transparent),0 16px 34px -10px color-mix(in oklab,var(--idle) 70%,transparent),0 0 56px -6px color-mix(in oklab,var(--idle) 55%,transparent);
  transform: translateY(-1px);
}

.gbb-05 .gbb-05__btn:focus-visible {
  outline: 2px solid rgba(255,255,255,.85);
  outline-offset: 3px;
}

.gbb-05 .gbb-05__btn:active {
  transform: translateY(0);
  transition-duration: .1s;
}

.gbb-05 .gbb-05__hint {
  font: 400 11.5px/1.5 'Segoe UI',system-ui,sans-serif;
  color: rgba(255,255,255,.36);
  text-wrap: pretty;
  letter-spacing: .005em;
}

@media (prefers-reduced-motion: reduce) {
  .gbb-05 .gbb-05__input,
    .gbb-05 .gbb-05__btn {
    transition: none;
  }
}
```

## JavaScript
```js
/* No JavaScript — :focus-visible drives the halo; :user-valid / :user-invalid recolor it only after real user input (unlike :valid/:invalid). Submit is a no-op button so form doesn't navigate away in preview. */
```

How this works

The focus glow is a transitioned box-shadow: 0 0 0 4px ring plus a soft outer bloom, both derived from one --glow variable with color-mix(). Because box-shadow interpolates, the halo grows out of the border instead of popping — a 250ms transition is the entire animation budget, and it doubles as the accessible focus indicator (with a :focus-visible outline kept for forced-color modes).

The validation states use the modern pseudo-classes :user-valid and :user-invalid instead of :valid/:invalid. The difference is the whole UX: :invalid is true the moment the page loads (an empty required field is invalid), so forms yell at users before they've touched anything. The :user-* variants only match after the user has interacted, which is exactly when feedback is welcome. Each state just re-assigns --glow — the shadow/border machinery is written once.

Make it yours

  • All three state colors are custom properties on the section — brand-match the idle violet, keep green/red conventional.
  • Halo size: the 4px spread ring is the 'focus ring'; the second shadow is mood — drop it for ultra-minimal forms.
  • Add caret-color: var(--glow) so even the text cursor matches the state.
  • The same recipe drops onto <select> and <textarea> unchanged.

Gotchas — read before shipping

  • Never remove the outline without replacing it — this demo keeps a real box-shadow ring at ≥3:1 contrast, and a :focus-visible outline as belt-and-braces.
  • :user-valid needs 2023+ browsers; older engines simply keep the violet focus glow — a graceful degrade, but don't rely on color alone for errors (pair with a message).
  • Set outline:none only on the input itself, never globally.

Browser support

ChromeSafariFirefoxEdge
119+16.5+88+119+

Gated by :user-valid/:user-invalid. Everything else (transitioned box-shadow focus glow) works everywhere.

Techniques used in this demo

Search CodeFronts

Loading…