Zengin is a design system you own, built to stay yours. One command creates it with the components copied in, and a deterministic engine checks every edit after that, whether a person or an agent made it, and hands back the fix. The agent writes the code. Zengin keeps it on the system.
A design system is a set of rules nobody can enforce at the moment the code is written. A coding agent makes that worse in the most ordinary way: it writes more code, faster, and every off-system value it reaches for looks fine in a screenshot. Zengin exposes the rules as a deterministic engine. Code goes in, structured violations with the fix attached come out, and the engine runs wherever the agent works. Everyone else serves the agent context. Zengin serves it constraints.
Solo: product, design, engineering, and the marketing site, with Claude as the daily collaborator. The product is the environment it was built in.
At a glance
- Problem. Coding agents produce interface code faster than any team can review it for design-system drift, and the drift looks correct in a screenshot.
- Built. A component system people own, plus a deterministic engine exposed through an MCP server, an edit hook, the CLI and CI, and a rollup across every repository on the system.
- Surface. Thirty-three components, eight working templates, Storybook, six themes, ten font pairings, eight icon sets, and the tooling that scaffolds all of it.
- Validation. Three public React codebases, 424 violations read and classified by hand, every engine miss turned into a regression test.
- What it shows. Product framing, design-system architecture, developer experience, AI tooling, and the governance that lets a team use all three at once.
The thesis
Most of the industry's answer to "the agent drifted off the design system" is more context: a longer prompt, a bigger rules file, a better model. That does not hold. Context is a suggestion. The agent can read it and still write #2563EB where the token was --color-primary, because the two render identically and nothing stops it.
The thesis in one image pair, from the replayable demo on the site: an off-system version of a component and a corrected one that render pixel for pixel the same. A screenshot review passes the off-system version. The engine does not.
So the rules had to be a constraint, not a suggestion. Deterministic, so the same input always gives the same answer. Fast, so it can run after every edit. And attached to the fix, so the agent can apply it without asking.
How it works
Nothing to train and nothing to prompt. The system describes itself once, and the engine holds every agent to it.
- Definitions your system already has. A tokens file in the W3C design-token format, and a components manifest: the props each component declares, the values it accepts, the properties it owns, and what it replaces. Write them by hand, or derive them:
zengin init --from shadcnreads a shadcn project, andzengin init --from package <name>reads any installed design-system package that ships CSS variables and type declarations. - A deterministic engine. It parses TSX and CSS, resolves class names through the project's own stylesheets (and through Tailwind, if the project uses it), and runs seven rule kinds. Every violation carries the fix and how sure the engine is of it: exact, nearest, or none. No model in the loop.
- Wherever the agent works. An MCP server for tools it can call, a hook that runs after every edit, a CLI for the terminal and CI, and a rollup across every repository that consumes the system. One engine, four surfaces, the same violations in each.
The seven rule kinds fall into three families. Foundation rules guard the tokens: color-literal, a hex, rgb, oklch or named color where a token reference is required; spacing-literal, an arbitrary length on margin, padding, gap or a scroll offset; token-reference, a utility or var() pointing at a token that does not exist. Contract rules guard the components: unknown-prop, a prop the manifest does not declare; unknown-prop-value, an enum value the component does not accept, including one from a newer version than the project pins; classname-policy, a class name or style setting a property the component owns. The substitution rule, component-substitution, catches the system being rebuilt by hand: an import from a package the system shadows, or a raw element styled to look like a system component.
Seven rule kinds, three families. Foundation rules run in consumer and owned files, never in theme files. Contract and substitution rules run only in consumer files: a forked component is yours to style.
Suppression is deliberate. One line suppresses one violation, and only with a reason. Without the reason nothing is suppressed, and the rollup counts it.
One engine, four surfaces
The same violation with the same fix, whether the agent asked first, edited a file, opened a pull request, or the team looked across every repository.
- The MCP server. Four tools over stdio. The agent can describe the system before it starts, check a snippet before it commits to it, list the violations in the files in scope, and read what each rule means and how to suppress one.
- The edit hook. A Claude Code hook that runs after every file write. It blocks the write, with the violations as the reason, so the agent fixes the code before anyone reviews it.
- The CLI. A check for pre-commit and CI, scoped to changed or staged files, with GitHub annotations on the pull request.
- The rollup. A report from each consuming repository and a rollup across them: drift, adoption, suppressions, owned forks, pinned versions, and the deltas since last time. Zengin hosts its own at zengin.timurtek.com/rollup, rebuilt on every push.
The MCP server: four tools the agent calls before and after it writes, installed with five lines of JSON.
Run on code nobody wrote for it
An engine that only passes its own fixtures proves nothing. So the first real question was the false-positive rate on public code that had never heard of Zengin, with definitions derived from the project's own theme rather than invented.
- shadcn/taxonomy. First run, 41 violations. Every one read and classified by hand. Six were engine false positives and four were definition false positives, and each became an engine change with a regression test. Second run, 35 violations, all true. Among them a real typo shadcn shipped in its own toast component, and dead classes that had rendered nothing for years.
- vercel/ai-chatbot. Tailwind 4, the unified radix-ui package, oklch colors. 128 violations on the first run, 109 after the fixes, zero false positives by hand classification. It found two custom variant classes that had not survived the project's Tailwind 3 to 4 migration and rendered nothing.
- umami. The first system that was not shadcn:
@umami/react-zen, read straight from the installed package by the CSS adapter, tokens from its variables with the names kept and a manifest from its type declarations. 255 violations on the first run, 147 after the fixes, zero false positives. Among the real ones, an invalid DOM attribute and avar()whose fallback is what actually renders.
Every violation across the three runs was read and classified by hand, 424 of them, and none of the remaining ones is a false positive. The counts do not fall to zero on purpose: the drop is the engine getting corrected, and what is left is real drift in those codebases, for their maintainers to keep or fix. A run that ended at zero would mean the rules had been bent until they found nothing. No run used a hand-written definition. The recorded lesson is the useful part: the rules did not change between 2023 shadcn and 2026 shadcn, or between shadcn and a system that had never heard of it. The edges did, attribute lists, type-only imports, accessibility utilities, framework extend semantics, and those are fixed once, centrally.
Three public codebases, every violation read and classified by hand. The write-ups are in the repository.
Start from something real
Building the engine changed what the product is. Nobody wakes up wanting a conformance layer. People want to start a product and have it look finished: components they own, a brand they can change in one place, Storybook, accessibility, releases, and an agent that stays on the system. So the design system went in front, shadcn-style, and the engine became the feature shadcn cannot copy. What gets copied into your project stays understood, so it can be checked, tracked and upgraded.
- The registry. One command scaffolds a project that owns its components, its definitions, a brand file, Storybook, the release setup, and the MCP server and hook. Another adds components. Every file carries the version it was copied from, so a third command can take what the system changed since you copied it: a local edit stays yours, a conflict shows its diff. A mock command generates typed, seeded data for the entities apps show, as plain TypeScript with no runtime dependency.
- Templates. Eight, each a working app on Zengin UI: blank, a marketing page, a review workspace, a SaaS dashboard, an AI chat product on the Vercel AI SDK, auth, docs, and a storefront. Each arrives checked, and the site shows them as live previews.
- Themes, fonts, icons, brands. A brand is one token file. A theme command swaps the whole look, six to choose from. A fonts command sets a pairing for headlines, text and code from ten curated ones, self-hosted if you want Google out of the runtime. An icons command redraws the sixty-three-name icon vocabulary from any of eight icon sets, and the names in your code never change. A brand command derives a palette, favicon and wordmark from a name, a logo, or one color.
- Zengin UI. The reference system the registry copies from. Plain CSS on custom-property tokens, so it works in any bundler. Radix for behavior. Light and dark attach to any element, so a hero can run in the opposite scheme to the page. Storybook with a theme toolbar, contrast tests, and thirty-three components at last count, served from the marketing site.
- Figma, both ways. Tokens to Figma variables with light and dark modes and code syntax set to the CSS variable, and back with a report. Code Connect files from the manifest.
Every template is a working app on Zengin UI, served from the same registry the CLI reads. Pick a theme and every preview changes.
Same foundations, two treatments. A brand is one file; no component changes between them.
Zengin UI in Storybook, public on the marketing site. Every component with its docs, the AI kit included, and a theme toolbar to flip the scheme.
Engineering
A pnpm monorepo of twelve packages, public on npm under the @zenginui scope since 2026-09-13, all MIT: the engine, the MCP server, the hook, the CLI, the shadcn and CSS adapters, the rollup, the registry, the mock generator, the Figma bridge, the reference system, and the unscoped create-zengin that makes the start command the npm convention, npm create zengin. CI on every change and releases through Changesets, so a change a consumer should know about ships with a note naming the packages and the bumps, and a change to the engine releases the surfaces that depend on it.
The engine's own fixtures are the three violation examples from the design record it was built against, written twice: once with Tailwind utilities and the adapter on, once in plain CSS. Every field-test finding became a regression test.
Every example app is itself a consumer of Zengin UI and reports on every push to main; the marketing site rolls the snapshots up at build time, so its drift-and-adoption page is live data from real repositories, a sparkline each.
An agent skill teaches a coding agent the loop: learn the system, write, check, apply fixes by confidence, re-check, and never suppress without a reason.
How it was built
Solo, from 2026-09-11 to 2026-09-13, fifty-three commits, with Claude Code as the pair programmer throughout. That is not a footnote. It is the thesis tested on its own construction: the engine was designed against three hand-written violation examples before any code existed, the field tests ran on real codebases at each phase, and every one of the site's own pages and templates is checked by the engine on every push. A product about holding agents to a system was built by an agent held to that system.
The product went through three vision documents in two days. The first made the engine the product. The third put the generated, owned design system in front and the engine behind it as the feature nobody else can copy, because that is the order in which people buy.
Where it stands
Public since 2026-09-13. The first release went to npm that day, twelve packages at 0.1.0, and one command starts a project with the components copied in and the engine, the MCP server, the hook and Storybook wired:
npm create zengin@latest acme -- --template saas
The marketing site serves the public registry the command reads, so there is no infrastructure behind it. The core is MIT; templates, themes and kits are the paid layer.
The first measure of success is the honest one: three paying customers for templates or themes before any hosted service exists. None yet. Until then Zengin runs on its own marketing site and its own workspaces, which is the point. The product is the environment its author works in. Next: the product's own documentation page on the docs template, scaffolding through the MCP server so an agent can build an application on the system end to end, and a store for templates and themes.
Stack: TypeScript, a pnpm monorepo, a stdio MCP server, a Claude Code PostToolUse hook, a CLI with GitHub annotations, plain CSS on W3C design tokens, Radix, Storybook, Changesets to npm, the Figma variables and Code Connect APIs.
