About this snippet

A glow around the edge of the screen that shows an AI assistant is active, while the content in the middle stays untouched and readable. The colors travel around the frame, a thin sharp line sits on the very edge, and a soft glow fades in from it. The switch in the card turns the glow on and off, and the heading changes to match.

The frame is a conic-gradient whose starting angle comes from a custom property registered with @property, animated from 0deg to 360deg. A mask of two layers, one clipped to the content box and one covering the whole box, combined with exclude, cuts the gradient down to a band as wide as the padding. The soft layer is the same band, wider and blurred with filter, and it reaches past the edges so the blur does not fade out at the border. The switch uses body:has(#glow-toggle:checked), so no JavaScript is needed.

The glow is fixed to the viewport and ignores the pointer, so everything under it still works. To frame a card or a phone mockup instead, use absolute instead of fixed positioning inside a positioned parent with overflow hidden. The animation pauses while the glow is off and stops for visitors who prefer reduced motion. It works in current Chrome, Edge, Firefox and Safari, which all support @property, :has() and mask-composite.

Size
4.1 KB
Uses
HTML, CSS
Loads
Nothing external
Views
59
<div class="edge-glow" aria-hidden="true">
  <span class="edge-glow-soft"></span>
  <span class="edge-glow-line"></span>
</div>
<main class="page">
  <section class="card" aria-labelledby="card-title">
    <h1 class="card-title" id="card-title">
      <span class="when-on">Assistant is working on this page</span>
      <span class="when-off">Assistant is idle</span>
    </h1>
    <p class="card-text">The glow runs around the edge of the screen, so the content stays readable while it shows that an assistant is active.</p>
    <label class="switch">
      <input type="checkbox" role="switch" id="glow-toggle" checked>
      <span class="switch-track" aria-hidden="true"></span>
      <span>Show the edge glow</span>
    </label>
  </section>
</main>
*, *::before, *::after {
  box-sizing: border-box;
}

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

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

.edge-glow {
  --glow-angle: 0deg;
  --glow-a: #3d7bfa;
  --glow-b: #9a5cf6;
  --glow-c: #ef4f98;
  --glow-d: #f7a531;
  position: fixed;
  inset: 0;
  z-index: 10;
  pointer-events: none;
  opacity: 0;
  transition: opacity 0.6s ease;
  animation: glow-spin 6s linear infinite paused;
}

.edge-glow-soft,
.edge-glow-line {
  position: absolute;
  inset: 0;
}

.edge-glow-soft {
  inset: -40px;
  filter: blur(24px);
}

.edge-glow-soft::before,
.edge-glow-line::before {
  content: "";
  position: absolute;
  inset: 0;
  padding: var(--band);
  background: conic-gradient(from var(--glow-angle), var(--glow-a), var(--glow-b), var(--glow-c), var(--glow-d), var(--glow-a));
  -webkit-mask: linear-gradient(#000, #000) content-box, linear-gradient(#000, #000);
  -webkit-mask-composite: xor;
  mask: linear-gradient(#000, #000) content-box exclude, linear-gradient(#000, #000);
}

.edge-glow-soft::before {
  --band: 68px;
}

.edge-glow-line::before {
  --band: 3px;
}

body:has(#glow-toggle:checked) .edge-glow {
  opacity: 1;
  animation-play-state: running;
}

.page {
  display: grid;
  grid-template-columns: minmax(0, 440px);
  justify-content: center;
  align-content: center;
  min-height: 100vh;
  padding: 48px 24px;
}

.card {
  padding: 28px;
  border: 1px solid rgba(148, 163, 184, 0.2);
  border-radius: 18px;
  background: #121829;
}

.card-title {
  margin: 0 0 10px;
  font-size: 1.25rem;
  line-height: 1.35;
}

.when-off {
  display: none;
}

body:has(#glow-toggle:not(:checked)) .when-on {
  display: none;
}

body:has(#glow-toggle:not(:checked)) .when-off {
  display: inline;
}

.card-text {
  margin: 0 0 22px;
  color: #a3afc2;
  line-height: 1.6;
}

.switch {
  position: relative;
  display: inline-flex;
  align-items: center;
  gap: 12px;
  min-height: 44px;
  font-weight: 600;
  cursor: pointer;
}

.switch input {
  position: absolute;
  opacity: 0;
  width: 1px;
  height: 1px;
}

.switch-track {
  position: relative;
  flex-shrink: 0;
  width: 46px;
  height: 26px;
  border-radius: 13px;
  background: #64748b;
  transition: background 0.2s ease;
}

.switch-track::after {
  content: "";
  position: absolute;
  top: 3px;
  left: 3px;
  width: 20px;
  height: 20px;
  border-radius: 50%;
  background: #ffffff;
  transition: transform 0.2s ease;
}

.switch input:checked + .switch-track {
  background: #6d6af0;
}

.switch input:checked + .switch-track::after {
  transform: translateX(20px);
}

.switch input:focus-visible + .switch-track {
  outline: 2px solid #93c5fd;
  outline-offset: 3px;
}

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

@media (forced-colors: active) {
  .switch-track {
    forced-color-adjust: none;
    border: 1px solid ButtonText;
    background: Canvas;
  }

  .switch-track::after {
    top: 2px;
    left: 2px;
    background: ButtonText;
  }

  .switch input:checked + .switch-track {
    border-color: Highlight;
    background: Highlight;
  }

  .switch input:checked + .switch-track::after {
    background: HighlightText;
  }
}

@media (prefers-reduced-motion: reduce) {
  .edge-glow {
    animation: none;
  }

  .edge-glow,
  .switch-track,
  .switch-track::after {
    transition: none;
  }
}
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