npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@laddtnov/cyberpunk-ui

v0.9.0

Published

Zero-dependency cyberpunk neon CSS kit — design tokens, glow/glitch/scanline effects, form controls, feedback, and containers on native <details>/<dialog>. WCAG-aware light theme, no build step.

Readme

>_ @laddtnov/cyberpunk-ui

Zero-dependency cyberpunk neon CSS kit — design tokens, glow / glitch / scanline effects, form controls, and feedback components.

npm downloads CI license size deps

>_ OPEN THE LIVE DEMO

Every component on one page — themes, forms, feedback, effects.


Neon glow, CRT scanlines, glitching headlines, a terminal window that looks like it belongs in a Netrunner's rig. 63 kB of CSS, 38 design tokens, zero dependencies, and nothing to build. Drop in one stylesheet and start writing class names. Extracted from the laddtnov.xyz portfolio.

Two minutes to a glowing page

One <link>, no install, no build:

<!doctype html>
<link rel="stylesheet"
      href="https://cdn.jsdelivr.net/npm/@laddtnov/cyberpunk-ui/cyberpunk-ui.css">
<link rel="stylesheet"
      href="https://fonts.googleapis.com/css2?family=Orbitron:wght@700&family=Rajdhani:wght@400;600&family=Share+Tech+Mono&display=swap">

<body style="background: var(--cy-bg); padding: 3rem; font-family: var(--cy-font-body)">
  <section class="cy-grid-bg cy-scanlines" style="padding: 3rem">
    <h1 class="cy-glitch">NIGHT CITY</h1>

    <div class="cy-card">
      <p>Reactor online <span class="cy-cursor"></span></p>
      <progress class="cy-progress" value="66" max="100"></progress>
      <a href="#" class="cy-btn cy-glow">Launch</a>
      <a href="#" class="cy-btn cy-btn--pink">Abort</a>
    </div>
  </section>
</body>

That is the whole setup. The grid drifts, the headline splits its channels, the cursor blinks, the button lights up on hover.

Want a different world? Change one line:

:root { --cy-neon-cyan: #39ff14; }  /* the whole kit turns acid-green */

Or flip to the light theme, where the neon dims itself to keep text readable:

<html data-theme="light">   <!-- or "auto" to follow the operating system -->

Play with every token live in the demo's theme editor, then copy the CSS it generates.

What you get

Buttons, cards, inputs, checkboxes, radios, alerts, toasts, badges, a spinner and a progress bar. Navigation: a top bar, a breadcrumb, a sidebar, a data table. Containers built on real HTML elements: an accordion on <details>, a modal on <dialog>, a terminal window with a title bar. Six effects that do the heavy visual lifting: glow, text-glow, glitch, scanlines, animated grid, blinking cursor.

Accessibility is part of the design here. Every contrast ratio is checked in CI and fails the build if it drops below WCAG AA. Focus rings are identical across every control. Animations back off under prefers-reduced-motion, and the kit answers prefers-contrast: more and Windows High Contrast too. Where HTML already has an element for the job, the kit styles that element instead of faking it with ARIA.

Install

Pick your package manager — the kit is plain CSS with no dependencies, no postinstall and no build step, so all four behave the same.

npm install @laddtnov/cyberpunk-ui
pnpm add @laddtnov/cyberpunk-ui
yarn add @laddtnov/cyberpunk-ui
bun add @laddtnov/cyberpunk-ui

All eight stylesheet subpaths resolve under every one of them, including Yarn Plug'n'Play with no node_modules on disk. Verified against the published package — see docs/STATE.md for the version matrix.

Just published and your lockfile still shows the previous version? That is deliberate, and not a bug in this package. pnpm 11 and Yarn 4 both refuse versions younger than 24 hours by default — a supply-chain cooldown (minimumReleaseAge / npmMinimalAgeGate, both 1440 minutes). They silently resolve to the newest release older than that, and pnpm prints (x.y.z is available) while doing it. npm, Yarn Classic and Bun install immediately. Ask for the version explicitly to bypass the wait — naming any exact version skips the gate, so use whichever one you are actually after:

pnpm add @laddtnov/[email protected]

cyberpunk-ui.css is one concatenated stylesheet, so a <link> to it is a single request. Cherry-picking still works — each part ships as its own file:

/* everything */
@import "@laddtnov/cyberpunk-ui";

/* …or cherry-pick */
@import "@laddtnov/cyberpunk-ui/tokens";
@import "@laddtnov/cyberpunk-ui/effects";
@import "@laddtnov/cyberpunk-ui/components";
@import "@laddtnov/cyberpunk-ui/containers";
@import "@laddtnov/cyberpunk-ui/navigation";
@import "@laddtnov/cyberpunk-ui/table";
@import "@laddtnov/cyberpunk-ui/forms";
@import "@laddtnov/cyberpunk-ui/feedback";

Documentation

Per-component reference, one page per subpath — markup, modifiers, the tokens that restyle each component, and the accessibility contract the kit cannot enforce for you:

docs/components/ — effects · button and card · containers · navigation · table · forms · feedback

Usage

A form and a status message:

<div class="cy-field">
  <label class="cy-label" for="callsign">Callsign</label>
  <input class="cy-input" id="callsign" aria-describedby="callsign-hint">
  <span class="cy-hint" id="callsign-hint">Visible to other operatives.</span>
</div>

<label><input class="cy-checkbox" type="checkbox" checked> Encrypt</label>

<button class="cy-btn cy-btn--danger">Purge</button>

<output class="cy-alert cy-alert--success">Upload complete.</output>
<span class="cy-badge cy-badge--warning">BETA</span>

Reference

Tokens (tokens.css)

| Custom property | Purpose | |-----------------|---------| | --cy-neon-cyan / --cy-neon-pink / --cy-neon-purple | accent hues | | --cy-neon-gold | brass-gold accent — warm, unlit, and carries no status meaning (unlike --cy-warning) | | --cy-bg / --cy-surface | background & card surfaces | | --cy-text / --cy-heading | body & heading text | | / / / | raw RGB triplets for theme-aware rgba() glows | | --cy-font-display / --cy-font-body / --cy-font-mono | type stacks (you load the fonts) | | --cy-font-terminal | .cy-terminal only — asks for a Nerd Font (powerline separators, file icons), falls through to --cy-font-mono | | --cy-ease | shared easing curve | | --cy-radius-sm / --cy-radius / --cy-radius-lg | corner radii | | --cy-border-width | shared border thickness | | --cy-space-xs / --cy-space-sm / --cy-space-md / --cy-space-lg / --cy-space-xl | 4px-based spacing scale | | --cy-focus-width / --cy-focus-offset / --cy-focus-color | shared :focus-visible ring, used by every interactive element | | --cy-success / --cy-warning / --cy-danger / --cy-info | status colours | | --cy-backdrop | modal scrim — stays dark in both themes, since a scrim's job is to push the page behind it away | | --cy-disabled-opacity | opacity applied to disabled controls |

Effects (effects.css)

| Class | Effect | |-------|--------| | .cy-glow · --pink · --purple · --gold | neon box-shadow glow | | .cy-text-glow · --pink · --gold | neon text-shadow glow | | .cy-glitch | RGB-split glitch on text | | .cy-scanlines | CRT scanline overlay (on any container) | | .cy-grid-bg | animated moving grid backdrop | | .cy-cursor | blinking terminal _ cursor |

Components (components.css)

| Class | Component | |-------|-----------| | .cy-btn · .cy-btn--pink · .cy-btn--secondary · .cy-btn--danger (+ --sm, --lg) | neon outline button with hover-glow | | .cy-card | glassmorphism holo card with edge glow |

Forms (forms.css)

| Class | Element | |-------|---------| | .cy-field | wrapper for label + control + hint/error | | .cy-label | field label | | .cy-input | <input>, <textarea>, <select> (+ --sm, --lg) | | .cy-checkbox · .cy-radio | applied to the real input | | .cy-hint · .cy-error | helper and error text |

<div class="cy-field">
  <label class="cy-label" for="email">Email</label>
  <input class="cy-input" id="email" type="email" required
         aria-invalid="true" aria-describedby="email-error">
  <span class="cy-error" id="email-error">Enter a valid address.</span>
</div>

Invalid styling uses :user-invalid, so fields only turn red after the user interacts — not on page load. Drive it manually with aria-invalid="true".

Containers (containers.css)

| Class | Purpose | |-------|---------| | .cy-accordion · .cy-accordion__body | disclosure on <details> — styles summary itself, so the wrapper class is the whole contract | | .cy-modal | dialog on <dialog>, with a blurred ::backdrop | | .cy-terminal · .cy-terminal__bar | code window; styles a scoped <pre>. Add .cy-scanlines and .cy-cursor for the CRT treatment | | .cy-popover | toggle-tip on the native popover attribute — keyboard-reachable, no JavaScript |

All three are native elements, so the behaviour is the browser's: <details> handles its own keyboard and open state, and <dialog> brings focus trapping, Esc and the top layer.

Open the modal with showModal(), not show(). Only showModal() traps focus, wires up Esc, and renders ::backdrop at all — show() gives a non-modal box with none of it, and nothing for the kit to paint.

<dialog class="cy-modal" id="m">…</dialog>
<script>m.showModal()</script>

Feedback (feedback.css)

| Class | Purpose | |-------|---------| | .cy-alert | inline message (+ --info --success --warning --danger) | | .cy-toast · .cy-toast-container | floating message (+ --bottom) | | .cy-badge | status pill (+ variants, --outline) | | .cy-spinner | indeterminate loader | | .cy-progress | determinate bar, on the native <progress> element | | .cy-sr-only | visually-hidden text |

Prefer native elements over ARIA roles where one exists — the browser gets the semantics right for free. <output> has an implicit role="status", so use it for the "polite" cases (info/success alerts, the spinner, toasts):

<output class="cy-alert cy-alert--info">Connection established.</output>

<output class="cy-spinner">
  <span class="cy-sr-only">Loading…</span>
</output>

alert is assertive and has no native-element equivalent, so warnings and errors still need the explicit role — CSS cannot make a red box mean "error":

<div class="cy-alert cy-alert--danger" role="alert">Breach detected.</div>

Progress is the native <progress> element — accessible by default, no ARIA required:

<progress class="cy-progress" value="66" max="100"></progress>

Omit value for an indeterminate bar; the element handles that itself. The div-and-fill progress pair was removed in 0.5.0 — see the CHANGELOG if you were using it.

Toasts ship as styling only — no JavaScript in this package. Show one with:

const container = document.querySelector('.cy-toast-container');
const toast = document.createElement('output');
toast.className = 'cy-toast cy-toast--success';
toast.textContent = 'Package published.';
container.append(toast);
setTimeout(() => toast.remove(), 4000);

Navigation (navigation.css)

| Class | Purpose | |-------|---------| | .cy-nav · .cy-nav__brand | top navigation bar | | .cy-breadcrumb | breadcrumb trail on an <ol> | | .cy-sidebar · .cy-sidebar__title | vertical section nav on a <ul>; the page owns its width |

None of the three has an --active modifier, deliberately. Mark the current item with aria-current="page" and the kit styles it from that attribute, so what a screen reader announces and what the eye sees cannot drift apart.

Table (table.css)

| Class | Purpose | |-------|---------| | .cy-table | data table (+ --striped --compact) | | .cy-table-scroll | horizontal scroll container for a wide table |

A scrollable box needs to be reachable without a mouse, so give the container tabindex="0" and a label:

<section class="cy-table-scroll" tabindex="0" aria-label="Reactor readings">
  <table class="cy-table cy-table--striped">…</table>
</section>

Theming

Every colour, space, radius and font in the kit comes from a custom property, so one override reaches every rule that uses it:

:root { --cy-neon-cyan: #39ff14; }   /* acid green, glows included */

One property per colour, and the glows follow it. Every translucent effect is color-mix(in srgb, var(--cy-neon-cyan) 50%, transparent) — mixed from the same token you just set, so a hue change carries its glow with it.

Until 0.8.0 each colour needed a second --cy-*-rgb property holding its raw channels, and setting the hue without the twin left every glow on the old colour. Those are gone. If you were overriding them, drop the twin and keep the hue.

The light theme is a second palette rather than an inversion. Each neon hue is darkened until it clears WCAG AA against a pale background:

<html data-theme="light">

| data-theme | Result | | --- | --- | | absent | dark, always — the kit's default look | | "auto" | follows prefers-color-scheme | | "light" / "dark" | that theme, whatever the OS says |

Following the OS is opt-in rather than automatic, and that is deliberate: prefers-color-scheme: light matches when a visitor has expressed no preference, not only when they have chosen light. Keying off the absence of the attribute would flip a neon kit to light for most visitors and restyle every site already using it. "auto" is static markup, so it still costs no JavaScript.

Overriding the kit

Every rule ships inside the cyberpunk-ui cascade layer, and anything you write outside a layer beats a layered rule regardless of specificity:

.cy-btn { border-radius: 0; }   /* wins. No !important, no longer selector. */

That also means the kit loses to your framework's utilities by default, which is the behaviour you want when you are the one composing the page.

Right-to-left

Accent bars, the sidebar marker, the chevron and the table alignment are all written with logical properties, so they mirror on their own under dir="rtl" — no separate stylesheet. The one exception is the <select> arrow: background-position has no inline-axis keyword, so the kit ships a [dir="rtl"] rule to move it.

Accessibility

  • Light theme dims cyan/pink to preserve WCAG contrast.
  • Most animations (glitch, grid, cursor, hover transforms) are disabled under prefers-reduced-motion: reduce. .cy-spinner is the exception: it keeps rotating, just slower and without the decorative glow pulse, because a frozen spinner reads as broken and is essential feedback, not decoration.

Contributing

docs/STATE.md is the map: every token and class that already exists, the conventions an addition has to follow, how releases work, and the known debt. Read it before adding a component.

Support

The kit is MIT and stays that way. If it saved you an afternoon of fighting box-shadow, there is a tip jar — and starring the repo or passing it to someone who builds dark interfaces helps just as much for free.

License

MIT © Vladyslav Novytskyi