Skip to content

Repository files navigation

vault66-crt-effect

Live playground and documentation →

npm npm downloads npm total downloads

A CRT screen effect you wrap around any content: scanlines, sweep line, glow, flicker, glitch, screen curvature, glare, and static. Ships for React, Vue, and as a framework-agnostic Web Component (so it also works in Angular, plain HTML, Astro, Svelte, or anything else that runs in a browser).


CRT Effect Demo

Installation

npm install vault66-crt-effect
pnpm add vault66-crt-effect
yarn add vault66-crt-effect
bun add vault66-crt-effect

Use it with your framework

Same effect, same props everywhere — only the import and the syntax change. Presets get you a full look in one line; every individual prop is listed in Props.

React

import CRTEffect from "vault66-crt-effect";
import "vault66-crt-effect/style.css";

export default function App() {
  return (
    <CRTEffect preset="fallout">
      <div style={{ padding: 20, fontSize: 24 }}>Welcome to the Wasteland!</div>
    </CRTEffect>
  );
}

Vue

<script setup>
import CRTEffect from "vault66-crt-effect/vue";
import "vault66-crt-effect/style.css";
</script>

<template>
  <CRTEffect preset="fallout">
    <div style="padding: 20px; font-size: 24px">Welcome to the Wasteland!</div>
  </CRTEffect>
</template>

Web Component

Import once to register the <crt-effect> element, then use it anywhere — no CSS import needed, the component carries its own styles in a shadow root. Props become attributes: booleans are bare (fill), everything else is kebab-case (sweep-duration="5").

<script type="module">
  import "vault66-crt-effect/element";
</script>

<crt-effect preset="fallout">
  <div style="padding: 20px; font-size: 24px">Welcome to the Wasteland!</div>
</crt-effect>

Angular uses the same element — just add CUSTOM_ELEMENTS_SCHEMA to your component/module so the compiler accepts the custom tag (otherwise you'll hit NG8001), and import "vault66-crt-effect/element" once:

import { Component, CUSTOM_ELEMENTS_SCHEMA } from "@angular/core";
import "vault66-crt-effect/element";

@Component({
  selector: "app-root",
  standalone: true,
  schemas: [CUSTOM_ELEMENTS_SCHEMA],
  template: `<crt-effect preset="fallout">…</crt-effect>`,
})
export class AppComponent {}

Props

Every prop works the same across React, Vue, and the Web Component. Start from a preset and override whatever you want, or set props from scratch.

Prop Type Default Purpose
preset "fallout" | "dos" | "cyberpunk" | "arcade" | "commodore64" | "apple2" | "vt100" | "minimal" undefined Apply a complete preset configuration. Individual props can override preset values. See Presets below
enabled boolean true Enables or disables the entire CRT effect
sweepDuration number 10 Duration in seconds for the vertical sweep line animation
sweepThickness number 10 Height (thickness in pixels) of the sweep line
sweepStyle "classic" | "soft" "classic" Style of the vertical sweep line: "classic" is a sharp black line, "soft" is a blurred shadow effect
scanlineOpacity number (0 to 1) 0.2 Opacity of the scanlines
scanlineThickness number 2 Thickness of each scanline in pixels
scanlineGap number 3 Gap between scanlines in pixels
scanlineColor string "rgba(91, 179, 135, 0.2)" Custom hex, RGB, or RGBA scanline color (used only if theme is "custom"; alpha is controlled by scanlineOpacity)
enableScanlines boolean true Shows or hides the scanlines overlay
scanlineOrientation "horizontal" | "vertical" "horizontal" Orientation of the scanlines: "horizontal" renders lines from top to bottom, "vertical" renders lines from left to right
enableSweep boolean true Shows or hides the vertical sweep line animation
theme "green" | "amber" | "blue" | "custom" "green" Predefined scanline color themes (overrides scanlineColor unless custom is used)
enableGlow boolean false Enables outer glow effect around the container
glowColor string "rgba(0, 255, 128, 0.3)" Color of the outer glow
enableEdgeGlow boolean false Enables inset glow effect around edges
edgeGlowColor string "rgba(0, 255, 128, 0.2)" Color of the inset edge glow
edgeGlowSize number 30 Size in pixels of the inset edge glow (box-shadow inset size)
enableFlicker boolean false Enables subtle flicker animation for CRT realism
flickerIntensity "low" | "medium" | "high" | number 0.08 Flicker depth: preset string or custom number (0-1) where 0=no flicker, 1=max brightness variance
flickerSpeed "low" | "medium" | "high" | number 0.8 Flicker animation speed: preset string or custom number in seconds. Lower values = faster flicker
enableGlitch boolean false Enables shaking/interference glitch animation
glitchIntensity "low" | "medium" | "high" | number 0.6 Glitch distance: preset string or custom number (0-1) where 0=no movement, 1=max shake amount
glitchSpeed "low" | "medium" | "high" | number 0.6 Glitch animation speed: preset string or custom number in seconds. Lower values = faster glitch
enableVignette boolean false Enables a subtle edge-darkening vignette overlay
vignetteIntensity number (0 to 1) 0.4 Controls vignette darkness at the edges
enableCurvature boolean false Enables a curved-glass overlay: a soft corner highlight plus darkened, rounded screen edges
curvatureIntensity number (0 to 1) 0.5 Strength of the curved-glass edge darkening
enableGlare boolean false Enables a broad, soft diagonal glass-reflection sheen across the screen
glareIntensity number (0 to 1) 0.18 Brightness of the glare/reflection sheen
enableNoise boolean false Enables an animated RF static/snow overlay generated from SVG fractal noise
noiseOpacity number (0 to 1) 0.15 Opacity of the static/snow overlay
glitchChromatic boolean false Adds animated red/cyan chromatic aberration to the glitch (requires enableGlitch)
tintText boolean false Recolors the wrapped content to a phosphor tint (derived from theme) with a soft glow. Children with their own explicit color keep it
textColor string undefined Overrides the phosphor tint color used when tintText is on. Any CSS color
sweepColor string undefined Gives the sweep line a bright, colored glow instead of the default dark refresh bar. Any CSS color
fill boolean false Stretches the effect to fill its parent's full width and height instead of sizing to its content. See Full-screen layouts
children content Content rendered inside the effect: React children, the Vue default slot, or slotted DOM in the Web Component

In the Web Component, prop names are kebab-case attributes: enableGlowenable-glow, sweepDurationsweep-duration="5", and booleans are set by presence (fill, enable-flicker).

Presets

Eight ready-made monitor looks. Use one as-is, or as a starting point and override individual props.

Preset Look
fallout Green Vault-Tec phosphor terminal — medium scanlines, sweep, subtle edge glow, gentle flicker
dos Amber IBM/Hercules monitor — clean scanlines, no sweep or glow
cyberpunk Purple/magenta neon — heavy outer + edge glow, fast sweep, flicker, glitch
arcade Bright saturated green — thick scanlines, fast sweep, strong glow
commodore64 Periwinkle 8-bit home computer — chunky scanlines, subtle flicker
apple2 Green Apple II — tight scanlines, clean (no sweep or glow)
vt100 Pale green DEC terminal — minimal scanlines, no sweep or glow
minimal Barely-there — subtle scanlines + light vignette only
// Use a preset as-is
<CRTEffect preset="arcade">
  <YourContent />
</CRTEffect>

// Override specific preset values
<CRTEffect preset="fallout" sweepDuration={5} enableFlicker={false}>
  <YourContent />
</CRTEffect>

Customization

Start from a preset and change a few values, or build the whole look from individual props. A few common patterns (React syntax; the props are identical in Vue and the Web Component):

// Custom scanline color and orientation
<CRTEffect theme="custom" scanlineColor="#ff6400" scanlineOpacity={0.3} scanlineOrientation="vertical">
  <YourContent />
</CRTEffect>

// Fine-tune flicker and glitch with numbers
<CRTEffect
  enableFlicker
  flickerIntensity={0.05}  // subtle brightness variance (0-1)
  flickerSpeed={2}         // slow flicker (seconds)
  enableGlitch
  glitchIntensity={0.3}    // gentle shake (0-1)
  glitchSpeed={1.5}        // medium speed (seconds)
>
  <YourContent />
</CRTEffect>

// Or use the "low" / "medium" / "high" shortcuts
<CRTEffect enableFlicker flickerIntensity="low" enableGlitch glitchIntensity="high">
  <YourContent />
</CRTEffect>

Full-screen layouts

By default the wrapper sizes to its content — good for cards, panels, and inline blocks. When you want the effect to cover a whole region or the full viewport, add fill so the wrapper stretches to its parent (and the content stretches with it):

// The parent needs a real height — here, the full viewport
<div style={{ width: "100vw", height: "100vh" }}>
  <CRTEffect preset="fallout" fill>
    <YourApp />
  </CRTEffect>
</div>

fill fills the parent, so the parent must have a resolved height (100vh, a fixed px, or 100% inherited down the chain).

Building your own integration

If your framework isn't React or Vue and you'd rather not use the Web Component, the core is exported separately. computeCrt(options) takes the same props and hands back everything needed to render the effect onto your own markup — no framework attached. It's exactly what the React and Vue wrappers are built on.

It returns four things:

const { enabled, wrapper, inner, overlays } = computeCrt({ preset: "fallout" });
  • enabledfalse when the effect is turned off; render your content untouched.
  • wrapper{ className, style } for the outer element.
  • inner{ className, style } for the element that holds your content.
  • overlays — an array of class names, one per overlay layer.

The markup contract is: a wrapper element containing an inner element (your content goes in there), followed by one empty overlay div per entry in overlays:

<div class="{wrapper.className}" style="{wrapper.style}">
  <div class="{inner.className}" style="{inner.style}">…your content…</div>
  <!-- one sibling div per overlay class -->
  <div class="crt-vignette"></div>
</div>

One catch when applying the styles by hand: the style bag contains both normal CSS properties (filter, flexDirection) and CSS custom properties (--scanline-color-rgb). Custom properties have to be set with setProperty, not by assigning to el.style. Here's a complete vanilla-DOM renderer that handles it:

import { computeCrt } from "vault66-crt-effect/core";
import "vault66-crt-effect/style.css";

function applyStyle(el, style) {
  for (const [key, value] of Object.entries(style)) {
    if (key.startsWith("--")) el.style.setProperty(key, String(value));
    else el.style[key] = value;
  }
}

// Wrap any element in the CRT effect and return the new root element.
function crt(content, options) {
  const { enabled, wrapper, inner, overlays } = computeCrt(options);
  if (!enabled) return content; // effect off — hand content back as-is

  const root = document.createElement("div");
  root.className = wrapper.className;
  applyStyle(root, wrapper.style);

  const innerEl = document.createElement("div");
  innerEl.className = inner.className;
  if (inner.style) applyStyle(innerEl, inner.style);
  innerEl.append(content);
  root.append(innerEl);

  for (const cls of overlays) {
    const layer = document.createElement("div");
    layer.className = cls;
    root.append(layer);
  }
  return root;
}

const screen = crt(myContent, { preset: "fallout" });
document.body.append(screen);

Reduced motion

If the system has "Reduce Motion" enabled, animated layers (sweep, flicker, glitch, static) turn themselves off automatically, while layers like scanlines and glow stay visible. Nothing to configure.

License

MIT — see LICENSE for details.

Releases

Packages

Contributors

Languages