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

@algrith/styledwind

v0.1.4

Published

A Tailwind-native styled() utility for React and Next.js, born from twin.macro breaking under Next.js's Turbopack — plus a live dev-time invalid-class validator with an in-browser overlay.

Readme

Algrith Styledwind

A Tailwind-native styled() utility for React and Next.js — with nested selectors, variants(), merge-aware override precedence, and a live dev-time invalid-class validator with an in-browser overlay.

Algrith Styledwind is released under the MIT license. npm downloads/month

Why this exists

twin.macro was the standard way to combine Tailwind utility classes with styled-components-style CSS-in-JS — but it depends on babel-plugin-macros to do its work at build time. Next.js's Turbopack bundler doesn't support Babel plugins or macros at all, and Turbopack has moved from an opt-in flag to the default (and, from Next.js 16 onward, effectively the only supported) bundler. That leaves twin.macro with no way to run in a current Next.js app.

Styledwind exists to fill that gap: a Tailwind-native styled() API that needs no Babel macro, no custom webpack loader, and no bundler-specific integration at all — just plain tagged template literals, evaluated at runtime.

Features

  • styled.div, styled.button, styled(Component) — works on any intrinsic HTML tag or any ref-forwarding React component (including third-party ones, e.g. Ant Design, MUI).
  • Nested selectors — .child { ... }, comma-separated selectors, and &-compounding (&.active { ... }, &:hover { ... }) resolve against the component’s root at runtime. Nested tw classes are applied to matching DOM nodes after mount and re-applied when React updates a className (on a child or on the root itself), when prop-driven nested values change, and when an edited template arrives through Fast Refresh — so compound selectors stay in sync with state-driven classes and style edits show up without a page reload.
  • Override precedence that matches CSS — when a nested block matches an element, it wins any conflicting Tailwind class the element already has, including the root's own classes under an &-compound such as &.open { ... }, instead of falling back to Tailwind's arbitrary internal stylesheet order. The overridden class is restored as soon as the nested block stops matching. This applies within one styled component; when two different styled components both target the same element with conflicting utilities, neither removes the other's classes and Tailwind's stylesheet order decides, so they can't undo each other.
  • tw template tag — merges Tailwind classes via tailwind-merge, so conflicting utilities resolve predictably.
  • variants() helper — prop-driven class selection that keeps every possible class as literal, scanner-visible text, so Tailwind's production build never silently drops a class assembled at runtime.
  • Live class validation (optional) — a dev-only watcher checks every static tw\...`` call against Tailwind v4's real compiler and shows a full-screen overlay (plus a terminal warning) the instant an invalid class is saved.
  • No Babel, no macros, no bundler plugin required — works identically under Webpack, Turbopack, Vite, or any other bundler, since there's no build-time transform for anything to break.

Framework support

| Feature | Next.js | Vite / CRA / plain React | | ---------------------------------------- | :-----: | :----------------------: | | styled, tw, variants | ✅ | ✅ | | Live validator + overlay (auto-wired) | ✅ | — | | Live validator + overlay (manual script) | ✅ | ✅ |

The core styled API has no Next.js-specific code in it at all — it's plain React plus clsx/tailwind-merge, so it works the same way in any React 18+ project with Tailwind CSS configured, Next.js or not. The live validator auto-spawns via withStyledwindGuard() in next.config.ts specifically because that's the one hook every Next.js app already has; outside of Next.js, run it as a standalone CLI process instead — see Live class validation below.

Install

npm install @algrith/styledwind
yarn add @algrith/styledwind
pnpm add @algrith/styledwind

Requirements: React 18+, Tailwind CSS v4+. Next.js 15+ only if you want the live validator auto-wired — see below for using it without Next.js.

Quick start

import { styled, tw } from '@algrith/styledwind';

export const Card = styled.div`
  ${tw`flex flex-col gap-3 rounded-lg bg-white p-4 shadow-sm`};

  h1 {
    ${tw`text-xs font-semibold tracking-[0.1em] text-zinc-500`};
  }

  .step {
    ${tw`text-zinc-500`};

    &.done {
      ${tw`text-zinc-900`};
    }
  }
`;

Nested selectors run on the client after mount: matching nodes receive the nested tw classes via the DOM. If a child later changes className in React (for example adding done), nested rules that depend on those classes are applied again automatically.

.step / &.done still need those class names present on elements under the styled root — Styledwind does not invent them; it only attaches the nested utilities when the selectors match.

The same applies to the root itself, so a state class can override the root's own utilities:

export const Drawer = styled.aside`
  ${tw`fixed inset-y-0 right-0 translate-x-full transition-transform`};

  &.open {
    ${tw`translate-x-0`};
  }
`;

<Drawer className={isOpen ? 'open' : undefined} />;

While .open is present, translate-x-full is held off the element so translate-x-0 wins; it comes back when .open is removed.

Nested values that come from props (${(props) => ...} inside a nested block) are re-applied whenever they change, and so are edits to the template during development with Fast Refresh.

Nested classes are not present in server-rendered HTML; they appear after hydration.

This works exactly the same whether Card is rendered inside a Next.js app, a Vite app, Create React App, Remix, or any other React 18+ setup — styled, tw, and variants have no framework dependency.

Third-party components

import { Button } from 'antd';
import { styled, tw } from '@algrith/styledwind';

export const PrimaryButton = styled(Button)`
  ${tw`rounded-full px-6 font-medium`};
`;

Works with any component that forwards its ref and spreads unknown props onto its root DOM node (the same requirement styled-components and emotion place on wrapped components). In development, styledwind warns in the console if a component with nested selectors never attaches a ref, so a silent styling failure doesn't go unnoticed.

Styled components forward refs too. A ref passed to one — directly, or by a wrapper such as Ant Design's Dropdown or Tooltip attaching itself to its trigger — receives the root DOM node, alongside the ref Styledwind keeps for nested selectors, so neither replaces the other.

Prop-driven variants

import { styled, variants } from '@algrith/styledwind';

type BadgeProps = { color?: 'red' | 'blue' };

export const Badge = styled.span<BadgeProps>`
  ${(props) =>
    variants(props.color, {
      red: 'bg-red-100 text-red-700',
      blue: 'bg-blue-100 text-blue-700',
    })}
`;

Always spell out full class names inside the map object — Tailwind's build-time scanner reads literal source text, not runtime values, so a dynamically-constructed class (e.g. `bg-${color}-100`) is invisible to it and silently produces no CSS in production.

Live class validation

Catch typos and invalid Tailwind classes the moment you save — not after a production build silently drops them.

With Next.js

1. Wrap your Next.js config:

// next.config.ts
import type { NextConfig } from 'next';
import { withStyledwindGuard } from '@algrith/styledwind/next';

const nextConfig: NextConfig = {
  /* ... */
};

export default withStyledwindGuard(nextConfig, {
  entryCss: 'app/globals.css', // optional — auto-detected if omitted
});

withStyledwindGuard spawns the watcher process alongside next dev automatically — no separate script or terminal tab needed.

With Vite, Create React App, or any other setup

There's no next.config.ts hook to spawn the watcher from outside of Next.js, so run it as its own process instead, using the CLI the package ships:

{
  "scripts": {
    "dev": "concurrently \"vite\" \"styledwind-watch\""
  }
}

(concurrently — or npm-run-all, or two terminal tabs — just needs to run your normal dev server and styledwind-watch side by side.) Configure it with the same environment variables withStyledwindGuard sets internally:

{
  "scripts": {
    "dev": "concurrently \"vite\" \"STYLEDWIND_ENTRY_CSS=src/index.css STYLEDWIND_GUARD_PORT=47821 styledwind-watch\""
  }
}

Both variables are optional — STYLEDWIND_ENTRY_CSS is auto-detected against common Vite/CRA paths (src/index.css, src/App.css, src/main.css, etc.) the same way it auto-detects Next.js's globals.css, and STYLEDWIND_GUARD_PORT defaults to 47821.

Mounting the overlay (both setups)

Mount <StyledwindGuardOverlay /> once, near the root of your app:

import { StyledwindGuardOverlay } from '@algrith/styledwind/overlay';

export default function App() {
  return (
    <>
      {/* ...your app... */}
      <StyledwindGuardOverlay />
    </>
  );
}

With this in place, saving a file containing an invalid Tailwind class (inside a static tw\...`call) logs it to your terminal and shows a full-screen overlay in the browser until it's fixed. Classes inside interpolated${...}` expressions can't be statically checked and are skipped.

Config options

| Option | Env var (standalone CLI) | Default | Description | | ---------- | ------------------------ | ------------- | ------------------------------------------------------------------------------------------- | | entryCss | STYLEDWIND_ENTRY_CSS | auto-detected | Path to your Tailwind entry CSS, relative to the project root. | | port | STYLEDWIND_GUARD_PORT | 47821 | WebSocket port. Must match between the watcher and <StyledwindGuardOverlay port={...} />. |

API reference

| Export | Import from | Description | | ------------------------ | ----------------------------- | ------------------------------------------------------------ | | styled | @algrith/styledwind | Tagged-template factory — styled.div, styled(Component). | | tw | @algrith/styledwind | Merges Tailwind classes via tailwind-merge. | | variants | @algrith/styledwind | Prop-driven, scanner-safe class selection. | | withStyledwindGuard | @algrith/styledwind/next | Next.js config wrapper — spawns the dev-time watcher. | | StyledwindGuardOverlay | @algrith/styledwind/overlay | Client component — renders the invalid-class overlay. | | styledwind-watch | CLI (via npx or a script) | Standalone watcher process for non-Next.js setups. |

Limitations

  • The validator only checks statically-written tw\...`calls. Anything containing${...}` interpolation is skipped, since there's no literal string to check without evaluating the component at runtime.
  • Nested-selector styling is applied on the client via document.querySelectorAll after mount (and re-applied when the styled subtree’s DOM / class attributes change). It does not affect server-rendered HTML before hydration, so there can be a brief mismatch until the client runs. Nested utilities are merged onto nodes with classList alongside any React className. React remains the source of truth for the classes you set in JSX; Styledwind adds and removes the nested utilities it owns, and while a nested block matches it also holds off any of the element's own classes that conflict with it (per tailwind-merge), restoring them when the block stops matching. Conflicts among an element's own classes are left alone, and so are classes another styled component applied to the same element.
  • If something keeps undoing a styled component's nested classes (another script fighting it over the same element), Styledwind stops re-applying that component's nested classes after 500 updates in a second and warns in development, rather than freezing the page.
  • Templates are CSS text, not JavaScript, so a // comment inside a styled template is read as part of the next selector, making it invalid. Styledwind skips that nested block (with a console warning in development) rather than breaking the component, but its classes won't apply. Keep comments above the styled call instead.
  • The overlay is advisory, not blocking: your app keeps running and serving requests even while an invalid class is showing.

Contributing

  • Missing something or found a bug? Report here.
  • Local setup, project layout, and how to test changes against a real app before opening a pull request: see CONTRIBUTING.md.

License

MIT