CLI tool
Railly/tinte avatar
Railly/tinte

Tinte compiles a design system into an Agent Plugin with SKILL.md and tokens.css

Compile your design system into an Agent Plugin. Extract an identity from any reference, emit SKILL.md + tokens.css, lint what bypasses your tokens.

625 stars39 forksTypeScriptMIT

At a glance

What is it?
Tinte is a TypeScript monorepo whose CLI turns one identity config into an installable agent plugin containing a SKILL.md of composition rules and a tokens.css the agent reads as an API, plus a lint command that fails when a colour bypasses the tokens. It also keeps the older theme workbench that converts one reference into 19+ output formats, and a separate screenshot service.
Who is it for?
Adopt Tinte if you want a coding agent to write UI that follows your design system, and you can describe your identity in a tinte.config.json that the compiler reads. Do not adopt it if your design system is defined in files the compiler does not read, because extraction, compilation and linting all consume the same config and nothing is restated by hand.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 76 days ago.
What is it written in?
Mainly TypeScript, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

One config, three commands: extract, compile, lint

Tinte compiles your design system into an Agent Plugin. The premise is stated plainly at the top of the README: a coding agent writes on-brand UI when it can read your tokens as an API and your composition rules as instructions, and Tinte compiles both from one identity config into a single installable artifact.

The quick start is four commands, and they map onto the three stages:

bash
bunx tinte from --emit-script       # print the extraction script for a live page
bunx tinte from --normalize <json>  # turn captured styles into a draft identity
bunx tinte build --plugin           # compile the identity into an Agent Plugin
bunx tinte lint <paths>             # exit 1 on any color that bypasses the tokens

The from command has two modes. With --emit-script it prints a script you run against a live page, and with --normalize it turns the captured styles into a draft identity. The build command compiles that identity into a plugin, and lint is the enforcement step, exiting with status 1 on any colour that bypasses the tokens.

The sentence that ties them together is worth repeating because it is the design constraint of the whole project: extraction, compilation and enforcement all read the same tinte.config.json, and nothing is restated by hand. That is the difference between a theme generator and this. A generator that writes a dozen files from a palette is a one way street. Tinte keeps the config as the single source of truth and reads it three times, once to derive the identity, once to compile the artifact, and once to judge whether the code obeys it.

The plugin is a skill file and a token file, and nothing else

The output of the build command is a directory with a fixed shape:

code
acme-plugin/
├── plugin.json
└── skills/
    └── acme-design/
        ├── SKILL.md
        └── references/
            └── tokens.css

Two files carry the design system, and the division between them is the interesting part. SKILL.md carries the type scale, the voice and the composition law, which is the instruction half. tokens.css is the token file the agent reads as an API, which is the data half.

Putting the tokens under a references/ directory inside the skill is a deliberate convention rather than an implementation detail. The agent loads the skill, and the reference file is available when it needs the concrete values, so the instruction text stays short enough to be read every time and the token table is pulled in only when it matters. The plugin.json at the root is what makes the directory installable as a plugin rather than merely a folder of files.

The design intent behind the layout is that the artefact is portable. Nothing in the output points at a build server or a private registry, so the plugin can be committed to a repository, shared, or installed the way any other agent plugin is installed.

The project's own site is compiled by the same pipeline

There is a self hosting detail that says more about the design than any of the feature descriptions. tinte.dev is styled by the plugin Tinte compiles from its own config, and both artefacts are fetchable at their real paths, tinte.dev/design.md and tinte.dev/tokens.css.

The README's conclusion is that an agent can install this design system the same way it would install yours. That is the whole point of the format. A design system that only exists as a Figma file, or as a set of CSS custom properties inside an application, cannot be installed by an agent. One that exists as a skill and a token file at a URL can.

The repository layout backs the claim up. There is a tinte.config.json at the root, and the package.json scripts include generate:design, which runs generate:artifacts and then generate:plugin, both of which invoke the CLI directly against that config with an output directory under apps/web/public. So the site's own design context is generated by the same command a user would run, into a public directory that is then served.

There is also a skills/ directory at the repository root and a CLAUDE.md, which suggests the project uses the artefacts it produces for its own agent workflows, and an apps/ directory holding what looks like a web application alongside the packages.

A monorepo of three packages on Bun, with a legacy theme builder alongside

The repository is a Bun workspace with packages/* and apps/*, and the root package.json is private. The three published packages are described in the README.

@tinte/core holds theme primitives, the OKLCH color model and type definitions. @tinte/providers holds 19+ format converters covering shadcn, VS Code, terminals and design tools. The tinte package itself is the Agent Plugin compiler, with the from, build and lint commands, and it doubles as the theme installer.

That last point is the part that makes Tinte more than one thing. The classic workbench is still there: start from a reference, convert to any provider, install it, with bunx tinte <theme-slug> installing into VS Code, Cursor or Zed, and npx shadcn@latest add pulling a preset. Thirteen semantic OKLCH tokens compile to shadcn/ui presets, VS Code themes, terminal configs for Alacritty, Kitty, Warp and Windows Terminal, and 19+ formats from the same source of truth.

So the newer agent plugin path sits on top of a converter library that already knew how to express one palette in many dialects. The OKLCH choice is what makes that work, since a perceptually uniform space gives the converter predictable lightness relationships across formats that each have their own colour model.

The tooling is Bun throughout, with biome for linting and formatting, simple-git-hooks for a pre-commit and commit-msg hook, and a script that validates commit messages. The last push was on 2026-08-08 and the only release tag is v1.3.0 from 2026-08-07, so the version line is young.

The lint command is the part that changes daily work

Most design system tooling stops at generation. Tinte's third command is the interesting one, and it is the one that changes how a team works rather than how it sets a theme up.

bunx tinte lint <paths> exits 1 on any colour that bypasses the tokens. That is a check you can put in a pre-commit hook or a CI job, and the failure mode it prevents is the usual one: a developer hardcodes a hex value because the design system does not have the colour they needed, and the hardcoded value ships.

The README frames this as a contract rather than a lint rule. Extraction, compilation and enforcement read the same config, and nothing is restated by hand. If the token file and the lint rule are generated from one source, they cannot drift apart, which is the failure mode that makes a tokens file useless after six months.

The related product line shows where this is heading. There is a separate service at ray.tinte.dev for code screenshots and theme extraction, offering 500+ syntax themes across 16 languages with PNG and SVG export, a free REST API with 60 requests per minute and no auth, and two endpoints, one to extract a colour theme from an image and one to screenshot code with any Tinte theme:

bash
curl -X POST https://ray.tinte.dev/api/v1/screenshot \
  -H "Content-Type: application/json" \
  -d '{"code": "const x = 42;", "language": "typescript", "theme": "one-hunter"}' \
  --output screenshot.png

The ecosystem table in the README puts the three products in one line: Tinte generates the system, Elements installs it, Ray shows it. Elements is the shadcn registry route, and the preset API makes every public Tinte theme installable as a shadcn registry:base item, with a font variant and a pack endpoint that returns base, fonts and install commands together.

Where the approach stops: one config, one extraction model

The limitation follows directly from the design. Because everything reads tinte.config.json, the quality of the result is bounded by that file and by the extraction step that produces it.

The extraction is a capture of a live page or a reference, and it produces a draft identity. A draft is a starting point, and the README does not describe a review or merge workflow for it, so a designer or engineer has to review the generated identity against their real palette by hand before compiling. A tool that guarantees the config is the single source of truth does not also guarantee the config is correct.

The lint command has the matching weakness. It can catch a colour that is not one of your tokens, but the README does not say it checks anything else. Type scale drift, spacing that is off, a component that composes wrongly, none of that is described as in scope. And the lint rule only reaches files you pass it, so a codebase that generates styles at runtime or in a different directory needs that path included.

There is also a scope question the README does not answer. The theme builder path reaches nineteen plus output formats, and the plugin path reaches an agent skill. Whether the composition law in SKILL.md survives contact with a real codebase depends on how specific it is, and the README says the type scale, the voice and the composition law are carried, without showing what a composition law looks like in the emitted file.

The alternative to Tinte is to write the skill file by hand and keep the tokens in your application, which is what most teams already do. The difference in approach is that Tinte generates the skill from your existing theme data, so the instruction half and the data half start in agreement. If your design system is small and stable, that generation step may be more machinery than the problem needs, and the lint command is the part that pays for itself.

Editorial conclusion

Adopt Tinte if you want a coding agent to write UI that follows your design system, and you can describe your identity in a tinte.config.json that the compiler reads. Do not adopt it if your design system is defined in files the compiler does not read, because extraction, compilation and linting all consume the same config and nothing is restated by hand. Verify first that the extracted identity matches your actual palette and type scale, since the compiler will not correct a wrong source of truth, that the lint rule covers the files you intend to guard, and which of the three commands in the quick start you will run first.

Frequently asked questions

What does Tinte compile and from what?

Tinte compiles your design system into an Agent Plugin from one identity config. The emitted artefact contains a plugin.json plus a skill directory holding SKILL.md with the type scale, voice and composition law, and a references/tokens.css file that the agent reads as an API.

How do I install and use the Tinte CLI?

The commands run through bunx. Use bunx tinte from --emit-script to print an extraction script for a live page, bunx tinte from --normalize with a JSON argument to turn captured styles into a draft identity, bunx tinte build --plugin to compile the identity into a plugin, and bunx tinte lint with paths to fail on colours that bypass the tokens.

What does the Tinte lint command check?

It exits with status 1 on any colour that bypasses your tokens, when run against the paths you pass it. Extraction, compilation and enforcement all read the same tinte.config.json, so the rule and the token file are generated from one source of truth.

What output formats can Tinte convert a theme into?

Thirteen semantic OKLCH tokens compile to shadcn/ui presets, VS Code themes, terminal configs for Alacritty, Kitty, Warp and Windows Terminal, and 19+ formats in total from the same source of truth. Every public Tinte theme is also installable as a shadcn registry:base item.

Can an agent install the design system Tinte compiles?

Yes. tinte.dev is styled by the plugin Tinte compiles from its own config, and both artefacts are fetchable at their real paths, tinte.dev/design.md and tinte.dev/tokens.css. The README states that an agent can install this design system the same way it would install yours.

Official sources

  1. License: MIT
  2. Project website
  3. Railly/tinte on GitHub
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/railly-tinte.svg)](https://hysenlabs.com/projects/railly-tinte)

Was this useful?