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

@civitai/theme

v0.4.0

Published

Framework-agnostic --civitai-* design tokens, derived build-time from civitai's real Mantine theme. CSS + typed JS + W3C DTCG.

Readme

@civitai/theme

Framework-agnostic design tokens, derived at build time from civitai's real Mantine theme. Ships three forms of the same --civitai-* token contract:

  • dist/tokens.css — a :root + [data-theme='light'|'dark'] + OS-preference stylesheet (--civitai-* custom properties; <color> tokens registered via @property).
  • typed JS — import { tokens, darkTokens, tokenVars, tokensCss } from '@civitai/theme'.
  • dist/tokens.dtcg.json — a W3C Design Tokens Community Group export ($value/$type/$description) for interop with token tooling.

Why generated (not hand-authored)

The tokens are produced by feeding a vendored copy of civitai's createTheme override (src/theme.source.ts) through Mantine's public pipeline — mergeMantineThemedefaultCssVariablesResolver → transitive var() resolution → re-namespaced --civitai-*. This is the same primitive civitai uses in mantine-css-variables.ts. --mantine-* is fully resolved away, so the --civitai-* contract is self-contained.

Two kinds of token depart from that. The breakpoint scale bypasses the Mantine pipeline entirely (see below), and a few tokens declare an explicit light/dark pair — see next section.

Four guards keep it honest (pnpm --filter @civitai/theme test):

  • drift guard — reads civitai/civitai's live ThemeProvider.tsx and fails if the vendored palette/white/black diverge (set CIVITAI_REPO; skips with a clear message when the checkout is absent).
  • breakpoint drift guard — same, against civitai's src/utils/breakpoints.json.
  • generation parity — the committed generated source + built artifacts must byte-match a fresh generation, so a stale hand-edit can't slip through.
  • px-not-em guard — self-contained; pins the breakpoint tokens to the px scale and asserts the em values are absent.

Scheme-paired tokens

A custom property inherits across a shadow boundary; an ancestor selector does not cross one. So a component that wanted "gray in light, surface in dark" could only say it as [data-theme='dark'] <descendant> in @civitai/components, which a shadow root can never match (:host-context() has never shipped in Firefox).

Those decisions are tokens now — --civitai-card-border-width, --civitai-color-track, --civitai-color-segmented-bg and --civitai-color-media-placeholder. Mantine has no variable that carries either side of these pairs, so TokenSpec.source and .literal each accept a { light, dark } pair, resolved against its own scheme's variable map.

--civitai-card-border-width is a width, not a color: dark drops the default hairline's box, and a transparent color would still occupy 1px on every card.

Breakpoints — 🔴 the px scale, not Mantine's em scale

civitai has two breakpoint scales and they agree on exactly one key:

| scale | xs | sm | md | lg | xl | |---|---|---|---|---|---| | pxsrc/utils/breakpoints.json, mirrored by Tailwind and mantineContainerSizes. This package emits this one. | 480 | 768 | 1024 | 1184 | 1440 | | Mantine's stock em scale — never overridden in civitai; what every Mantine responsive prop uses | 576 | 768 | 992 | 1200 | 1408 |

Only sm matches, so a wrong implementation looks correct at a glance and a test that pins sm alone passes against the wrong scale. The px scale is therefore vendored in its own module (src/breakpoints.source.ts), guarded directly against breakpoints.json, and emitted as a literal token spec that never touches mergeMantineTheme — routing it through the Mantine resolver is exactly how an un-overridden key would silently come back as the em value.

import { breakpoints, BREAKPOINT_KEYS, tokens } from '@civitai/theme';
breakpoints.md;          // 1024   ← use this for a JS width comparison
tokens.bpMd;             // "1024px"
// CSS: var(--civitai-bp-md)

Use the numeric breakpoints for width comparisons: a CSS custom property cannot appear inside a @media/@container condition, so --civitai-bp-* is for lengths (max-width, grid-template-columns), not for conditions. React blocks should use useBlockBreakpoint() from @civitai/blocks-react, which resolves the block's own measured width against this scale.

Usage

<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@civitai/theme/styles.css" />

Unversioned on purpose: it tracks the latest dist-tag, so the tokens you load always include the ones documented above. A pinned URL keeps returning 200 with an old stylesheet@0.2.0, the version this README shipped until now, carries zero --civitai-bp-* tokens, so every var(--civitai-bp-md) on this page resolved to nothing. Pin only if you need a reproducible build, and re-check the pin when you upgrade.

import { injectTokens, tokens } from '@civitai/theme';
injectTokens();          // inject the stylesheet at runtime (JS consumers)
tokens.colorPrimary;     // "#228BE6"

Theme by setting data-theme="light" | "dark" on any ancestor.

Set nothing and the page follows the OS: the stylesheet carries the dark values under @media (prefers-color-scheme: dark), scoped to :root:not([data-theme]). An app that sets the attribute — a block acting on the host's THEME_CHANGE, or its own theme switch — never matches that block and is unaffected. The attribute is the override, and it wins wherever it sits, because a nearer ancestor's tokens inherit over a farther one's.

Build

pnpm --filter @civitai/theme build regenerates src/tokens.generated.ts, dist/tokens.css, and dist/tokens.dtcg.json, then compiles with tsc.