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

@themeshift/vite-plugin-themeshift

v0.3.11

Published

Vite plugin that makes using Style Dictionary + SASS easy as pie.

Readme

@themeshift/vite-plugin-themeshift

ThemeShift is a Vite plugin that makes using Style Dictionary easy as pie. It watches your design tokens, regenerates token outputs automatically, and keeps your app up to date without extra build scripts. It also injects a global Sass token() function so you can reference CSS variables ergonomically in SCSS. It can also extend token JSON published by UI packages and layer local app overrides on top. It also ships a standalone Sass module for explicit token() imports.


Why this exists

If you’re already using Style Dictionary to manage design tokens, you usually end up writing custom scripts to rebuild tokens and wire up live reload. ThemeShift moves that logic into a Vite plugin so token changes behave like any other frontend change.


Features

  • 👀 Watches tokens/**/*.json and rebuilds on change
  • ⚙️ Runs Style Dictionary programmatically (no extra CLI step)
  • 🎨 Outputs CSS variables for multi-mode theming
  • 🧵 Optional Sass output for static tokens
  • ✨ Injects a global Sass token() helper
  • 📦 Ships a standalone Sass token() module
  • 📦 Extends tokens from installed UI packages (like @themeshift/ui)
  • 🏷️ Supports CSS variable prefixes
  • 🔥 Vite HMR for tokens.css (fallback to full reload)

Basic usage

// vite.config.ts
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import { themeShift } from '@themeshift/vite-plugin-themeshift';

export default defineConfig({
  plugins: [react(), themeShift()],
});

By default, ThemeShift expects a tokens/ directory in your project root containing Style Dictionary JSON files and outputs:

  • src/css/tokens.css
  • src/sass/_tokens.static.scss
  • src/design-tokens/token-paths.{json,ts}
  • src/design-tokens/token-values.{json,ts}

Local tokens continue to work exactly as before. If you do nothing, the plugin only reads your app's tokens/**/*.json.


Getting started (new project)

If you're wiring this up for the first time, this is a good baseline setup:

  1. Install packages
npm install --save-dev @themeshift/vite-plugin-themeshift style-dictionary sass
  1. Add the plugin to vite.config.ts
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import { themeShift } from '@themeshift/vite-plugin-themeshift';

export default defineConfig({
  plugins: [react(), themeShift()],
});
  1. Create your first tokens file

Create tokens/theme.json:

{
  "theme": {
    "text": {
      "base": { "$value": "#0f172a" }
    }
  }
}
  1. Import the generated CSS

Import the CSS file that ThemeShift generates. For example in src/main.tsx:

import './css/tokens.css';
  1. Ignore generated outputs

In most apps, these files should be treated as build artifacts rather than source:

src/css/tokens.css
src/sass/_tokens.static.scss
src/design-tokens/
  1. Optional: import the Sass token helper directly

ThemeShift injects token() automatically by default. If you prefer explicit Sass imports, you can use the published module instead:

@use '@themeshift/vite-plugin-themeshift/token' as themeShift;

.button {
  color: themeShift.token('theme.text.base');
}

If your app uses cssVarPrefix, the plugin injects that prefix as the Sass default automatically, so the same explicit import still works without module configuration:

@use '@themeshift/vite-plugin-themeshift/token' as themeShift;

.button {
  color: themeShift.token('components.button.font');
}

You can also pass a prefix explicitly per call:

@use '@themeshift/vite-plugin-themeshift/token' as themeShift;

.button {
  color: themeShift.token('components.button.font', 'themeshift');
}

For shared mixins and partials, prefer the namespaced import form so they do not depend on root-level injection:

@use '@themeshift/vite-plugin-themeshift/token' as themeShift;

@mixin style($path) {
  font: themeShift.token('typography.styles.#{$path}.font');
}

Root stylesheets can still use the injected global token() helper. The plugin now injects that helper as a thin wrapper around the canonical token module, so explicit imports and root-level injection can coexist in the same Sass compile graph without module configuration conflicts.

  1. Optional: use the JavaScript token helpers

ThemeShift also ships a JavaScript runtime API on the same ./token subpath:

import { token, tokenValue } from '@themeshift/vite-plugin-themeshift/token';
import { tokenValues } from './design-tokens/token-values';

const currentTextColor = token('theme.text.base', { prefix: 'themeshift' });
const authoredTitleStyle = tokenValue('text.style.title', { values: tokenValues });

token() reads the current computed CSS custom property value in the browser. tokenValue() reads the authored value from the generated token-values manifest.

Dark/light mode setup

To enable theme modes, split your tokens into separate files:

  • tokens/theme.light.json
  • tokens/theme.dark.json

Example:

tokens/theme.light.json:

{
  "theme": {
    "light": {
      "text": {
        "base": { "$value": "#0f172a" }
      },
      "surface": {
        "base": { "$value": "#ecf0f1" }
      }
    }
  }
}

tokens/theme.dark.json:

{
  "theme": {
    "dark": {
      "text": {
        "base": { "$value": "#e2e8f0" }
      },
      "surface": {
        "base": { "$value": "#2c3e50" }
      }
    }
  }
}

Then set the data-theme attribute on your document root (usually inside of index.html):

<html lang="en" data-theme="dark">
  ...
</html>

You can toggle data-theme between light and dark to switch modes at runtime. To easily toggle this on the fly you can use a hook like useDarkMode from useHooks (or you can write your own).


Playground

This repo includes a playground project under playground/ to try things locally.

npm install
npm -C playground install
npm run playground

Plugin options

type ThemeShiftExtendSource =
  | string
  | {
      package: string;
      tokensGlob?: string;
      contractFile?: string;
    };

type ThemeShiftCssGroup = {
  label: string;
  match: (name: string) => boolean;
};

type themeShiftOptions = {
  tokensGlob?: string; // default: "tokens/**/*.json" (watch uses tokensDir)
  tokensDir?: string; // default: "tokens"
  extends?: ThemeShiftExtendSource[];
  cssVarPrefix?: string;
  groups?: ThemeShiftCssGroup[];
  defaultTheme?: 'light' | 'dark';
  outputPrintTheme?: boolean; // default: false
  watch?: boolean; // default: true
  injectSassTokenFn?: boolean; // default: true
  platforms?: Array<'css' | 'scss' | 'meta'>; // default: all three
  filters?: Partial<
    Record<
      'css' | 'scss' | 'meta',
      | {
          includePrefixes?: string[];
          excludePrefixes?: string[];
        }
      | ((token: any) => boolean)
    >
  >;
  reloadStrategy?: 'hmr' | 'full'; // default: "hmr"
  log?: {
    warnings?: 'warn' | 'error' | 'disabled';
    verbosity?: 'default' | 'silent' | 'verbose';
    errors?: { brokenReferences?: 'throw' | 'console' };
  };
};

extends

Use extends to load token JSON from installed packages before local app tokens are loaded. Local files always win.

themeShift({
  extends: ['@themeshift/ui'],
  cssVarPrefix: 'themeshift',
});

Resolution order is:

  1. Extended package tokens
  2. Local tokens/**/*.json

If you pass a string entry like @themeshift/ui, ThemeShift resolves that package from the consuming app root and looks for theme-contract.json in the package root by default.

Example contract:

{
  "name": "@themeshift/ui",
  "cssVarPrefix": "themeshift",
  "tokensGlob": "dist/tokens/**/*.json",
  "version": "1.0.0"
}

If you do not want to publish a contract file, use the explicit object form:

themeShift({
  extends: [
    {
      package: '@themeshift/ui',
      tokensGlob: 'dist/tokens/**/*.json',
    },
  ],
});

You can also override the contract filename:

themeShift({
  extends: [
    {
      package: '@themeshift/ui',
      contractFile: 'dist/theme-contract.json',
    },
  ],
});

If an extended package cannot be resolved, the build fails with a clear error.

cssVarPrefix

Use cssVarPrefix to make generated CSS variable names part of a stable public contract:

themeShift({
  cssVarPrefix: 'themeshift',
});

This changes output like:

  • --components-button-font
  • to --themeshift-components-button-font

The injected Sass helper uses the same naming, so token('components.button.font') resolves to var(--themeshift-components-button-font) when a prefix is configured.

The standalone Sass module uses the same naming contract. It checks the prefix argument first, then the plugin-injected default prefix, and finally falls back to an unprefixed CSS variable. The plugin configures the shared default prefix before other Sass imports load, then injects a local token() wrapper for root stylesheets.

groups

Use groups to customize comment sections in the generated css/variables-modes-grouped output:

themeShift({
  cssVarPrefix: 'themeshift',
  groups: [
    { label: 'Colors', match: (name) => name.startsWith('color-') },
    {
      label: 'Typography',
      match: (name) =>
        name.startsWith('font-') || name.startsWith('typography-'),
    },
    {
      label: 'Accessibility',
      match: (name) =>
        name.startsWith('accessibility-') || name.startsWith('a11y-'),
    },
    { label: 'Theme', match: (name) => name.startsWith('theme-') },
    {
      label: 'Components',
      match: (name) =>
        name.startsWith('component-') || name.startsWith('components-'),
    },
    { label: 'Other', match: (_name) => true },
  ],
});

Defaults use the same group list shown above.

groups fully replaces the defaults, and match order matters. The first matching group wins.

Grouping is always based on the raw token name, not the final CSS variable name. That means cssVarPrefix does not change how tokens are grouped:

  • groups.match(name) sees components-button-text
  • cssVarPrefix: "themeshift" renders --themeshift-components-button-text

defaultTheme

Use defaultTheme to emit either your light or dark themed variables into bare :root as a fallback when the document does not have a data-theme attribute.

themeShift({
  defaultTheme: 'light',
});

This is useful for published component libraries like @themeshift/ui, where you want styles to work immediately with zero consumer setup.

Theme-specific blocks are still emitted, so apps can override the fallback later with:

  • :root[data-theme='light']
  • :root[data-theme='dark']

outputPrintTheme

By default, ThemeShift does not emit the :root[data-theme='print'] block or the matching @media print block.

Set outputPrintTheme: true to opt into that output when you have print theme tokens:

themeShift({
  outputPrintTheme: true,
});

filters

Use filters to customize which tokens are included per output platform.

By default, ThemeShift preserves its existing behavior:

  • scss includes non-themed radius-*, spacing-*, font-*, text-*, and layout-* tokens
  • css includes all tokens
  • meta includes all tokens

You can override this with declarative include/exclude rules:

themeShift({
  filters: {
    scss: {
      includePrefixes: ['radius-', 'spacing-', 'font-', 'text-', 'layout-'],
      excludePrefixes: ['theme-', 'components-'],
    },
    css: {
      includePrefixes: [],
      excludePrefixes: [],
    },
    meta: {
      includePrefixes: [],
      excludePrefixes: [],
    },
  },
});

Or with a predicate function for advanced cases:

themeShift({
  filters: {
    scss: (token) => !token.attributes?.theme,
  },
});

reloadStrategy

When tokens change, ThemeShift will try to HMR-reload the generated tokens.css. If it can’t find the CSS module in Vite’s module graph, it will fallback to a full reload. Set reloadStrategy: "full" to always reload.

log

By default, ThemeShift silences Style Dictionary output (verbosity: "silent" and warnings: "disabled"). Override to opt back into logs.

Forwarded to Style Dictionary's logging config. Use this to reduce or silence output. For example, to hide warnings:

themeShift({
  log: { warnings: 'disabled' },
});

To fully silence Style Dictionary output:

themeShift({
  log: { verbosity: 'silent', warnings: 'disabled' },
});

Token workflow notes

  • The token() Sass helper maps token("theme.text.base")var(--theme-text-base).
  • With cssVarPrefix: "themeshift", the same token becomes var(--themeshift-theme-text-base).
  • The standalone Sass module exposes the same token() API via @use '@themeshift/vite-plugin-themeshift/token'.
  • The Sass API is token($path, $cssVarPrefix: null), so explicit imports do not require with (...).
  • Shared Sass modules should prefer @use '@themeshift/vite-plugin-themeshift/token' as themeShift;.
  • Root stylesheets may use the injected global token() helper or an explicit namespaced import.
  • The JavaScript @themeshift/vite-plugin-themeshift/token export provides token() for computed CSS values and tokenValue() for authored values from token-values.
  • Pass the token's JSON path to token(). CamelCase segments like gapWidth are normalized to kebab-case CSS vars like --...-gap-width.
  • groups matches raw token names; cssVarPrefix only changes emitted CSS custom property names.
  • defaultTheme duplicates either light or dark tokens into bare :root as a startup fallback.
  • Tokens that include light, dark, or print in their path are treated as mode-specific.
  • Print-theme CSS blocks are only emitted when outputPrintTheme is true.
  • The CSS output groups tokens for readability, and those groups are configurable.
  • CSS variable names are intended to be a public API for consuming packages and apps.

Development

npm run dev

Build:

npm run build

License

MIT