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

twilightcss

v0.2.1

Published

Small, fast, production-usable Tailwind v4 runtime for dynamically composed UIs — no build step, 27 KB gz all-in, zero dependencies, differentially tested against the real compiler

Readme

twilightcss

A small, fast Tailwind v4 runtime for dynamic UIs. Call tw("flex px-4 hover:bg-red-500/50") in the browser, get correct Tailwind v4 CSS in the document before the call returns. No built step, config, or dependencies. 27 KB gz all-in.

(if you're familiar with twind, this is the modern equivalent.)

Every release is differentially tested against the real Tailwind compiler, and currently matches it on 20,000 / 20,000 generated utility candidates after normalization, including identical rejection of invalid classes.

import { tw } from "twilightcss";

el.className = tw("flex items-center gap-2 px-4 rounded-md bg-white shadow-md");
// the CSS for those classes exists in <head> before this line runs

When would you want this?

  1. You (or an LLM) want to compose and render HTML at runtime: chat UIs, playgrounds, dynamic artifacts, user-generated or CMS content.
  2. No build step is available: plain script tags, scrict-CSP hosts (VS Code webviews, Electron, sandboxed iframes).
  3. Self-styling components: libraries and embeddable widgets that can't ask the host page to run a tailwind build.

One script tag

For pages you don't build — generated HTML, playgrounds, single-file artifacts — include the drop-in build and nothing else:

<script src="https://cdn.jsdelivr.net/npm/[email protected]"></script>

Zero configuration: it injects Tailwind's theme and preflight, styles everything already on the page, and keeps watching — content added later, including markup streamed in while the page is still parsing, is styled before it paints. tw and observe are available on window.twilightcss if you want them, and including the script twice is a no-op.

This is the same role as Tailwind's own browser build (or, a generation back, the cdn.tailwindcss.com script-tag habit), with a different engine underneath:

| | @tailwindcss/browser | twilightcss drop-in | | ---------------------------------- | ----------------------------------------- | ---------------------------------------- | | size (gz) | 74 KB | 27 KB | | CSS injection | async, full compiler | synchronous | | mutation cost, 20k-element page | ~11.5 ms/frame (rescans classed elements) | 0–1.4 ms/frame (proportional to change) | | production stance | “not for production” | production target |

Both style new content before its first paint; the differences are size, steady-state cost on a mutating page, and intent. Methodology and full numbers: bench/browser/.

Customization uses the same channel as Tailwind's browser builds — a <style type="text/tailwindcss"> tag, anywhere in the page (before or after the script; it can even stream in):

<style type="text/tailwindcss">
  @theme {
    --color-brand-500: oklch(0.7 0.15 200);
    --breakpoint-widescreen: 100rem;
  }
  .btn {
    @apply px-3 py-1.5 rounded-md bg-brand-500 text-white hover:bg-brand-500/80;
  }
</style>

@theme variables validate their whole utility family (bg-brand-500, widescreen:flex, …), and @apply expands exactly as the real compiler expands it: utilities in canonical order, variants as CSS-nesting rules. @utility / @custom-variant blocks aren't parsed yet — see Not included.

As a module

npm install twilightcss

Vanilla

<script type="module">
  import { tw } from "twilightcss";
  document.body.innerHTML = `
    <button class="${tw("px-3 py-1.5 rounded-md bg-blue-600 text-white hover:bg-blue-700")}">
      Styled at runtime
    </button>`;
</script>

React

import { tw } from "twilightcss";

function Badge({ children, tone = "gray" }) {
  return (
    <span
      className={tw(
        "inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium",
        tone === "red" && "bg-red-100 text-red-800",
        tone === "gray" && "bg-gray-100 text-gray-800",
      )}
    >
      {children}
    </span>
  );
}

tw() joins its string arguments (falsy arguments are dropped, so the cond && "classes" idiom works), ensures the CSS exists, and returns the joined class string synchronously.

Styling markup you didn't write

For HTML that arrives at runtime (chat UIs, artifacts, CMS content) point the engine at a container and everything in it gets styled, including what streams in later:

import { observe } from "twilightcss";

const stop = observe(container); // sanitize untrusted HTML first

observe styles the existing tree before it returns, then watches for added elements and class changes. Injection is synchronous and mutation callbacks run before the browser paints, so new content is never shown unstyled. Call the returned function to stop watching. It shares the default engine with tw(), so the two mix freely.

This is the machinery behind the drop-in build — the head-to-head numbers against @tailwindcss/browser there apply here too.

No CDN fetch or eval, style injection via <style> text only — it works under strict Content-Security-Policy (VS Code webviews, Electron, sandboxed iframes).

Configuration

tw and observe share one default engine, created on first use. To give it options, call configure once at startup, before the first tw() or observe() (it throws if you're too late — an engine's options are fixed at creation). This is a module-build affordance: the drop-in script initializes the default engine the moment it loads.

import { configure } from "twilightcss";

configure({ ...options });

createEngine(options) exists for when you genuinely need a second engine — another document (an iframe, a webview) or deliberate isolation. It returns the same tw/observe pair bound to its own stylesheet.

Typography

prose ships as a separate asset, compiled from the real @tailwindcss/typography. Configure it in and it is injected once, when the first prose token appears; if you never use it, you never load it:

import { configure, tw } from "twilightcss";
import { proseCss } from "twilightcss/assets/prose.mjs";

configure({ proseCss });
tw("prose"); // markdown containers get typography styles

Bare prose only for now — the prose-sm / prose-invert modifiers aren't included yet.

Custom themes

Utilities resolve against CSS variables, so extending the theme is how you define new utilities. There is no plugin API or JS config object; the config format is CSS:

import { configure, tw } from "twilightcss";
import { themeCss } from "twilightcss/assets/theme.mjs";

configure({
  themeCss:
    themeCss +
    `
    :root {
      --color-brand-500: oklch(0.7 0.15 200);
      --font-display: Poppins, sans-serif;
      --text-huge: 4rem; --text-huge--line-height: 1.05;
      --breakpoint-widescreen: 100rem;
    }`,
});

tw("bg-brand-500/50 hover:border-brand-500 font-display text-huge widescreen:flex");

One variable enables its whole family: --color-brand-500 makes bg-/text-/border-/ring-/fill-…-brand-500 work with every variant and opacity modifier, and --breakpoint-widescreen adds a widescreen: variant. <style type="text/tailwindcss"> tags with @theme / @apply (see the script-tag section) work in any mode — the engine processes them in whatever document it's attached to. For component classes in JS, ordinary constants compose:

const focusRing =
  "focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-blue-500";

Server-side / static extraction

The core has no DOM dependency and runs anywhere:

import { compile, compileOne } from "twilightcss/core";

const { css, unmatched } = compile(["flex", "px-4", "hover:bg-red-500/50"]);
// css: the stylesheet text, in Tailwind's canonical rule order;
// unmatched: tokens twilight (and Tailwind) reject

Pair with assets/theme.mjs and assets/preflight.mjs (raw CSS strings) to emit complete documents.

Compared to the alternatives

The existing ways to run Tailwind at runtime:

| | min | gz | semantics | sync | status | | -------------------------- | --------- | --------- | ------------------------ | -------------- | ----------------------------------------- | | twind v1 | 46 KB | 17 KB | v3-era, approximate | mostly | unmaintained since ~2022 | | UnoCSS runtime | 195 KB | 52 KB | v3-flavored dialect | async | active, but not Tailwind | | tailwindcss v4 compile() | 299 KB | 77 KB | exact (it is Tailwind) | async init | official; “not for production” in-browser | | twilightcss | 89 KB | 27 KB | v4, compiler-tested | fully sync | this package |

(All bundles measured the same way: esbuild, browser ESM, minified, gzip −9, themes included — twilightcss's figure is the whole shippable stack. Its theme is static CSS, so a consumer that brings its own theme carries just the 63 KB min / 20 KB gz engine.)

A small engine became practical with Tailwind v4, which moved the theme out of JavaScript into CSS custom properties. Where twind and UnoCSS embed every color hex and spacing value as JS data, twilightcss ships Tailwind's @theme as static CSS and emits only references:

  • bg-red-500 → background-color: var(--color-red-500)
  • p-13 → padding: calc(var(--spacing) * 13) (the spacing scale is a multiplication, not a lookup table)
  • bg-red-500/50 → color-mix(in oklab, var(--color-red-500) 50%, transparent) (no color math in JS)

What remains in JS is a candidate parser, a variant engine, and a utility table, and most of that is the compressed Tailwind vocabulary itself.

Correctness

The test suite drives the real Tailwind compiler (tailwindcss, pinned) as an oracle and diffs its output against twilight's for each candidate class, after CSS normalization with lightningcss:

  • Generated corpus: candidates enumerated from the compiler's own class list × variants × modifiers × arbitrary values. Currently 20,000 / 20,000 matching, enforced in CI as a ratchet that can only rise.
  • Negative corpus: invalid classes must be rejected identically — twilight never invents CSS that Tailwind wouldn't produce.
  • Rule order: rules are kept in the compiler's canonical order (checked against its getClassOrder), so when two equal-specificity classes target the same property on one element — p-4 px-2 — the cascade resolves them the way a Tailwind build would. Currently 131,018 / 131,018 ordered pairs agreeing, same ratchet regime.
  • Versioning: the Tailwind version twilight matches is pinned in its devDependencies. Bumping it turns upstream changes into visible test failures instead of silent drift. Current oracle: tailwindcss 4.3.3.

If the compiler and twilight ever disagree, that's a bug in twilight.

Not included (v0.2)

  • @utility / @custom-variant blocks aren't parsed yet (@theme and @apply in <style type="text/tailwindcss"> tags are). Arbitrary values ([mask-image:…], bg-[#123]), arbitrary variants ([&>li]:flex) and @apply are the escape hatches.
  • Legacy-browser fallbacks. The compiler's polyfill output (e.g. color-mix in srgb for pre-2023 engines) is not emitted; twilight targets current browsers and matches the compiler's un-polyfilled output.

License

MIT. The static theme, preflight and typography assets are generated from Tailwind CSS and @tailwindcss/typography (MIT, © Tailwind Labs); see NOTICE. Not affiliated with or endorsed by Tailwind Labs.