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

varena

v0.3.0

Published

A framework-agnostic, type-safe styling library for building component variants and design tokens, with first-class slots support. Inspired by `stitches`.

Readme

varena

A framework-agnostic, type-safe styling library for building component variants and design tokens, with first-class slots support. Inspired by Stitches.

Features

  • Framework agnostic: works anywhere you can use plain strings and style objects.
  • Type-safe variants: strongly typed variant names and values.
  • Slot-based components: define slot classes for root, icon, label, and any custom slot.
  • Compound variants: apply slot classes when multiple variant conditions match.
  • Design tokens: generate CSS custom properties and var(...) references with types.
  • Tiny utilities: cx for class merging and sx for style merging.

Installation

pnpm add varena
# or
npm install varena
# or
yarn add varena

Quick Start

Button with Tailwind CSS

project/
├── lib/
│   └── varena.ts          # varena instance with twMerge
└── components/
    └── button.tsx         # button component + styles
pnpm add varena tailwind-merge
// lib/varena.ts
import { create } from "varena";
import { twMerge } from "tailwind-merge";

export const { createStyles, createTokens } = create({
  mergeClasses: twMerge,
});
// components/button.tsx
import type { InferComponentStylesConfig } from "varena";

import * as React from "react";

// Note: `@/` is a path alias, use your project's import path
import { createStyles } from "@/lib/varena";

const ButtonStyles = createStyles({
  slots: {
    root: "inline-flex items-center justify-center rounded-md px-4 py-2 font-medium transition-colors",
    icon: "mr-2 h-4 w-4",
  },
  variants: {
    disabled: {
      true: { root: "opacity-50 cursor-not-allowed" },
    },
    variant: {
      solid: { root: "bg-blue-600 text-white hover:bg-blue-700" },
      outline: { root: "border border-gray-300 hover:bg-gray-50" },
    },
    size: {
      sm: { root: "text-sm px-3 py-1.5", icon: "h-3 w-3" },
      md: { root: "px-4 py-2", icon: "h-4 w-4" },
      lg: { root: "text-lg px-6 py-3", icon: "h-5 w-5" },
    },
    icon: {
      true: { root: "px-2", icon: "mr-0" },
    },
  },
  defaultVariants: {
    variant: "solid",
    size: "md",
    icon: false,
    disabled: false,
  },
});

type ButtonStylesConfig = InferComponentStylesConfig<typeof ButtonStyles>;

export interface ButtonProps extends Omit<ButtonStylesConfig, "icon"> {
  style?: React.CSSProperties;
  className?: string;
  children?: React.ReactNode;
  icon?: React.ReactNode;
}

export function Button(props: ButtonProps) {
  const { style, className, children, icon, slots, disabled, variant, size } = props;

  const $slots = ButtonStyles(
    { slots, variants: { disabled, variant, size, icon: Boolean(icon) && !children } },
    className,
  );

  return (
    <button style={style} className={$slots.root} disabled={disabled}>
      {icon && <span className={$slots.icon}>{icon}</span>}
      {children}
    </button>
  );
}
// usage

// Basic button with default solid style
<Button>Click me</Button>

// Outline variant for secondary actions
<Button variant="outline">Cancel</Button>

// Large size for prominent CTAs
<Button size="lg">Get Started</Button>

// Disabled state for unavailable actions
<Button disabled>Loading...</Button>

// Button with icon for better visual cues
<Button icon={<PlusIcon />}>Add Item</Button>

// Icon-only button for compact UI
<Button icon={<SettingsIcon />} />

// Override root slot class via className
<Button className="bg-green-600 hover:bg-green-700">Custom Color</Button>

// Override multiple slots via slots prop
<Button slots={{ root: "w-full", icon: "animate-spin" }} icon={<LoadingIcon />}>
  Loading...
</Button>

API Reference

create(options?)

Create a preconfigured varena instance so createStyles and createTokens share defaults.

Parameters

  • options?: CreateOptions - Optional global defaults applied to both factories.
  • options.mergeClasses?: (...classes: string[]) => string - Default class merge strategy injected into createStyles.
  • options.createVariableName?: (key: string, prefix?: string) => string - Default token-key-to-variable-name mapper injected into createTokens.

Returns

  • createStyles(styles, options?) - Preconfigured createStyles function with the provided defaults applied.
  • createTokens(tokens, options?) - Preconfigured createTokens function with the provided defaults applied.

Examples

import { create } from "varena";
import { twMerge } from "tailwind-merge";

export const { createStyles, createTokens } = create({
  mergeClasses: twMerge,
});

createStyles(styles, options?)

Create a typed slot styles factory for slot-based components with variants, compound variants, and defaults.

Parameters

  • styles.slots: SlotsValue - Base class names for each slot key.
  • styles.variants?: VariantsValue<slots> - Variant definitions that override slot classes based on variant values.
  • styles.compoundVariants?: CompoundVariants<slots, variants> - Additional slot classes applied when multiple variant conditions match simultaneously.
  • styles.defaultVariants?: Partial<Variants<slots, variants>> - Default variants used when call-time variants are omitted.
  • options?: CreateStylesOptions - Optional style factory behavior overrides.
  • options.mergeClasses?: (...classes: string[]) => string - Custom class merging function (default joins with spaces).

Returns

  • Styles(config?, overrides?) - Resolves final slot classes by combining defaults, variants, and overrides.
  • Styles.definition - Original style definition passed to createStyles.
  • Styles.slots - Cached slots resolved from base slots + defaultVariants.

Call-time Parameters

  • config.variants?: Partial<Variants<slots, variants>> - Per-call variant overrides.
  • config.slots?: Partial<slots> | ((variants) => Partial<slots> | undefined) - Per-call slot class patches, either object or function form.
  • overrides?: Partial<slots> | string (string is treated as root) - Final override layer applied after variants/config slots.

Examples

import type { InferComponentStylesConfig } from "varena";
import { createStyles } from "varena";

export const ButtonStyles = createStyles({
  slots: {
    root: "btn",
    icon: "btn__icon",
  },
  variants: {
    size: {
      sm: { root: "btn--sm", icon: "btn__icon--sm" },
      lg: { root: "btn--lg", icon: "btn__icon--lg" },
    },
    tone: {
      neutral: { root: "btn--neutral" },
      danger: { root: "btn--danger" },
    },
  },
  compoundVariants: [
    {
      variants: { size: "lg", tone: "danger" },
      slots: { root: "btn--lg-danger" },
    },
  ],
  defaultVariants: {
    size: "sm",
    tone: "neutral",
  },
});

export type ButtonStylesConfig = InferComponentStylesConfig<typeof ButtonStyles>;
// => {
//   slots?: { root?: string; icon?: string } | ...;
//   size?: "sm" | "lg";
//   tone?: "neutral" | "danger";
// }
ButtonStyles.definition;
// => { slots: {...}, variants: {...}, compoundVariants: [...], defaultVariants: {...} }

ButtonStyles.slots;
// => { root: "btn btn--sm btn--neutral", icon: "btn__icon btn__icon--sm" }

const slots = ButtonStyles();

slots.root;
// => "btn btn--sm btn--neutral"

slots.icon;
// => "btn__icon btn__icon--sm"
const slots = ButtonStyles({ variants: { size: "lg", tone: "danger" } });

slots.root;
// => "btn btn--lg btn--danger btn--lg-danger"

slots.icon;
// => "btn__icon btn__icon--lg"
const slots = ButtonStyles(
  { slots: { icon: "custom-icon" }, variants: { size: "lg" } },
  "override-root",
);

slots.root;
// => "btn btn--lg btn--neutral override-root"

slots.icon;
// => "btn__icon btn__icon--lg custom-icon"
const slots = ButtonStyles(
  { slots: { icon: "custom-icon" }, variants: { size: "lg" } },
  { root: "override-root" },
);

slots.root;
// => "btn btn--lg btn--neutral override-root"

slots.icon;
// => "btn__icon btn__icon--lg custom-icon"
const slots = ButtonStyles({
  slots: (variants) => ({
    root: variants.tone === "danger" ? "btn--ring" : undefined,
  }),
  variants: { tone: "danger" },
});

slots.root;
// => "btn btn--sm btn--danger btn--ring"

createTokens(tokens, options?)

Create a typed token factory for generating CSS custom properties and var(...) helpers.

Parameters

  • tokens: TokensValue - Base token definition map.
  • options?: CreateTokensOptions - Optional token factory behavior overrides.
  • options.prefix?: string - Prefix prepended to generated CSS custom property names.
  • options.createVariableName?: (key: string, prefix?: string) => string - Custom formatter for CSS variable names.

Returns

  • Tokens(config) - Generates a style object with only the specified CSS custom property overrides.
  • Tokens.definition - Original token definition passed to createTokens.
  • Tokens.style - Cached style object generated from full default token values.
  • Tokens.css(selector?, wrapper?) - Returns a formatted CSS string for creating CSS files.
  • Tokens.css(config, selector?, wrapper?) - Returns a formatted CSS string with only the specified CSS custom property overrides.
  • Tokens.value<TKey>(key) - Reads a token value. Returns undefined if the key is not defined.
  • Tokens.value<TKey>(key, fallback) - Reads a token value with a guaranteed non-null return, using fallback when the key is missing.
  • Tokens.value<TKey>(key, fallback?) - Reads a token value. Returns undefined if the key is not defined and no fallback is provided.
  • Tokens.property(key) - Returns the CSS custom property name for a token key.
  • Tokens.variable(key, fallback?) - Returns var(...) reference for a token key, with optional fallback.
  • Tokens.extend(config) - Returns a new Tokens instance with merged default values.

Call-time Parameters

  • config: Partial<typeof tokens> - Partial token overrides to generate a style object for a specific context.

Examples

import type { InferTokensConfig } from "varena";
import { createTokens } from "varena";

export const ThemeTokens = createTokens<{
  "color.primary": string;
  "color.secondary"?: string;
  "radius.md": string;
}>(
  {
    "color.primary": "#0ea5e9",
    "radius.md": "8px",
  },
  { prefix: "app" },
);

export type ThemeTokensConfig = InferTokensConfig<typeof ThemeTokens>;
// => { "color.primary"?: string; "color.secondary"?: string; "radius.md"?: string }
ThemeTokens.definition;
// => { "color.primary": "#0ea5e9", "radius.md": "8px" }

ThemeTokens({});
// => {}

ThemeTokens({ "color.primary": "#0369a1" });
// => { "--app-color-primary": "#0369a1" }

ThemeTokens.style;
// => { "--app-color-primary": "#0ea5e9", "--app-radius-md": "8px" }

ThemeTokens.css();
// =>
// :root {
//   --app-color-primary: #0ea5e9;
//   --app-radius-md: 8px;
// }

ThemeTokens.css("#main");
// =>
// #main {
//   --app-color-primary: #0ea5e9;
//   --app-radius-md: 8px;
// }

ThemeTokens.css(":root", "@media (prefers-color-scheme: dark)");
// =>
// @media (prefers-color-scheme: dark) {
//   :root {
//     --app-color-primary: #0ea5e9;
//     --app-radius-md: 8px;
//   }
// }

ThemeTokens.css({ ...ThemeTokens.definition, "color.primary": "#ff0000" });
// =>
// :root {
//   --app-radius-md: 8px;
//   --app-color-primary: #ff0000;
// }

ThemeTokens.css({ ...ThemeTokens.definition, "color.primary": "#ff0000" }, "#main");
// =>
// #main {
//   --app-radius-md: 8px;
//   --app-color-primary: #ff0000;
// }

ThemeTokens.css(
  { ...ThemeTokens.definition, "color.primary": "#ff0000" },
  ":root",
  "@media (prefers-color-scheme: dark)",
);
// =>
// @media (prefers-color-scheme: dark) {
//   :root {
//     --app-radius-md: 8px;
//     --app-color-primary: #ff0000;
//   }
// }

ThemeTokens.css({});
// => ""

ThemeTokens.value("color.primary");
// => "#0ea5e9"

ThemeTokens.value("color.secondary", "#64748b");
// => "#64748b" (returns fallback since key not in definition)

ThemeTokens.property("color.primary");
// => "--app-color-primary"

ThemeTokens.variable("color.primary");
// => "var(--app-color-primary)"

ThemeTokens.variable("radius.md", "6px");
// => "var(--app-radius-md, 6px)"
export const DarkThemeTokens = ThemeTokens.extend({
  "color.primary": "#0284c7",
});
DarkThemeTokens.definition;
// => { "color.primary": "#0284c7", "radius.md": "8px" }

DarkThemeTokens.style;
// => { "--app-color-primary": "#0284c7", "--app-radius-md": "8px" }

DarkThemeTokens.value("color.primary");
// => "#0284c7"

cx(...classes)

Merge class names and ignore falsy undefined entries.

Parameters

  • classes: (string | undefined)[] - Class names to merge in order.

Returns

  • string | undefined - Merged class string with undefined entries skipped.

Examples

import { cx } from "varena";

cx("btn", undefined, "btn--primary", "rounded-md");
// => "btn btn--primary rounded-md"

sx(...styles)

Merge style objects and ignore undefined entries.

Parameters

  • styles: (object | undefined)[] - Style objects to shallow-merge in order.

Returns

  • object | undefined - Merged style object with undefined entries skipped.

Examples

import { sx } from "varena";

sx({ padding: "8px", borderRadius: "8px" }, undefined, { padding: "12px", color: "white" });
// => { padding: "12px", borderRadius: "8px", color: "white" }

isStyles(target)

Type guard to check if a value is a Styles instance.

Parameters

  • target: unknown - The value to check.

Returns

  • target is Styles<any, any> - true if the value is a Styles instance, narrowing the type in TypeScript.

Examples

import { isStyles, createStyles } from "varena";

const ButtonStyles = createStyles({ slots: { root: "btn" } });

isStyles(ButtonStyles);
// => true

isStyles({});
// => false

isStyles(null);
// => false

isTokens(target)

Type guard to check if a value is a Tokens instance.

Parameters

  • target: unknown - The value to check.

Returns

  • target is Tokens<any> - true if the value is a Tokens instance, narrowing the type in TypeScript.

Examples

import { isTokens, createTokens } from "varena";

const ThemeTokens = createTokens({ "color.primary": "#0ea5e9" });

isTokens(ThemeTokens);
// => true

isTokens({});
// => false

isTokens(null);
// => false

InferStylesConfig<TStyles>

Infers the full createStyles call config type.

Input

  • TStyles extends Styles<any, any> - A createStyles return type.

Output

  • { slots?: Partial<SlotsValue> | ((variants) => Partial<SlotsValue> | undefined); variants?: Partial<Variants<...>> } - Full config shape accepted by Styles(config).

Examples

import type { InferStylesConfig } from "varena";

export type ButtonStylesConfig = InferStylesConfig<typeof ButtonStyles>;
// => {
//   slots?: { root?: string; icon?: string } | ...;
//   variants?: { size?: "sm" | "lg"; tone?: "neutral" | "danger" };
// }

ExtractStylesConfig<TStyles, TRules>

Extracts matching slot keys from the createStyles config type.

Input

  • TStyles extends Styles<any, any> - A createStyles return type.
  • TRules extends string - A string literal type pattern to match slot keys.

Output

  • StylesConfig with only the matching slot keys in slots.

Examples

import type { ExtractStylesConfig } from "varena";

export type IconOnlyButtonStylesConfig = ExtractStylesConfig<typeof ButtonStyles, "icon">;
// => {
//   slots?: { icon?: string } | ...;
//   variants?: { size?: "sm" | "lg"; tone?: "neutral" | "danger" };
// }

ExcludeStylesConfig<TStyles, TRules>

Excludes matching slot keys from the createStyles config type.

Input

  • TStyles extends Styles<any, any> - A createStyles return type.
  • TRules extends string - A string literal type pattern to exclude slot keys.

Output

  • StylesConfig without the excluded slot keys in slots.

Examples

import type { ExcludeStylesConfig } from "varena";

export type WithoutIconButtonStylesConfig = ExcludeStylesConfig<typeof ButtonStyles, "icon">;
// => {
//   slots?: { root?: string } | ...;
//   variants?: { size?: "sm" | "lg"; tone?: "neutral" | "danger" };
// }

InferComponentStylesConfig<TStyles>

Infers a component-friendly flattened style config type.

Input

  • TStyles extends Styles<any, any> - A createStyles return type.

Output

  • { slots?: Partial<SlotsValue> | ((variants) => Partial<SlotsValue> | undefined); [variantName]?: VariantValue } - Flattened component props style config.

Examples

import type { InferComponentStylesConfig } from "varena";

export type ButtonStylesConfig = InferComponentStylesConfig<typeof ButtonStyles>;
// => {
//   slots?: { root?: string; icon?: string } | ...;
//   size?: "sm" | "lg";
//   tone?: "neutral" | "danger";
// }

ExtractComponentStylesConfig<TStyles, TRules>

Extracts a component-friendly config type with only matching slots and flattened variants.

Input

  • TStyles extends Styles<any, any> - A createStyles return type.
  • TRules extends string - A string literal type pattern to match slot keys.

Output

  • Flattened config type with only the matching slot keys in slots.

Examples

import type { ExtractComponentStylesConfig } from "varena";

export type IconOnlyButtonStylesConfig = ExtractComponentStylesConfig<typeof ButtonStyles, "icon">;
// => {
//   slots?: { icon?: string } | ...;
//   size?: "sm" | "lg";
//   tone?: "neutral" | "danger";
// }

ExcludeComponentStylesConfig<TStyles, TRules>

Excludes a component-friendly config type with excluded slots and flattened variants.

Input

  • TStyles extends Styles<any, any> - A createStyles return type.
  • TRules extends string - A string literal type pattern to exclude slot keys.

Output

  • Flattened config type without the excluded slot keys in slots.

Examples

import type { ExcludeComponentStylesConfig } from "varena";

export type WithoutIconButtonStylesConfig = ExcludeComponentStylesConfig<
  typeof ButtonStyles,
  "icon"
>;
// => {
//   slots?: { root?: string } | ...;
//   size?: "sm" | "lg";
//   tone?: "neutral" | "danger";
// }

InferTokensConfig<TTokens>

Infers the config shape accepted by a Tokens(config) call.

Input

  • TTokens extends Tokens<any> - A createTokens return type.

Output

  • Partial<TokensValue> - Config shape accepted by Tokens(config).

Examples

import type { InferTokensConfig } from "varena";

export type ThemeTokensConfig = InferTokensConfig<typeof ThemeTokens>;
// => { "color.primary"?: string; "color.secondary"?: string; "radius.md"?: string }

License

MIT