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

@vean/theme

v0.50.2

Published

The Vean theme engine: a static palette layer plus a declarative semantic alias layer (no measurement or correction).

Readme

@vean/theme

English | 中文

license npm version npm downloads github stars

The full design spec, token contract, and AI-agent handbook live in docs/design/theme.md (this README only covers package-level usage).

The Vean theme engine: a static palette layer plus a semantic alias layer — a declarative mapping table with no measurement and no correction.

Status: ✅ Implemented (the first-generation engine is retired; this package is the only implementation, and docs/design/theme.md is the design authority). Adapters and runtime (UnoCSS preset, SConfigProvider, first-paint script, persistence, customizer panel) live in @vean/unocss and @vean/ui, not in this package.

📦 Installation

pnpm add @vean/theme

🧩 Three-Layer Model

| Layer | Contents | Output | | :----------- | :------------------------------------------------------------------------- | :----------------------------------------------------------------------------------- | | Palette | 26 palettes × 11 levels from @soybeanjs/colord, plus white/black | generatePaletteCss() / dist/palette.css (static, cacheable forever) | | Semantic | 41 semantic tokens + 5 role ramps; values are palette-level references | resolveThemeMap() → emitThemeCss() (159 declarations ≈5.7 KB raw / gzip ≈1.2 KB) | | Literal | size / radius / spacing unit / layering / border width / font families | emitted together with the semantic layer |

Color variables are always bare channels (--zinc-50: 0 0% 98%); semantic tokens are only references (--background: var(--zinc-50)). Therefore:

  • the palette layer can ship as a static artifact (switching a theme never recomputes it);
  • the semantic layer is tiny — switching a theme only swaps references;
  • when you need a full color, use resolveTokenColor() (a pure function; works in SSR / workers / canvas) — it reads the same mapping table as the CSS, so the two never diverge;
  • consumers must always wrap values in a function: hsl(var(--primary) / 0.5) (a bare var() silently drops alpha).

⚙️ Core Mechanics

No contrast guardrails: every token's level is declared in CORE_RULES and overrides apply verbatim — the engine neither measures nor corrects. Readability is the theme author's responsibility; the customizer panel and axe are where it gets checked.

Surface levels are fixed declarations (CORE_RULES); there is no runtime "shift everything one step darker" knob: fine-tune an individual token with overrides, or go globally darker/lighter by changing the base palette or surfaceStyle.

📐 Dimension Scales

Two literal scales (space-control-scale.md):

| Scale | Slots | Value | | :--------------- | :---------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | --spacing-unit | — (the only spacing variable) | Grid unit: 0.25rem (default) or calc(0.25rem * factor). The 18 slots are coefficients of this unit (mapped in UnoCSS's theme.spacing, no CSS variables emitted; at the default unit they equal UnoCSS's same-named values and extend downward). | | --radius-* | 2xs … 4xl (9 slots) + none / full | Positive-coefficient multiples of the seed (calc(var(--radius) * 0.25…2.25), md = the seed itself); changing the seed moves the whole scale, and no seed can produce a negative slot — negative border-radius is an invalid value, so the declaration is dropped and the corner silently becomes square. |

On the UnoCSS side, spacing is exposed through both named and numeric keys (gap-md / p-2xl / mt-3xs), while radii are taken over as a whole family (rounded-2xs … rounded-4xl, none / full, plus rounded = the seed; rounded-3xl / rounded-4xl also come back to the theme — upstream fixed lengths don't move with the seed, and leaving them upstream would break the scale at 2xl). Control heights are not theme-mapped: use UnoCSS's numeric h-* (20–56px, i.e. h-5…h-14, scaled by size). Named slots and numeric classes share one source (gap-md and gap-4 both produce calc(var(--spacing-unit) * 4)), so the single spacing option drives both; w-* / h-* / size-* go through theme.width / theme.height and are unaffected by spacing.

🚀 Quick Start

import { resolveThemeMap, emitThemeCss, generatePaletteCss } from '@vean/theme';

// 1) Layer 1 (static, produced once at build time)
const paletteCss = generatePaletteCss({ format: 'hsl' });

// 2) Layer 2 (recomputed with the theme config)
const map = resolveThemeMap({ base: 'zinc', primary: 'indigo', surfaceStyle: 'layered' });
const themeCss = emitThemeCss(map); // token names carry no prefix by default; pass { prefix: 'acme' } when you need a namespace

// 3) When you need a full color (canvas / charts / color math)
import { resolveTokenColor } from '@vean/theme';
const primary = resolveTokenColor({ primary: 'indigo' }, 'primary', 'dark');

The UnoCSS-side color mapping (channel + <alpha-value> — the only shape in which alpha works):

const ref = (name: string, format: 'hsl' | 'oklch') => `${format}(var(${name}) / <alpha-value>)`;

theme.colors = {
  background: ref('--background', 'hsl'),
  'muted-foreground': ref('--muted-foreground', 'hsl'),
  'primary-500': ref('--primary-500', 'hsl'), // role ramp (50–950, follows the primary palette / scheme)
  indigo: { 500: ref('--indigo-500', 'hsl') }
};

🎛 Options

| Option | Default | Description | | :----------------------------- | :---------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | base / primary | zinc / indigo | Any of the 26 built-in palettes (primary switches to "near-black in light / near-white in dark" when set to a neutral palette) | | feedback | classic | Status-color scheme (see FEEDBACK_SCHEMES); chart colors have no scheme and derive from primary (CHART_RAMP) | | overrides | — | Per-token overrides as { light, dark } (TokenOverride: stone.950 / white / hsl(...) / oklch(...) / token.primary referencing another semantic token), highest priority; when emitting full colors the value is encoded as a channel in the theme format, alpha lands in the companion variable of the border family, and references copy the target value at resolution time (self-references / cycles are ignored like any other invalid value) | | surfaceStyle | layered | flat returns to the "page and container share one color, layered by borders and shadows" shape | | prefix | false | Prefix for semantic variables; token names map one-to-one to shadcn by default (--background / --card / --ring…), and hosts that need a namespace pass a string | | size / radius / spacing | md / md / default | Root font size (density scaling, all dimensions) / radius seed (the 9-slot scale is calc()-derived from the seed; md is the seed) / spacing grid unit (presets compact 0.75 · default 1 · relaxed 1.25 · spacious 1.5, or any factor; moves padding / margin / gap / inset only — never font size, control height, or radius) | | borderOpacity | 1 | Multiplier for decorative border alpha | | format | hsl | Palette-layer format (oklch is smaller) | | styleTarget / darkSelector | :root / class | Light-block selector / dark expression (media → @media (prefers-color-scheme: dark)) |

🧪 Commands

pnpm --filter @vean/theme test        # layer invariants, emission contract, JS↔CSS parity, family split, dimension scales, budget
pnpm --filter @vean/theme typecheck
pnpm --filter @vean/theme build       # vp pack + dist/palette.css

📌 Not Yet Included

Custom palette registration (beyond the built-in 26) is deferred — use overrides to cover individual tokens for now. See apps/docs/src/content/{en,zh}/ui/migration/ for the upgrade guide and docs-site content.

📖 Documentation

Design spec and AI-agent handbook: docs/design/theme.md · scale rationale: docs/design/space-control-scale.md · docs site: veanui.com

📄 License

MIT