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

@pyreon/styler

v0.25.1

Published

Lightweight CSS-in-JS engine for Pyreon

Readme

@pyreon/styler

Lightweight CSS-in-JS engine — styled / css / keyframes / theme, ~3.8KB gzipped.

@pyreon/styler is the CSS-in-JS layer that powers @pyreon/rocketstyle, @pyreon/elements, and every other rocketstyle-derived component. Singleton StyleSheet with FNV-1a class hashing and dedup cache. Static templates resolve once at module load (zero per-render cost); dynamic interpolations re-resolve on theme/prop change with class-cache dedup. ThemeContext is a reactive Pyreon context — whole-theme swaps (user-preference theme switching) propagate through the resolver effect in styled() and re-resolve CSS + swap class names WITHOUT remounting the VNode. SSR-isolated via createSheet(). CSS Nesting passes through to the browser unchanged.

Install

bun add @pyreon/styler @pyreon/core @pyreon/reactivity

Quick start

import { styled, css, keyframes, createGlobalStyle, ThemeProvider } from '@pyreon/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; }
`

const GlobalStyle = createGlobalStyle`
  *, *::before, *::after { box-sizing: border-box; }
  body { margin: 0; font-family: ${({ theme }) => theme.font}; }
`

<ThemeProvider theme={{ colors: { primary: '#0d6efd' }, font: 'Inter, sans-serif' }}>
  <GlobalStyle />
  <Button>Click me</Button>
</ThemeProvider>

API

styled(tag, options?)

Creates a styled Pyreon component from an HTML tag, another component, or a styled component.

const Box = styled('div')`display: flex;`
const StyledLink = styled(Link)`color: blue;`
const Wider = styled(Box)`padding: 24px;`         // wrap an existing styled

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

<Box as="section">Renders as a section</Box>

Transient props ($-prefixed)

const Box = styled('div')`color: ${(p) => (p.$active ? 'blue' : 'gray')};`
<Box $active>$active is used for styling but does NOT reach the DOM.</Box>

Custom prop filtering

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

css

Tagged template for composable CSS fragments — lazy CSSResult, resolved on use.

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

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

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

keyframes

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

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

createGlobalStyle

Global, non-scoped rules:

const GlobalStyle = createGlobalStyle`
  body { margin: 0; }
`

ThemeProvider / useTheme / useThemeAccessor

ThemeContext is a Pyreon reactive context — whole-theme swaps (e.g. user preference dark→light) re-resolve every styled-component's CSS and swap classes in place, no VNode remount.

import { ThemeProvider, useTheme, useThemeAccessor } from '@pyreon/styler'

<ThemeProvider theme={{ colors: { primary: '#0d6efd' } }}>
  <App />
</ThemeProvider>

// Inside a component:
const theme = useTheme()                  // snapshot at call time
const themeFn = useThemeAccessor()        // () => Theme — track inside effects/computeds
effect(() => console.log(themeFn().colors))

TypeScript theme augmentation

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

sheet / createSheet

The singleton sheet manages CSS-rule injection. Use createSheet() for per-request SSR isolation:

import { sheet, createSheet } from '@pyreon/styler'

// SSR
const requestSheet = createSheet()
const html = renderToString(<App />)
const styleTags = requestSheet.getStyleTag()
requestSheet.reset()

@layer support

const sheet = createSheet({ layer: 'components' })
// All scoped rules emitted inside @layer components { ... }

useCSS(cssResult)

Read-only hook for retrieving the resolved class name of a CSSResult — useful for hand-managed JSX paths that need the class without styled().

Low-level

import {
  resolve, resolveValue, normalizeCSS, clearNormCache,
  hash, hashUpdate, hashFinalize, HASH_INIT,
  buildProps, filterProps, isDynamic,
} from '@pyreon/styler'

buildProps / filterProps are the prop-forwarding helpers styled() uses internally — exported for HOC authors who need to recreate the same filter contract.

How it works

Static path — zero runtime cost

Templates with no function interpolations resolve once at module evaluation. The CSS class, rules, and <style> element are pre-computed and cached.

Dynamic path

Templates with function interpolations resolve on every render. A class-cache keyed by ($rocketstyle, $rocketstate) (rocketstyle path) or by $element bundle identity (Element path) skips the resolver pipeline entirely on cache hits. Companion injectRules(rules, key) is the idempotent entry point the compile-time-collapse path uses to ship pre-resolved CSS without re-hashing.

Reactive theme swaps

ThemeProvider wires the theme through createReactiveContextstyled() reads via the accessor inside a renderEffect, so flipping the provider's theme re-resolves CSS and patches className on the same node. No remount.

CSS nesting passes through

Native CSS nesting is forwarded unchanged — &:hover, &::before, nested selectors, and @media queries work as-is in 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

Bundle size

| Library | Minified | Gzipped | | ----------------------- | -----------: | ----------: | | goober | 2.32 KB | 1.31 KB | | @pyreon/styler | 10.13 KB | 3.81 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 | — | — | | Nested composition | 8.3M | 2.2M | 1.4M | 8K | | SSR renderToString | 307K | 69K | 192K | 18K | | styled() factory | 17.3M | 109K | 933K | 18.2M |

Gotchas

  • Theme swaps re-resolve CSS but do NOT remount. A whole-theme swap (<ThemeProvider theme={B}><ThemeProvider theme={C}>) updates the className in place. Identity preservation: pass a STABLE theme object via signal/computed if you want maximum cache reuse.
  • useTheme() returns a snapshot. Inside effects/computeds, use useThemeAccessor() to subscribe to live updates.
  • ThemeProvider requires nativeCompat if used in a compat-layer app — it's already marked. User code in compat-mode apps inheriting from ThemeProvider should preserve that contract.
  • @layer is opt-in, not the default. The singleton sheet does not wrap in @layer.
  • Failed insertRule in production used to be silently swallowed. Current code uses bare process.env.NODE_ENV !== 'production' — bundler-agnostic; do not regress to import.meta.env.DEV or typeof process guards.

Documentation

Full docs: docs.pyreon.dev/docs/styler (or docs/docs/styler.md in this repo).

License

MIT