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

@servicetitan/carto-tokens

v0.2.3

Published

Carto design tokens. DTCG JSON → vanilla-extract, Swift Package, Gradle module. (CSS / SCSS targets present in source, not currently built.)

Readme

@servicetitan/carto-tokens

Carto design tokens. DTCG JSON → a native TypeScript build pipeline → three active outputs:

| Output | Consumer | |---|---| | Vanilla-extract module (three tier-scoped createTheme layers) | import { cartoThemeClass, primitive, semantic, component } from "@servicetitan/carto-tokens/vanilla-extract"; | | Motion values (raw JS literals for the motion.* namespace) | import { easing, duration, scale } from "@servicetitan/carto-tokens/motion"; | | Swift Package | TBD | | Kotlin | In gradle/libs.versions.toml: carto-tokens = { module = "com.servicetitan:carto-tokens", version = "<insert_version>" } | | React Native | import { light, dark } from "@servicetitan/carto-tokens/react-native"; |

CSS and SCSS targets are present in source (src/formats/scss.ts and the buildCss driver in scripts/build-pipeline.ts) but are excluded from the production build today — vanilla-extract is the web source of truth. To re-enable, drop the excludePlatforms option in scripts/build.ts and re-add the subpath exports + files: entries in package.json.

How the build works — DTCG-spec handling, the Carto pipeline, and how to add a new token / $type / platform / extension: see PIPELINE.md.

Token source format

DTCG-flavored JSON with a few Carto extensions. Source files live in src/primitive/, src/semantic/, src/component/.

{
  "radius": {
    "$extensions": { "com.figma.scopes": ["CORNER_RADIUS"] },
    "400": { "$type": "dimension", "$value": { "value": 16, "unit": "px" } }
  }
}

Value shapes per $type

| $type | Shape | |---|---| | dimension | { "value": <px>, "unit": "px" \| "%" } | | color | Ref string "{color.X}" or { "ref": "{color.X}", "alpha": <0–1> } (primitive + alpha overlay) or DTCG { "colorSpace": "srgb", "components": […], "alpha"?: <0–1>, "hex"?: "#rrggbb" } | | shadow | Composite { "color": <colorValue>, "offsetX": <dim>, "offsetY": <dim>, "blur": <dim>, "spread": <dim> }color accepts the same three forms | | fontFamily | String (single family) or array of strings (font stack, most-preferred first). Web joins the stack with , ; Figma/Swift/Kotlin take the head. | | fontWeight | Number (CSS 100–900) | | number | Unitless number (e.g. line-height ratios) | | duration | { "value": <ms>, "unit": "ms" } (motion timing) |

$extensions Carto reads

| Key | Purpose | |---|---| | appearance.dark | A full DTCG token ({ $type, $value, $extensions? }) for the dark mode variant. The root $value is implicitly the light variant. | | excludePlatforms | Array<"web" \| "swift" \| "kotlin" \| "figma"> — skip emission on the named platforms. Group-inherited; deepest level wins. | | preservePxForWeb | Boolean — skip the px → rem conversion on web (used for breakpoints and the radius.contained pill sentinel). | | com.figma.scopes | Array<string> — Figma variable scope list (e.g. ["SHAPE_FILL", "STROKE_COLOR"]). |

Rules

  • Dimensions default to px; the web transform divides by base (16) to emit rem unless preservePxForWeb is set or unit is %. iOS and Android transforms emit the raw value.
  • Durations are authored in ms. Web (vanilla-extract / CSS) emits the ms literal (with the unit even for 0 — CSS <time> requires it); the /motion JS export emits seconds as a unitless number (100ms0.1, matching Framer Motion's duration prop); Swift emits seconds as TimeInterval (100ms0.1, matching Animation(duration:)); Kotlin emits raw ms as Int (100ms100, for Compose tween(durationMillis:)).
  • Easing is authored as a cubicBezier control-point array. Web (vanilla-extract / CSS) emits a cubic-bezier(...) string; the /motion JS export emits the raw [x1, y1, x2, y2] tuple (Framer Motion's ease does not accept the CSS string); Swift emits a (CGFloat, CGFloat, CGFloat, CGFloat) tuple; Kotlin emits Compose CubicBezierEasing(...).
  • Tier names (primitive, semantic, component) are organizational and stripped from emitted CSS variable names: semantic.background.color.standard.resting--carto-background-color-standard-resting.
  • Tier chains: a token whose $value is "{radius.400}" emits as a reference (var(--carto-radius-400) / primitive.radius["400"] / Radius.r400), not the resolved literal.
  • Alpha lives in the value, not in extensions. $extensions.modification.alpha is not used — author { "ref": "{color.X}", "alpha": <N> } as the $value instead. The platform emitters preserve the primitive reference and apply the alpha as color-mix(...) (web), .opacity() (Swift), or .copy(alpha = ...) (Kotlin).

Development

pnpm install
pnpm --filter @servicetitan/carto-tokens build      # regenerates build/
pnpm --filter @servicetitan/carto-tokens test
pnpm --filter @servicetitan/carto-tokens dev        # watch mode

build/ is generated output and is not committed — including the Swift sources under build/swift/. It's gitignored and rebuilt on demand by each consumer (the npm tarball via prepack, and the Swift/Android mirror workflows in CI).

Figma sync

The package ships a Figma sync pipeline under scripts/figma/ that pushes the same DTCG tokens to the Carto Design Tokens Figma library as chained variable aliases with Light / Dark modes.

pnpm --filter @servicetitan/carto-tokens figma:validate          # offline; no creds needed
pnpm --filter @servicetitan/carto-tokens figma:sync --dry-run    # plan only (needs creds)
pnpm --filter @servicetitan/carto-tokens figma:sync              # write only your changes (default)
pnpm --filter @servicetitan/carto-tokens figma:sync --all        # full resync of every token
pnpm --filter @servicetitan/carto-tokens figma:auth              # mint a refresh token (one-time)

By default figma:sync only pushes the tokens you changed relative to origin/main (committed changes plus uncommitted edits), so a stale branch can't overwrite tokens that already landed on main. It requires origin/main to be fetched; if the baseline can't be resolved it fails loudly rather than syncing everything. Use --all (or FIGMA_SYNC_ALL=1) for a full resync — initial seeding, recovery, or CI runs on main where there is no meaningful diff. --dry-run previews the plan and composes with both.

Credentials

Drop these into a .env.local at the repo root (gitignored):

FIGMA_CLIENT_ID=…
FIGMA_CLIENT_SECRET=…
FIGMA_REFRESH_TOKEN=…

The current values are stored in 1Password — search the shared Carto vault for the Figma Token Sync entry. Ask a maintainer if you don't have access.

Alternatively, a Personal Access Token works for local one-offs (skips OAuth entirely):

FIGMA_ACCESS_TOKEN=figd_… pnpm --filter @servicetitan/carto-tokens figma:sync --dry-run

Mint a PAT at https://www.figma.com/settings → Personal access tokens. Note: the Variables REST API is Enterprise-only — verify the PAT scope checkbox for file_variables:write is available.

CI

.github/workflows/figma-sync.yml validates tokens on every PR, dry-runs on push to main, and performs a real sync on manual workflow_dispatch. The sync itself doesn't notify Slack — that fires from release.yml only when changesets publishes a new @servicetitan/carto-tokens version. Designers get pinged about actual releases, not every manual sync run; the message links to the Figma library so they can verify and publish.

Required repo secrets: FIGMA_CLIENT_ID, FIGMA_CLIENT_SECRET, FIGMA_REFRESH_TOKEN, CARTO_FIGMA_SLACK_WEBHOOK. All four live in 1Password alongside the local credentials. The Slack channel, message format, and Figma library link are owned by a Slack Workflow Builder workflow (same pattern hammer-token uses); the GH Action POSTs { "version": "..." } and the Workflow expands it into the visible message.

Figma variable IDs

Every leaf token in src/ carries the server-assigned Figma variable ID under its $extensions["com.figma.variableId"]. The sync uses this ID — not the variable name — as the durable join key between source and Figma, so a token can be renamed in source and the Figma variable gets renamed in place rather than orphaned.

Atomic leaves (color, dimension, fontFamily, fontWeight, number) store a single string ID:

"0": {
  "$type": "color",
  "$value": { "colorSpace": "srgb", "components": [1, 1, 1], "hex": "#ffffff" },
  "$extensions": { "com.figma.variableId": "VariableID:1234:5678" }
}

Composite leaves ($type: shadow, $type: typography) decompose into multiple Figma variables — one per subprop. They store a nested map keyed by subprop name:

"resting": {
  "$type": "shadow",
  "$value": { ... },
  "$extensions": {
    "com.figma.variableId": {
      "color":   "VariableID:1:1",
      "offsetX": "VariableID:1:2",
      "offsetY": "VariableID:1:3",
      "blur":    "VariableID:1:4",
      "spread":  "VariableID:1:5"
    }
  }
}

The loader treats each map entry as a synthetic child token (e.g. shadow/raised/resting/color) with its own scope and type. Designers can then bind each subprop independently in Figma.

figma:validate (which runs on every PR, offline) requires every token to have a variableId — either a string for atomic leaves or a complete subprop map for composite leaves. Adding a new token in a PR therefore needs the ID to land in the same PR — see below.

Adding, renaming, and removing tokens

| What you did in source | What the sync does in Figma | |---|---| | Added a new token | Creates a new variable, captures its ID from the API response, and writes com.figma.variableId into your *.tokens.json file. Commit the writeback alongside your new token. | | Changed only the value (same ID, same name) | Updates the existing variable's mode values in place. Aliases to / from this variable keep working. | | Renamed a token (same ID, new path/key) | Looks up the variable by ID, sees the desired name has changed, emits an UPDATE that renames the Figma variable in place. No orphan, no duplicate. Existing aliases in Figma continue to resolve because they reference the variable by ID. | | Removed a token from source | Currently no-op — the sync is additive only. The variable stays in Figma until a designer deletes it manually. |

Workflow: adding a new token

Adding a brand-new token requires a local sync run, because Figma assigns the variable's ID server-side and you can't know it ahead of time.

  1. Add the token to the relevant src/<tier>/*.tokens.json file (without com.figma.variableId).
  2. Ensure your Figma OAuth credentials are set up in .env.local (see Credentials above; run pnpm figma:auth if you haven't).
  3. Run pnpm --filter @servicetitan/carto-tokens figma:sync. The script creates the variable in Figma, captures the new ID, writes it into your token file, and reports the change.
  4. Commit the resulting diff (your new token and its inserted ID) and open the PR. CI validate will pass.

Workflow: renaming a token

No Figma round-trip needed at PR time — the ID is already in $extensions and travels with the token entry. The next manual figma:sync will rename the Figma variable to match.

Failure modes

  • Token X has no Figma variable ID. — CI validate is blocking on a new token. Run pnpm figma:sync locally and commit the writeback.
  • Token X has Figma variable ID Y but no variable with that ID exists. — The Figma variable was deleted manually. Downstream consumers are also broken; restore the variable in Figma rather than auto-healing the source.
  • Two source tokens claim the same Figma variable ID. — Source-side inconsistency. One of the tokens has a copy-pasted or merge-conflicted ID that needs correction.

Syncing tokens on a PR

When a PR touches packages/carto-tokens/src/**/*.tokens.json, figma-pr-sync.yml (separate from the required-check figma-sync.yml) dry-runs the sync. If any variables would be created or updated, a bot posts a sticky comment on the PR requesting a Figma branch URL for the Carto Tokens library.

Once a contributor replies with a URL (https://www.figma.com/design/<fileKey>/branch/<branchKey>/…):

  1. The bot validates the URL points at the Carto Tokens file — any other file is rejected.
  2. It syncs tokens to that Figma branch (never to the main library from PR flow), which mints server-side variable IDs.
  3. The new com.figma.variableId values are committed straight to the PR branch as a single commit (pushed with secrets.PAT so the required check re-runs).
  4. figma:validate — the merge gate — re-runs on that commit and turns green, because it requires every token to carry a com.figma.variableId.
  5. The contributor who supplied the URL is pinged that the Figma branch is ready. Merging that Figma branch into the main library is the designer's call, done in Figma whenever they're ready.

Safety: the comment-triggered job runs only for commenters with repo association OWNER/MEMBER/COLLABORATOR, and it never writes to the main Figma library from PR flow.

Rotating Figma OAuth credentials

The OAuth refresh token Figma issues is long-lived (months to years in practice), but Figma's API may rotate it on any refresh call — the response to POST /v1/oauth/refresh can include a new refresh_token field, in which case the previously-stored value is invalidated server-side. When this happens, scripts/figma/auth.ts detects the change and warns. It does not auto-write the new value anywhere — that's a manual operator step.

The warning behaviour differs between local runs and CI to avoid leaking the new token through workflow logs:

| Where the rotation happens | What auth.ts does | Operator action | |---|---|---| | Local terminal (no GITHUB_ACTIONS env) | Prints the new refresh_token plainly to stderr, along with the three update locations. | Copy the printed value → update .env.local, 1Password, and the GitHub repo secret with it. | | GitHub Actions runner (GITHUB_ACTIONS=true) | Emits ::add-mask::<token> first, then prints a warning that does not include the value. The mask command registers the new token as a secret on the runner; any subsequent log line containing it gets replaced with ***, so the rotated value is not retrievable from run logs (intentionally — protects against accidental leak). | Re-mint a fresh token via pnpm --filter @servicetitan/carto-tokens figma:auth locally, then update all three locations (.env.local, 1Password, the GitHub secret). Note that re-minting issues yet another new token, which is what you want — the rotated one is gone either way once the CI run ends. |

Symptom of a missed rotation (auth.ts already had the rotated value by the time you noticed, or rotation happened in CI but the secret wasn't updated):

AuthenticationError: OAuth refresh failed (400): {"error":"invalid_grant","error_description":"The refresh token is invalid or expired"}

If CI fails this way but pnpm figma:sync --dry-run works locally, the local .env.local has a value that's still valid and the GH secret is stale — copy local → GH and you're back. If both local and CI fail, re-mint with pnpm figma:auth and update all three locations.

The trade-off is intentional: locally-readable rotated tokens are convenient for solo dev work; CI-masked tokens force a re-mint but never leak through Actions read-access. If the team later decides the convenience-of-CI-readability outweighs the leak risk for the workflow runs that designers actually care about, the masking branch in auth.ts is a one-line flip.

Theming

Web (vanilla-extract)

The vanilla-extract output is structured as three createTheme layers — primitive, semantic, component — so alias relationships survive into the generated CSS variables.

Apply the theme at the root

// app/layout.tsx (or your root component)
import { cartoThemeClass } from "@servicetitan/carto-tokens/vanilla-extract";

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html className={cartoThemeClass}>
      <body>{children}</body>
    </html>
  );
}

cartoThemeClass is the composed primitive + semantic + component class string. Apply it once at the root; the CSS variables cascade everywhere underneath.

Build a component

Use vanilla-extract's style() helper in a .css.ts sibling file to author styles that reference the token proxies. Each proxy property resolves to a var(--…) reference, so updates to a token at any tier cascade live without a recompile.

// IconCard.css.ts
import { style } from "@vanilla-extract/css";
import {
  component,
  semantic,
  primitive,
} from "@servicetitan/carto-tokens/vanilla-extract";

export const card = style({
  // Component tier — token assigned to a specific component slot.
  width: component.icon.size.medium,

  // Semantic tier — intent-based.
  backgroundColor: semantic.background.color.standard.resting,
  color: semantic.foreground.color.standard,
  borderColor: semantic.border.color.subtle,
  borderRadius: semantic.radius.moderate,
  padding: semantic.spacing.base,

  ":hover": {
    // Alpha-overlay token — resolves to color-mix(in srgb, var(--carto-color-neutral-400), transparent 92%).
    backgroundColor: semantic.background.color.ghost.hover,
  },

  // Primitive tier — raw scale values, used when no semantic token fits.
  outlineWidth: primitive.size["200"],
});

Light/dark

Light/dark switching is handled by the CSS light-dark() function — the browser tracks prefers-color-scheme automatically. To force a mode, set color-scheme: light or color-scheme: dark on a wrapping element.

Web (Motion / JS values)

The vanilla-extract output exposes motion tokens only as CSS custom properties — great for CSS transition / animation, but unusable from a JS animation library. Framer Motion (motion) needs the raw values: its ease prop wants a [x1, y1, x2, y2] tuple (never a cubic-bezier(...) string) and its duration prop wants a unitless number in seconds. The /motion subpath provides exactly that — inlined literals mirroring the motion.* token namespace.

import { motion } from "motion/react";
import { easing, duration, scale } from "@servicetitan/carto-tokens/motion";

function Toast() {
  return (
    <motion.div
      initial={{ opacity: 0, y: 8 }}
      animate={{ opacity: 1, y: 0 }}
      exit={{ opacity: 0, y: 8 }}
      whileTap={{ scale: scale.feedback }}
      transition={{ duration: duration.transition, ease: easing.enter }}
    />
  );
}

Each group is also available on a combined motion object (import { motion as motionTokens } from "@servicetitan/carto-tokens/motion"). easing values are typed as the exported CubicBezier tuple; duration values are seconds; scale values are unitless multipliers (scale.1000 = 1.0). For CSS-driven motion, keep using the vanilla-extract semantic.motion.* CSS variables instead.

Swift

Build a component

Coming soon!

Kotlin

Usage

Import the dependency via our Internal Maven repository

  1. Add carto = { group = "com.servicetitan", name = "carto-tokens", version.ref = "<insert_version>"} to libs.versions.toml
  2. Add the carto dependency to any module using the tokens
  3. Reference the tokens using CartoTokens as the root
Example:
Icon(
    icon = iconStart,
    contentDescription = label,
    loading = loading,
    size = CartoTokens.Icon.sizeMedium,
    tint = iconTint
)

Build a component

Coming soon!

React Native

import { light, dark } from "@servicetitan/carto-tokens/react-native";
import { useColorScheme } from "react-native";

function useCartoTheme() {
  return useColorScheme() === "dark" ? dark : light;
}

// in a component:
const t = useCartoTheme();
<View style={{ backgroundColor: t.semantic.background.color.standard.resting }} />;

Values are fully resolved for the platform: dimensions are unitless numbers, alpha colors are baked #rrggbbaa, shadows are boxShadow strings (RN 0.76+), and cubicBezier tokens are [x1,y1,x2,y2] tuples (spread into Easing.bezier(...)).