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

@chalp/vanilla-extract-styled

v2.0.1

Published

A small wrapper/utility for using `@vanilla-extract/recipes` and styled components in your project.

Readme

vanilla-extract-styled

A small wrapper/utility for using @vanilla-extract/recipes and styled components in your project.

This package provides a convenient styled API on top of vanilla-extract recipes and is intended to be published as a library (supports both ESM and CommonJS). The README describes installation, usage examples, and packaging/SSR recommendations.

Installation

We recommend using pnpm (or npm/yarn):

pnpm add @chalp/vanilla-extract-styled
# or
npm install @chalp/vanilla-extract-styled

Peer dependencies

The package requires the following peer dependencies to be present in the project (they are not bundled):

  • react (>=17)
  • react-dom (>=17)
  • @vanilla-extract/css
  • @vanilla-extract/recipes

Install them in your project if they are not already present:

pnpm add react react-dom @vanilla-extract/css @vanilla-extract/recipes -w

Usage

The key function: styled(elemType, ...recipes).

  • elemType — a React element/component: a string tag ('div', 'button', etc.) or a React component (functional or class).
  • ...recipes — any number of arguments, each of which can be:
    • a string — an already prepared className that will be appended to the resulting className;
    • a RuntimeFn<VariantGroups> function from @vanilla-extract/recipes (a recipe function). This function will be called at render time with an options object collected from the component's props.

The return value is a React component. Its props are:

  • all props of the original elemType (for example, for 'button' — standard button attributes);
  • plus automatically inferred variant props that correspond to the passed recipe functions;
  • optional className?: string.

How styled calls a recipe function

The implementation takes each recipe function and calls it like this:

  • it inspects recipeFn.classNames.variants (the keys of variant groups);
  • from the component props, it picks the value for each key — first checking the direct key (e.g. variant), then falling back to the $-prefixed key (e.g. $variant). The direct key takes priority;
  • it constructs an options object and passes it to recipeFn(options);
  • the result (a string of classes) is added to an array of classes.

The final className is the list of all classes (in the order recipes were passed), plus the className from component props (if provided), joined with spaces.

Important: prop forwarding

The implementation distinguishes between native DOM elements ('div', 'button', etc.) and React components (functions/classes):

  • For native DOM elements, any prop starting with $ ("transient prop") is automatically stripped before being forwarded to the DOM. These props are still passed to recipe functions.
  • For React components, all props (including $-prefixed) are forwarded as-is, letting the component handle them.

This lets you use recipe variant names that would conflict with DOM attributes without triggering React warnings:

const Button = styled('button', buttonRecipe);

// $variant → recipe gets { variant: 'primary' }, but <button> DOM does NOT receive $variant
<Button $variant="primary">Click</Button>

// variant → recipe gets { variant: 'primary' }, AND <button> DOM receives variant
<Button variant="primary">Click</Button>

Priority: if both prop and $prop are provided, the unprefixed version takes precedence for the recipe, and the $-prefixed version is ignored.

| Condition | $prop | prop (no prefix) | |-----------|---------|-------------------| | Native element ('div', 'button') | ✅ recipe, ❌ DOM | ✅ recipe, ✅ DOM | | Component (Button, styled wrapper) | ✅ recipe, ✅ props | ✅ recipe, ✅ props | | Both provided simultaneously | fallback | priority |

About refs

The function returns a simple functional component without forwardRef. If you need ref forwarding, wrap the result in React.forwardRef manually.

Examples

  1. Simple example: a string class
import styled from '@chalp/vanilla-extract-styled';

const Box = styled('div', 'my-base-class');

// In render
// <Box className="local" /> => element with class="my-base-class local"
  1. Recipe function (TypeScript)

Assume you have a recipe generated by @vanilla-extract/recipes:

import { recipe } from '@vanilla-extract/recipes';

export const buttonRecipe = recipe({
  base: 'btn',
  variants: {
    variant: {
      primary: 'btn--primary',
      ghost: 'btn--ghost',
    },
    size: {
      small: 'btn--sm',
      medium: 'btn--md',
    },
  },
});

Using styled:

import styled from '@chalp/vanilla-extract-styled';
import { buttonRecipe } from './button.css';

const Button = styled('button', buttonRecipe);

// In JSX
// <Button variant="primary" size="small">OK</Button>
// styled will call buttonRecipe({ variant: 'primary', size: 'small' })
// and add the resulting className to the element
  1. Multiple recipes and string classes
const Card = styled('div', baseCardRecipe, shadowRecipe, 'ux-card');

// Order matters: classes will be in the order [baseCard, shadow, 'ux-card', props.className]
  1. Custom React component
const Link = ({ href, className, children, ...rest }) => (
  <a href={href} className={className} {...rest}>{children}</a>
);

const StyledLink = styled(Link, linkRecipe);

// <StyledLink href="/" active>Home</StyledLink>
// All props are passed to Link, and linkRecipe will be called with appropriate variant props

Types (short)

The returned component is typed as:

ComponentType< ComponentPropsWithoutRef & VariantPropsFromMerged & { className?: string } >

This means TypeScript will try to infer variant props from the provided recipe functions (VariantPropsFromMerged<T>).

TypeScript example (detailed)

Below is a more detailed example of types and real usage. Types are taken from src/styled.ts and shown here to illustrate how types are inferred in user code.

// Type imports (assumes @vanilla-extract/recipes is installed)
import type { ComponentType, ElementType, ComponentPropsWithoutRef } from 'react';
import type { RuntimeFn, RecipeVariants } from '@vanilla-extract/recipes';

// Helper utilities (as in src/styled.ts)
type Resolve<T> = { [Key in keyof T]: T[Key]; } & {};
type BooleanMap<T> = T extends 'true' | 'false' ? boolean : T;
type RecipeStyleRule = any; // ComplexStyleRule | string — simplified for README
type VariantDefinitions = Record<string, RecipeStyleRule>;
type VariantGroups = Record<string, VariantDefinitions>;

type VariantSelection<Variants extends VariantGroups> = {
  [VariantGroup in keyof Variants]?: BooleanMap<keyof Variants[VariantGroup]> | undefined;
};

// RecipeOrClass — either a runtime recipe function or a className string
type RecipeOrClass = RuntimeFn<VariantGroups> | string;

type EmptyObject = NonNullable<unknown>;

// Main type: merges variant prop types from all provided recipe functions
type VariantPropsFromMerged<T extends readonly RecipeOrClass[]> = T[number] extends infer V
  ? V extends RuntimeFn<VariantGroups>
    ? RecipeVariants<V>
    : EmptyObject
  : EmptyObject;

// Return component type
type StyledReturn<
  E extends ElementType,
  T extends readonly RecipeOrClass[],
> = ComponentType<ComponentPropsWithoutRef<E> & VariantPropsFromMerged<T> & { className?: string }>;

Usage example (in your code):

import React from 'react';
import styled from '@chalp/vanilla-extract-styled';
import { recipe } from '@vanilla-extract/recipes';

// Example recipe (typical @vanilla-extract/recipes code)
export const buttonRecipe = recipe({
  base: 'btn',
  variants: {
    variant: {
      primary: 'btn--primary',
      ghost: 'btn--ghost',
    },
    size: {
      small: 'btn--sm',
      medium: 'btn--md',
    },
  },
});

// 1) styled with an HTML tag
const Button = styled('button', buttonRecipe);

// TypeScript will infer props as:
// ComponentPropsWithoutRef<'button'> & VariantPropsFromMerged<[typeof buttonRecipe]> & { className?: string }

// Usage in JSX:
// <Button variant="primary" size="small">OK</Button>

// 2) styled with a custom React component
type LinkProps = { href: string; children?: React.ReactNode };
const Link = ({ href, className, children, ...rest }: LinkProps & { className?: string }) => (
  <a href={href} className={className} {...rest}>{children}</a>
);

const linkRecipe = recipe({
  base: 'link',
  variants: {
    active: { true: 'link--active' },
  },
});

const StyledLink = styled(Link, linkRecipe);

// Prop types: LinkProps & VariantPropsFromMerged<[typeof linkRecipe]> & { className?: string }
// Usage: <StyledLink href="/" active>Home</StyledLink>

Notes

  • The first argument to styled is either a string tag ('div', 'button') or a React component.
  • You can pass any number of arguments afterward: strings (pre-made class names) and/or recipe functions (RuntimeFn<VariantGroups>).
  • VariantPropsFromMerged<T> merges the variant types (RecipeVariants) from all provided recipe functions.
  • If you pass recipe functions that share variant group names, the resulting props will be merged accordingly (the behavior is defined by RecipeVariants types from @vanilla-extract/recipes).

Helpers

The package exports color utilities grouped under the Color namespace, plus the ColorSpace enum:

import { Color, ColorSpace } from '@chalp/vanilla-extract-styled';
  • Color.darken(value, amount, colorSpace?) — mixes the color with black. Returns a CSS color-mix(...) expression.

    • Example: Color.darken('#ffcc00', 0.15)"color-mix(in srgb, #ffcc00 15%, black)".
  • Color.lighten(value, amount, colorSpace?) — mixes the color with white.

    • Example: Color.lighten('#002244', 0.1)"color-mix(in srgb, #002244 10%, white)".
  • Color.opacify(value, amount, colorSpace?) — mixes the color with transparent.

    • Example: Color.opacify('#000000', 0.5)"color-mix(in srgb, #000000 50%, transparent)".
  • ColorSpace — enum with values 'srgb' (default) and 'oklab'.

amount is a number from 0 to 1. colorSpace defaults to ColorSpace.SRGB.

Examples of using helpers in recipes:

import { recipe } from '@vanilla-extract/recipes';
import { Color, ColorSpace } from '@chalp/vanilla-extract-styled';

export const alertRecipe = recipe({
  base: 'alert',
  variants: {
    tone: {
      info: { background: Color.opacify('#0af', 0.1) },
      warn: { background: Color.darken('#ffcc00', 0.05) },
      muted: { background: Color.lighten('#002244', 0.1, ColorSpace.OKLAB) },
    },
  },
});

Recommendations

  • When styling native DOM elements ('div', 'button'), use the $ prefix for props that should only be consumed by recipes and not forwarded to the DOM (e.g., $variant, $size). See "Important: prop forwarding" above.
  • styled does not automatically forward refs — wrap the result with React.forwardRef if you need a ref.

Contributing and development

  • Follow the project rules (ESLint, Airbnb styles).
  • Run linting and build before creating a PR:
pnpm --filter @chalp/vanilla-extract-styled lint
pnpm --filter @chalp/vanilla-extract-styled build
# add tests/vitest as needed

License

MIT