VHS Tracking
Lines of Courier text that jump sideways with a red and cyan fringe, under scanlines and a bright line rolling down: the picture of a badly tracked VHS tape.
Updated
This effect is part of Effect.Labs — 840 vanilla effects, some free, some premium. Distortion has 31 effects, including 5 free. Explore the category →
3 usage examples



How it works
The root holds a .vht-screen (280 px wide, #111 background, rounded corners) made of three layers: the .vht-text text, a .vht-noise pattern of 2% white lines every 6 px, and a 3 px bright .vht-line. Both decorative layers carry aria-hidden="true". Each <span> in .vht-text is one line: PLAY, SP 00:12:34 and REC by default.
Each line plays vhtTracking over 3 s. During the first 1.35 s (45 % of the cycle), it slides 8 px right (reached at 0.45 s), swings 4 px left (at 0.9 s) and settles back, while its red and cyan shadow switches sides; for the remaining 1.65 s it holds still with a 1 to 2 px fringe. The 2nd line starts 0.5 s after the 1st, the 3rd 1 s after: the glitch steps down the screen. Meanwhile, vhtLine slides the bright line from top to bottom in 4 s.
Screen width (--vht-width), text size and colour, fringe colours, jump distance (--vht-jump), cycle length and screen background are variables set on the root. The screen never overflows its container (max-width: 100%); with --vht-width: 100% and --vht-panel: transparent, it sits over an image or a video.
No JavaScript. Beyond three lines, the extra ones start together with the first: add a .vht-text span:nth-child(4) { animation-delay: 1.5s } rule to extend the staircase.
Accessibility
- Screen readers: the text is real and read in order ("PLAY SP 00:12:34 REC"); the scanlines and the bright line, hidden with
aria-hidden, are not announced. - Reduced motion: under
prefers-reduced-motion: reduce, the lines stop with their 2 px fringe, the scanlines stay and the bright line is removed. Before the repair, all four animations kept running. - Flashing: the bright line is at 15% opacity and the glitch only takes up 1.35 s of each 3 s cycle; frame by frame, no cell goes above 3 changes per second.
- Contrast: white text on
#111by default. Over a video with--vht-panel: transparent, make sure the picture behind stays dark enough, or keep a semi-opaque background (rgba(0,0,0,.35)). - Meaning: PLAY or REC are labels, not controls; in a real player, the controls remain real
<button>elements outside the effect.
Browser compatibility
Pure CSS (2.9 KB), no dependency: CSS variables inside keyframes (transform, text-shadow), a repeating gradient for the scanlines, an animated top for the bright line. Measured in Chromium 151, Firefox 153 and WebKit 26.5: same look as the previous version once the lines are running (only difference: lines 2 and 3 already show their fringe during their start delay), two independent copies, no overflow at 390 px, 0 errors.
Without CSS animations, the screen stays frozen: text with its fringe, scanlines visible, bright line hidden above the edge. Without CSS variables, the adjustable colours and width no longer apply and the text takes the page's style.
The code
HTML structure to paste into your page (the CSS + JS come with your purchase):
<div class="fx-0272-root">
<div class="vht-screen">
<div class="vht-text">
<span>PLAY</span>
<span>SP 00:12:34</span>
<span>REC</span>
</div>
<div class="vht-noise" aria-hidden="true"></div>
<div class="vht-line" 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 |
|---|---|---|
Lines (HTML — one <span> per line in .vht-text) |
PLAY · SP 00:12:34 · REC | One to three lines each get their own offset; a single line is enough for a timestamp. |
--vht-width |
280px | Screen width; 100% to cover a picture, auto to fit the text. |
--vht-size · --vht-color |
18px · #fff | Text size and colour. |
--vht-red · --vht-cyan |
red · #0ff | Colours of the two fringes around the letters. |
--vht-jump |
8px | Largest jump to the right; the swing back left is half of it. 4 to 5 px for small text. |
--vht-speed |
3s | Length of one tracking cycle; 6s makes the glitch rarer. |
--vht-panel |
#111 | Screen background; transparent or semi-opaque for an overlay. |
--vht-bg · --vht-height |
#0a0a0f · 300px | Root background and minimum height; transparent and auto to blend it in. |
FAQ
position: absolute; inset: 0 inside the <video> container, with --vht-width: 100%, --vht-height: 100% and --vht-panel: transparent. Add pointer-events: none to the root so clicks reach the video controls.--vht-panel) around the text, like the timestamp in the examples.