Lightning Border
Electric border: a violet ring twisted by an SVG turbulence filter changes shape every 350 ms and breathes gently; it flares up on hover, focus and finger press.
Updated
This effect is part of Effect.Labs — 840 vanilla effects, some free, some premium. Utilities has 43 effects, including 3 free. Explore the category →
3 usage examples



How it works
The .ltb-box has a crisp 2 px #7b2ff7 border. Right around it, the .ltb-ring (3 px, 6 px wider on each side, 16 px radius) goes through an SVG filter: feTurbulence builds noise (frequency 0.05, 3 octaves) and feDisplacementMap uses it to shift each pixel of the line by up to 2 px. The straight line turns ragged, like an electric arc.
Every 350 ms the script changes the turbulence seed: the bolt takes a new shape, fewer than three changes per second. Meanwhile the ring's opacity goes from 0.7 to 1 and back in 1.2 s (ltb-flicker). The script stops the shape changes when the box leaves the screen (IntersectionObserver) or when the system asks for less motion, and resumes them afterwards.
The script also gives each copy its own filter id (ltb-filter-1, ltb-filter-2…) and links the ring to it: several borders on one page never share a filter. On hover, focus (:focus-within) or finger press (ltb-on class), border and ring turn #a855f7 with a 15 to 40 px violet halo.
Accessibility
- Flashes measured: frame by frame, no pixel of the border changes brightness more than 2 times per second (WCAG 2.3.1 threshold: 3), at rest and in the active state. The original version went up to 12.5 at rest and 21 on hover, reduced motion included.
- Reduced motion: under
prefers-reduced-motion: reducethe bolt shape is frozen and opacity stays at 0.8; nothing moves any more. The setting is followed live, without reloading the page. - Silent decoration: the filter SVG and the ring have
aria-hidden="true"; only the box content is read. - Keyboard and touch: the demo box is a
<div>, which Tab cannot reach. For a clickable card, give theltb-boxclass to your<a href>or<button>, keeping the filter SVG and the ring as first children, or put your link inside the box: Tab then lights the active state. On a box turned into a link or button, that state replaces the focus outline; a link placed inside keeps its own. A finger press lights the active state too, WebKit included. - Limit: the bolt is a continuous animation with no pause button; it only stops off screen or with the system setting.
Browser compatibility
SVG filter (feTurbulence, feDisplacementMap) applied in CSS to an HTML element, :is() selector (Chrome 88, Firefox 78, Safari 14), IntersectionObserver (Chrome 51, Firefox 55, Safari 12.1) and 2 KB of JavaScript. Measured in Chromium 151, Firefox 153 and WebKit 26.5 (Playwright): two copies with two distinct filter ids, only the second one hovered, keyboard focus, finger press, 390 px without overflow, reduced motion with no animation and no seed change, 0 errors. Flashes measured in Chromium over 200 frames 20 ms apart.
Without JavaScript, the ring keeps the first copy's filter (#ltb-filter) with a fixed shape and only the opacity breathes. Without :is(), the active state does not show but the border stays.
The code
HTML structure to paste into your page (the CSS + JS come with your purchase):
<div class="fx-0770-root">
<div class="ltb-box">
<svg class="ltb-defs" aria-hidden="true" focusable="false">
<filter id="ltb-filter" …>
<feTurbulence … result="turb"></feTurbulence>
<feDisplacementMap in="SourceGraphic" in2="turb" …></feDisplacementMap>
</filter>
</svg>
<div class="ltb-ring" aria-hidden="true"></div>
…
</div>
</div>
One-time payment, no subscription — the code is yours forever
Customize
Options passed to the API or data-* attributes:
| Option / property | Default | Effect |
|---|---|---|
Colour (CSS — --ltb-color) |
#7b2ff7 |
Crisp border and electric ring at rest. |
Active colour and halo (CSS — --ltb-color-active, --ltb-glow) |
#a855f7 and rgba(123, 47, 247, .45) |
Colour and glow on hover, focus and touch. |
Breathing (CSS — --ltb-flicker) |
1.2s |
Length of one opacity round trip between 0.7 and 1. Do not go below 0.7 s. |
Bolt pace (JS — STEP_MS) |
350 ms |
Time between two shapes. Measured: at most 2 brightness changes per second at 350 ms, 2.5 at 250 ms, 3.5 at 170 ms (over the threshold). Stay at 250 ms or more. |
Amplitude (HTML — scale of feDisplacementMap) |
4 (2 px each way) |
Bigger twists the line more; at 0 it is straight again. |
Grain (HTML — baseFrequency of feTurbulence) |
0.05 |
Higher gives tighter ripples; lower, wider ones. |
Forced active state (HTML — ltb-on class on .ltb-box) |
absent | Keeps the border flared, for instance on the item in alert. The script only removes the one it added on touch. |
FAQ
.fx-0770-root gets its own filter (a single .ltb-box per root: the script only handles the first one), and borders that are off screen stop changing shape. Each visible border still recomputes its filter for every new shape: beyond ten or so on screen at once, check smoothness on a low-end phone.