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

@reforma/project-tokens

v0.0.2

Published

Portable DTCG 2025.10 compiler for project design tokens.

Readme

@reforma/project-tokens

Compile project DTCG JSON into CSS, Tailwind theme bindings, and mode metadata. The compiler runs locally on Node 24 or newer.

Files

The compiler reads a DTCG resolver and the documents that resolver references. It writes tokens.css, tailwind.css, and modes.json into the directory from --out. Without that flag, the directory is .generated beside the resolver. The JSON stays the source. Ignore the output directory.

tokens/tokens.resolver.json       sets, modes, default
tokens/base.json                  every value for the default context
tokens/dark.json                  sparse overrides for one other context
tokens/.generated/tokens.css      custom properties
tokens/.generated/tailwind.css    Tailwind @theme bindings
tokens/.generated/modes.json      context names, selectors, revision

The directory and the JSON filenames are yours. $ref inside the resolver decides which documents are read. base.json and dark.json above are only an example: one complete document, then a document that overrides existing paths for another context. A mode file does not declare types and does not add tokens that exist only in that mode.

Pass --entry to point at the resolver and --out to choose the output directory. If you omit --entry, the CLI uses .reforma/tokens/tokens.resolver.json. If you omit --out, it writes .generated beside the resolver.

tokens.css puts the default context on :root and each other context on a selector such as html[data-theme="dark"]. tailwind.css is an @theme inline block. It includes only names Tailwind already treats as theme keys, including color, spacing, font, text, radius, and shadow. Any other group stays a plain variable in tokens.css. modes.json records the default, each modifier's contexts, the selector for every permutation, the input revision, and hashes of the source files.

This generated tailwind.css is not the project's Tailwind entry. The app imports its own Tailwind CSS, then these generated files. check writes nothing. build, watch, and dev replace the three files when the output changes, and leave the last good output in place when the JSON is invalid.

CLI

Install the package, then call the reforma-tokens binary from a script. --entry selects the resolver. The default is .reforma/tokens/tokens.resolver.json.

{
  "scripts": {
    "dev": "reforma-tokens dev -- next dev",
    "build": "reforma-tokens build && next build"
  }
}

dev compiles, then runs the command after --. That command starts the app and must not call reforma-tokens again. check, build, and watch take no child command.

| Command | Behavior | | -------------------- | -------------------------------------------------------------------------------------- | | check | Validate every permutation. Write nothing. Exit 1 on errors. | | build | Compile and replace changed generated files. Exit 1 on errors. | | watch | Build, then poll dependency hashes. Recover after invalid or missing files. | | dev -- command ... | Build before starting the command. Watch sources. Forward signals and the exit status. |

--cwd directory selects the workspace. --entry path selects its resolver. --out directory selects the output directory. Repeat --context axis=value to supply contexts, including axes without defaults. Every permutation is validated. The selected or default input supplies :root. Names are case-sensitive. Compilation stops at 1000 permutations.

A build may also leave staging and lock files in the output directory while it runs. Watch and dev print one JSON diagnostic per line on stderr and { "status": "stale", "revision" } when input is invalid. A successful rebuild prints { "status": "ready", "revision" } on stdout. The app keeps running after a bad edit. An invalid first build does not start the dev command.

API

import { compileProjectTokens } from "@reforma/project-tokens";

const result = await compileProjectTokens({ workspaceRoot: process.cwd() });
if (result.ok) {
  console.log(result.output?.tokensCss);
  console.log(result.permutations);
}

Compilation reads sources and returns diagnostics, dependency SHA-256 hashes, an input revision, resolved tokens per permutation, and generated strings. Each token keeps its canonical path, type, resolved value, CSS declarations, and winning source file and JSON Pointer. A missing dependency has a null hash. The API does not write files. Source extensions stay opaque.

Local file references and same-document JSON Pointers resolve. Remote URLs, paths that leave the workspace, and symlinks that escape it are rejected. Resolver $ref siblings replace referenced fields shallowly. Ordered composition replaces whole tokens. Groups merge recursively. Aliases resolve after composition for each context. $root stays in token identity and aliases, and is omitted from CSS names.

A mode modifier selects html[data-theme="context"]. Any other axis selects html[data-token-AXIS="context"]. Each selector constrains every active axis. :root receives the full default. Context selectors receive only the values that differ. CSS names join path segments with -, escape CSS characters, and reject collisions. Recognized Tailwind namespaces become @theme inline bindings. Other groups stay ordinary CSS variables. The namespace does not infer the type.

Authoring data

The snapshot describes the sources an editor can show:

| Field | Meaning | | ----------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | editing | A supported layout with defaultMode, baseFile, and modeFiles, or why structured editing is unavailable. A null mode file is a sparse context. | | permutations[].groups | Flat group hierarchy, including empty groups and the root at "". Each group has provenance, original metadata in definition, and effective type, description, and deprecated. | | tokens[].authoring.definition | Original token properties, including unresolved $value aliases, JSON Pointers, and vendor extensions. value stays resolved. | | tokens[].description, deprecated | Effective metadata after composition and group inheritance. Description belongs to the node. Type and deprecation can be inherited. | | tokens[].authoring.inheritance | base, inherited, or override in the supported mode layout, or null when ownership is ambiguous. An explicit override stays an override when its value equals the base. | | tokens[].authoring.canReset, readOnlyReason | Whether reset is allowed, and why the simple value editor cannot change this token. Composite values and tokens supplied through $extends stay readable. |

Group definition is metadata from the last contributing source, without children or $root. Effective fields describe the composed group. Do not write that object back as a document. Definitions and resolved values share the snapshot revision, including candidates from planTokenMutation.

Eligibility describes source structure. A mutation still checks references, usages, the revision, and the full candidate. Missing authoring fields do not mean the token can be edited.

Structured editing

planTokenMutation from @reforma/project-tokens/mutations plans source edits and compiles the candidate in memory. It returns the original revision, changed files with before and after text, and the candidate compilation. It writes nothing.

Create a top-level scope with { kind: 'scope.create', path, type } and one of the exported DTCG_TOKEN_TYPES. group.create adds an inherited subgroup inside an existing scope. token.create sets $value and metadata in the default mode and takes the type from that scope. $type is rejected on token create, ordinary updates, and sparse mode files.

Token operations are update, delete, reset, and rename. Group operations are delete and rename. A rename is { kind, path, to }. A token destination must share the scope type. Nested groups stay inside their scope. A scope can be renamed at the top level. Aliases, JSON Pointers, and mode overrides move with the node, and values keep their inherited type. A scope type cannot change. Deleting a nonempty group requires tokens, the exact list of descendant paths.

Mode operations are create, delete, default, rename, and reset. Mode rename is { kind: 'mode.rename', mode, to }. Mode reset drops that mode's overrides. The default mode cannot be reset.

The managed profile is one base set, then a mode modifier, with separate local JSON files and an empty default context. Every top-level base group declares one standard $type. Nested groups inherit it. Base owns every token path. A token $type, a mode $type, a mode-only path, or a cross-type descendant fails with a path-specific SCOPE_* diagnostic, including expectedType and actualType on a mismatch. Invalid managed sources produce no output and block structured writes. The compiler does not infer types or migrate documents.

A sparse context gets its own document on the first edit. Other resolver layouts remain ordinary DTCG input, and structured editing reports AMBIGUOUS_SOURCE. Changing the default preserves effective values and aliases. Detached mode documents stay on disk. An existing file is not reused because its name matches.

Changing the default, including replacing a deleted default, does not yet keep group-inherited $deprecated metadata across modes. CSS and values are preserved. Deprecation metadata is not.

buildProjectTokens and withTokenCompilationLock from @reforma/project-tokens/generation write the same output as the CLI.

Compatibility

The target is DTCG Format 2025.10 and Resolver 2025.10. Terrazzo's parser and CSS tools are pinned to 2.7.1. The adapter covers tested upstream gaps: shallow resolver-reference overrides, set references inside contexts, escaped pointers, shared group inheritance, JSON Pointer $extends, explicit $root, composite array aliases, and provenance. Neutral parser IDs keep legal names such as constructor and __proto__. A value gate rejects Terrazzo-only types and dimension units. Unknown vendor extensions do not turn on Terrazzo's legacy modes. A modifier with one context produces a nonfatal SINGLE_CONTEXT diagnostic.

CSS uses Terrazzo's web projection, including the dashed fallback for custom stroke patterns and separate custom properties for typography components. Colors and color-bearing composites are serialized with Color.js in their native space, which keeps alpha and avoids Terrazzo's wide-gamut fallback IDs.

Development

From this package:

bun run test
bun run build
bun run verify:project

verify:project packs the built artifact, installs it in a temporary consumer, and checks the Node API, CLI, standard types, modes, provenance, and Tailwind output. It needs registry access. If node is older than 24, set PROJECT_TOKENS_NODE to a Node 24+ binary.