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

@vitus-labs/styler

v2.7.2

Published

Lightweight CSS-in-JS engine for vitus-labs packages

Readme

@vitus-labs/styler

A lightweight CSS-in-JS engine for React. Drop-in replacement for styled-components at a fraction of the size.

4.82 KB gzipped | React 19+ | SSR & static export ready | TypeScript strict

Installation

npm install @vitus-labs/styler
# or
bun add @vitus-labs/styler

React 19+ is required as a peer dependency.

Quick Start

import { styled, css, ThemeProvider } from '@vitus-labs/styler'

const Button = styled('button')`
  display: inline-flex;
  align-items: center;
  padding: 8px 16px;
  border-radius: 4px;
  background: ${({ theme }) => theme.colors.primary};
  color: white;
  cursor: pointer;

  &:hover {
    opacity: 0.9;
  }
`

function App() {
  return (
    <ThemeProvider theme={{ colors: { primary: '#3b82f6' } }}>
      <Button>Click me</Button>
    </ThemeProvider>
  )
}

API

styled(tag)

Creates a styled React component from an HTML tag or another component.

// HTML tag
const Box = styled('div')`
  display: flex;
`

// Shorthand (via Proxy)
const Box = styled.div`
  display: flex;
`

// Wrapping a component
const StyledLink = styled(Link)`
  color: blue;
  text-decoration: none;
`

Dynamic interpolations

Function interpolations receive all props plus the current theme:

const Text = styled('p')`
  color: ${({ theme }) => theme.colors.text};
  font-size: ${(props) => props.$size || '16px'};
`

Polymorphic as prop

Render as a different element at runtime:

const Box = styled('div')`padding: 16px;`

<Box as="section">Renders as a &lt;section&gt;</Box>

Ref forwarding

All styled components forward refs via React.forwardRef:

const Input = styled('input')`border: 1px solid #ccc;`

const ref = useRef<HTMLInputElement>(null)
<Input ref={ref} />

Transient props

Props prefixed with $ are not forwarded to the DOM:

const Box = styled('div')`
  color: ${(p) => p.$active ? 'blue' : 'gray'};
`

// $active is used for styling but won't appear on the <div>
<Box $active />

Custom prop filtering

const Box = styled('div', {
  shouldForwardProp: (prop) => prop !== 'size',
})`
  font-size: ${(p) => p.size}px;
`

css

Tagged template for composable CSS fragments:

const flexCenter = css`
  display: flex;
  align-items: center;
  justify-content: center;
`

const Card = styled('div')`
  ${flexCenter};
  padding: 16px;
`

Supports conditional patterns:

const Box = styled('div')`
  display: flex;
  ${(props) => props.$bordered && css`
    border: 1px solid #e0e0e0;
    border-radius: 4px;
  `};
`

keyframes

Creates @keyframes animations:

const fadeIn = keyframes`
  from { opacity: 0; }
  to { opacity: 1; }
`

const FadeBox = styled('div')`
  animation: ${fadeIn} 300ms ease-in;
`

createGlobalStyle

Injects global CSS rules (not scoped to a class):

const GlobalStyle = createGlobalStyle`
  *, *::before, *::after {
    box-sizing: border-box;
  }

  body {
    margin: 0;
    font-family: ${({ theme }) => theme.font};
  }
`

// Renders nothing, injects CSS when mounted
<GlobalStyle />

ThemeProvider & useTheme

Provides a theme object to all nested styled components via React context:

const theme = {
  colors: { primary: '#3b82f6', text: '#111' },
  spacing: (n: number) => `${n * 4}px`,
}

<ThemeProvider theme={theme}>
  <App />
</ThemeProvider>

Access the theme from any component:

const MyComponent = () => {
  const theme = useTheme()
  return <div style={{ color: theme.colors.primary }} />
}

TypeScript theme augmentation

Extend DefaultTheme for strict typing across your app:

declare module '@vitus-labs/styler' {
  interface DefaultTheme {
    colors: { primary: string; text: string }
    spacing: (n: number) => string
  }
}

sheet & createSheet

The singleton sheet manages CSS rule injection. For SSR, use createSheet for per-request isolation:

import { createSheet } from '@vitus-labs/styler'

// Server-side rendering
const sheet = createSheet()
const html = renderToString(<App />)
const styleTags = sheet.getStyleTag()  // <style data-vl="">...</style>
sheet.reset()

@layer support

Wrap all scoped rules in a CSS Cascade Layer:

import { createSheet } from '@vitus-labs/styler'

const sheet = createSheet({ layer: 'components' })

HMR cleanup

if (import.meta.hot) {
  import.meta.hot.accept(() => {
    sheet.clearAll()
  })
}

How It Works

FOUC-free SSR & Static Export

Styler uses React 19's <style precedence> resource system. Each styled component renders an inline <style href="..." precedence="medium"> element alongside its markup. React automatically hoists these to <head>, deduplicates by href, and preserves them during hydration. This eliminates Flash of Unstyled Content (FOUC) for both SSR streaming and static export (e.g. next export) with zero configuration.

Static path (zero runtime cost)

Templates with no function interpolations are resolved once at component creation time. The CSS class, rules, and <style> element are pre-computed and cached. The React component is a thin forwardRef wrapper that reuses the same cached element reference on every render.

// Class + <style> element computed once at import time, not on every render
const Box = styled('div')`
  display: flex;
  padding: 16px;
`

Dynamic path

Templates with function interpolations resolve on every render. A useRef cache skips sheet.prepare() and <style> element creation when the resolved CSS text hasn't changed between renders.

Single-pass prop builder

Styled components build the final props object in a single allocation and loop — className merging, ref injection, and prop filtering (transient $ props, DOM validation) happen in one pass instead of three separate object spreads.

CSS Nesting

Native CSS nesting is supported out of the box. The engine passes CSS through without transformation, so &:hover, &::before, nested selectors, and @media queries work as-is in all modern browsers.

const Card = styled('div')`
  padding: 16px;

  &:hover {
    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
  }

  & > h2 {
    margin: 0 0 8px;
  }

  @media (min-width: 768px) {
    padding: 24px;
  }
`

Benchmarks

All benchmarks run via Vitest bench on the same machine. React is externalized in all bundle measurements.

Bundle Size

| Library | Minified | Gzipped | |---------|--------:|--------:| | goober | 2.32 KB | 1.31 KB | | @vitus-labs/styler | 12.21 KB | 4.82 KB | | styled-components | 44.93 KB | 17.89 KB | | @emotion/react + styled | 48.26 KB | 16.59 KB |

Performance (ops/sec, higher is better)

| Benchmark | styler | styled-components | @emotion | goober | |-----------|-------:|-------------------:|---------:|-------:| | css() creation | 25.2M | 9.0M | 2.2M | 26K | | css() with interpolations | 24.9M | 5.6M | 2.3M | 28K | | Template resolution | 21.4M | 3.9M | — | — | | Dynamic interpolation | 12.4M | 13.4M | — | — | | Nested composition | 8.3M | 2.2M | 1.4M | 8K | | SSR renderToString | 307K | 69K | 192K | 18K | | styled() factory | 17.3M | 109K | 933K | 18.2M |

Styler is 2.8–1034x faster than alternatives across css creation, composition, and SSR. The styled() factory is now essentially tied with goober (17.3M vs 18.2M ops/s) while being 158x faster than styled-components and 18x faster than Emotion. The only benchmark where styler doesn't lead is dynamic function interpolation, where styled-components' manual flatten is ~8% faster.

Migrating from styled-components

The API is intentionally compatible. Most code works by changing the import:

- import styled, { css, keyframes, createGlobalStyle, ThemeProvider } from 'styled-components'
+ import { styled, css, keyframes, createGlobalStyle, ThemeProvider } from '@vitus-labs/styler'

Key differences:

| Feature | styled-components | @vitus-labs/styler | |---------|------------------|-------------------| | Bundle size | ~16 KB gz | 4.82 KB gz | | styled.div shorthand | Yes | Yes | | as prop | Yes | Yes | | Ref forwarding | Yes | Yes | | Transient $ props | Yes | Yes | | shouldForwardProp | .withConfig() | Second argument | | SSR | ServerStyleSheet | Automatic (React 19 resources) | | Static export | Manual setup | FOUC-free out of the box | | CSS nesting | Preprocessed | Native (no transform) | | attrs() | Yes | Use @vitus-labs/attrs |

Security

Interpolated values are trusted by contract — the same model as styled-components and Emotion. Values are stringified and concatenated into CSS without sanitization, so interpolating untrusted third-party data (user input, URL params, API responses) can inject arbitrary CSS rules:

// ❌ DON'T — untrusted input can break out of the declaration
const Avatar = styled.div`background-image: url(${userProvidedUrl});`

// ✅ DO — validate/allowlist untrusted values before interpolating
const safe = ALLOWED_COLORS.has(input) ? input : 'inherit'
const Tag = styled.span`color: ${safe};`

CSS injection can't execute script, but it can exfiltrate data (attribute selectors + background-image: url(...) beacons) and deface the page. Treat template interpolations like you'd treat dangerouslySetInnerHTML: developer-controlled values only.

SSR output is safe against </style> breakout — React 19 escapes style-element children, and the manual getStyleTag() path neutralizes the sequence as well.

License

MIT