Back in February I wrote a post called Type-Level Library Dependency Injection. The short version: let the app own the config, and let the library derive its types from it.
I ended the post with a list of honest limitations, closed the laptop, and moved on with my life.
Seven months later I opened the laptop again (yeah, I don't open that laptop daily), and two weeks after that there was a component library. It's called yarcl, short for the title of this post, because naming things is hard and honesty is free. Yet I promise, It's not just another framework but a design system designer. No codegen, no module augmentation. One TypeScript config file, and every prop is type-checked against it.
Here's how a blog post becomes 51 components.
The trick, in 30 seconds
You've probably shipped this bug:
useFlag("premiumFeaure"); // typo, returns false, paywall wide open
TypeScript doesn't care, because useFlag takes a string. The fix is three things TypeScript already has:
// your app: the config
export const flags = {
darkMode: false,
premiumFeature: false,
} as const;
// the library: import YOUR config through a module alias
import type { flags } from "@app/flags";
type FlagKey = keyof typeof flags;
export function useFlag(key: FlagKey): boolean { /* ... */ }
as const keeps the literal keys, keyof typeof turns them into a union, and a module alias lets the library import a file that lives in your app. Typos stop compiling. Add a key and it just works. No codegen, no extra .d.ts file.
The original post goes deeper on why, so I won't repeat it here.
The part that wouldn't leave me alone
Flags were the easy example. The interesting one was design systems.
Component libraries usually decide your sizes for you. You get sm | md | lg, and if your designer wants xs, good luck. Or the prop is a string, and "meduim" compiles just fine.
With this pattern, the library doesn't decide anything. You do:
// src/yarcl.config.ts
import { defineConfig } from "@yarcl/react/define";
import defaults from "@yarcl/react/defaults";
export default defineConfig({
...defaults,
sizes: {
"talla-s": { height: "2.25rem", paddingX: "0.875rem", fontSize: "0.75rem", iconSize: "0.875rem" },
"talla-m": { height: "2.75rem", paddingX: "1.25rem", fontSize: "0.8125rem", iconSize: "1rem" },
},
});
<Button size="talla-m" /> // ✓ valid prop, and it renders at 2.75rem
<Button size="md" /> // ✗ Type '"md"' is not assignable to type '"talla-s" | "talla-m"'
The key becomes the type. The value becomes the style. The library has never heard of talla-m and doesn't need to. It just connects the two.
That idea kept bugging me until I built it.
What changed between the blog post and the library
A pattern in a blog post gets to skip the boring parts. A library doesn't. A few things had to grow up.
"Vite only" is gone. The post listed this as the main limitation. yarcl now ships build plugins for Vite, webpack, Rspack, Rollup and esbuild.
The alias needs a tsconfig buddy. I glossed over this in the post. The bundler alias handles runtime, but TypeScript needs a matching paths entry or it has no idea where @yarcl/config lives. yarcl has an init command that wires both up for you, so you don't have to think about it.
npx @yarcl/react init
No config? Still works. If your config file doesn't exist yet, both the alias and the types fall back to the library defaults. You can install it and start using components first, then make it yours later.
Then it got carried away. Light and dark mode for every color, WCAG contrast checks, one size scale shared by every form control, per-component overrides, and 51 components including the tricky ones like DatePicker, Combobox and CommandPalette.
Honest limitations, round two
- It's v0.5.0. Expect some sharp edges.
- React 19 or newer only.
- Types are still erased at runtime. If your config changes between build and deploy, nothing catches it.
- It's a new component library in a world that has plenty. I know. The "yet another" in the name is doing a lot of work.
Try it
Run npx @yarcl/react init in a React project, open src/yarcl.config.ts, rename a size, and watch every stale size prop turn red.
- Docs and theme playground: yarcl.dev
- Code: github.com/eugenioenko/yarcl
- The post that started it: Type-Level Library Dependency Injection
The config file was always there. Now it's the whole design system.
Top comments (0)