Case study by Gabriel Espinheira, senior full-stack engineer
Fabricator UI, shadcn/ui with its own feel
An open-source React component library that installs with the shadcn/ui CLI into projects already using it, with a design language of its own. I built it directing coding agents: first commit 05/10/2026, the CLI on npm as 0.2.1 on 11/10/2026.
Early days: version 0.3.0, served from the docs site.

What the repository says
5,120
registry items compared with ui.shadcn.com on every push
27
builds of every component: 3 primitives, 9 styles
62
components in the docs
61
style files that make the Fabricator look
32
interface sounds, synthesized in the browser
5
install scenarios built from scratch in CI
Counted from the repository on 10/10/2026 at commit a025e94, and from its CI run of 09/10/2026.
Its own look, nothing to migrate
shadcn/ui gives React teams their components as source code, through a CLI and registries anyone can publish. To give a library a look of its own, I could fork those components or ship a package nobody can edit, and both drift away from upstream.
I wanted a third option: a look and motion of its own, installed with the commands a shadcn/ui project already runs, with every upstream release still easy to merge. Here is whose work is whose, all of it under the MIT licence:
- shadcn/ui
- the component source for three primitive libraries, eight styles, the CLI engine and the docs app it started from
- Fluid Functionalism
- the design language by Micka Touillaud: surfaces, motion, fluid hover and scrollbars
- Mine
- the registry layer, the Fabricator style and its overrides, the tokens, sounds and Search, the CLI wrapper, the parity and install tests, and the site
Same components, another feel
The docs site renders the Fabricator style, so it shows what an install gives you. Each film below is a recording of the live site on 11/10/2026.

One highlight that glides
In menus, tabs, sidebars and tables, one highlight springs to the item nearest the pointer, so a list never blinks from row to row and a click in a gap lands on the lit item. Keyboard focus moves the same highlight. It measures items by their layout offsets, so a popup still scaling in can't throw it off, and it runs on CSS springs with no animation library. Twelve components use it in Base UI and Radix, ten in React Aria.

Eight surfaces, one colour
Every surface is a background and a shadow, with no borders: the shadow's 1px ring draws the edge. One base colour, held in two CSS variables, tints all eight levels in light and dark mode. Light mode lowers each level's lightness as the tint grows, because colour needs room below white, and tests check that every level stays in sRGB and every text pair stays AA across the hue wheel.

One API on three primitive libraries
Every component has a source tree for Base UI, Radix and React Aria, with the same names, props, variants and data-slot attributes in each. Switching the docs between them swaps the primitive underneath and leaves the component looking the same. A change to one tree lands in all three in the same change, and a component that replaces a shadcn/ui one only ever adds to its API.

Motion in three springs
Every transition picks one of three tiers: 90 ms for hover and popups, 210 ms for selections and sheets, and 290 ms with a slight bounce for dialogs. Exits run about a quarter quicker. Components never write milliseconds, so a single scale variable speeds up, slows down or stops every animation, and reduced motion keeps the fades and drops the travel.

Sounds without audio files
Thirty-two interface sounds are synthesized in the browser with the Web Audio API, each a few short tones or a burst of filtered noise. Each family of components has its own material: buttons sound like wood, and tabs ring as bells tuned to a chord. One component plays them from attributes the components already carry, and they stay off until someone turns them on. The catalogue follows Bencho's published list of roles and pitches; the synthesis is original.
Placeholders in, styles out
A component's source keeps its structure and behaviour inline and names everything a style could change with a placeholder class, such as cn-button-variant-outline. A style map turns each placeholder into Tailwind classes at build time. That is shadcn/ui's own model, and the Fabricator look is one more style map: 61 CSS files, one per component.
One build compiles every primitive library in every style and serves the result at two addresses. Blend mode compiles the extra components in the style a project already uses; Fabricator mode serves the whole Fabricator look. Behaviour CSS can't express, like Fluid Hover, lives in 48 overrides that compile into the Fabricator style only, so the upstream styles keep their exact output.
Most redesigns are a CSS change.
- Base UI
- Radix
- React Aria
Component source with placeholders
Style maps · 8 upstream, 1 Fabricator
Registry build · 27 combinations
Blend mode · your style
Fabricator mode · its look
The shadcn CLI in your project
Rules that keep it a drop-in
A project on shadcn/ui should take a Fabricator component without changing its own code. The repository's instructions call these rules the contract: a change that breaks one is a bug, even if everything builds.
Superset only
A component that replaces a shadcn/ui one keeps every name, prop, variant, export and data-slot. New props and variants are optional additions.
A valid style id, always
components.json keeps an upstream style id such as base-nova. A custom id would make every bare shadcn add in the project fail against ui.shadcn.com, so the Fabricator look travels in the registry address instead.
Namespaced dependencies
Fabricator items depend on each other as @fabricator/name. A bare name always means shadcn/ui's own item, and is written only when that is the intent.
The token contract
Every shadcn/ui CSS variable keeps its name and meaning, so a project's theme carries over. Fabricator tokens arrive under new names, inside the items that use them.
Three trees, one change
A change to one primitive library's source lands in the other two in the same change, and a deliberate exception says so in the commit.
Rebuilt, never hand-edited
Indexes, the assembled style map and the compiled registry are rebuilt from source. Upstream code enters only through a sync script, onto a branch of its own.

The checks every change has to pass
CI runs on every pull request and every push to main: a full registry build, lint, types and formatting, the unit tests, parity with upstream, then a production build of the site and the install tests. All of it passed on main on 09/10/2026.
Parity with upstream
Every push compares 5,120 compiled registry files with ui.shadcn.com. Apart from rebranded demo content and Fabricator's own items, they match byte for byte, and three bug fixes are recorded as deliberate exceptions.
Installs, built from scratch
The CLI sets up Vite apps on all three primitive libraries, a Next.js app and an existing shadcn/ui project, installs components into each from the locally served registry, and runs a production build of every one.
Contrast as a unit test
Status colours, the surface ladder and the tint are tested for WCAG AA in light and dark, the tint across the whole hue wheel at full strength, so a new shade can't ship with text under 4.5:1.
A build that hung without an error
On Vercel's standard 8 GB build machine, the site's build was killed for memory with no message, and deployments sat on Building until cancelled. I reproduced it in a container with the same limits, brought the build down to 7 GB, and wrote the budget into the repository's rules for every agent.
Most of the lines are shadcn's
Fabricator UI starts from shadcn/ui's repository at a pinned commit, and most of its lines are still shadcn's, unchanged. That's deliberate: the fewer upstream files I edit, the cheaper the next upstream release is to merge, so my work goes into new files wherever it can.
Every commit carries my name, and the work happened in agent sessions: 23 of the 27 commits credit Claude as co-author, and my local logs hold 14 Claude Code and 15 Codex sessions on the repository. PLAN.md records 13 architecture decisions, and the rules tell every agent to read it before touching the registry or the CLI.
It is early. The CLI went to npm as 0.2.1 on 11/10/2026, and the registry is served from the docs site on Vercel until fabricator-ui.com is live. The films on this page were composed and rendered in Relay, from recordings of the live site.
| Origin | Lines | Share |
|---|---|---|
| From shadcn/ui, unchanged | 301,940 | 57% |
| From shadcn/ui, edited | 164,218 | 31% |
| Generated by the build | 20,671 | 4% |
| Added for Fabricator UI | 43,562 | 8% |
The edited files changed by 19,607 added and 12,264 removed lines, mostly demo content rebranded. 48 of the added files are overrides: copies of shadcn/ui components with Fabricator behaviour added.
A hard call: become a registry
Problem
A library with its own look and its own components had to work in the projects people already have, and keep up with an upstream that moves fast. Between March and September 2026, shadcn/ui shipped a new CLI major, a new default primitive library and a new class-merging package.
Options
Forking the components and the CLI gives full control, but every upstream release becomes a manual port, and existing projects can't add a component with the commands they already run. A runtime npm package keeps the code in one place, and takes away the source people expect to own.
Choice
Become a registry the official CLI already understands. A thin branded CLI runs the pinned shadcn package and adds its defaults. The source keeps shadcn's placeholder model, so the design is one more style map, upstream arrives on its own branch through a sync script, and every push proves the upstream items still match ui.shadcn.com.
Cost
Fabricator UI is bound to shadcn's contract: no renamed prop, no renamed token and no style id of its own, so its look has to travel in the registry address. Every item builds 27 times, and the component shards for every upstream style were part of what pushed the site's build past Vercel's 8 GB.
A smaller call: build the site on my laptop
Vercel's build took seven to nine minutes and most of its memory; the laptop builds and uploads in under four. The deploy script sends the finished output, about 48,000 files, as one archive, because Vercel takes 15,000 per upload. The cost is that deploys depend on one machine, so production deploys refuse to run from anything but a clean main that matches GitHub.
Need someone to own the build?
I'm open to a full-time role or a contract build. I read every message myself and reply within 2 business days.
- Full-time role
A senior engineer on your team
Remote on EU hours, or hybrid and on-site in Lisbon.Talk about a role - Contract build
Your product, built end to end
Web, desktop and mobile on TypeScript, Next.js, Convex and Expo, including AI features.Talk about a build
