Skip to content

yarcl

Yet another react component library.
A React component library where your config file is the design system. Every key you define becomes a typed prop value and a rendered style, with no codegen and no runtime theme.
Theme
Brand color
Default radius
Default size
Default padding
Default gap
Color scheme

Create workspace

Beta
14-day trial
No card needed until the trial ends.
yarcl.config.ts
import defaults from 'yarcl/defaults';

export default defineConfig({
  ...defaults,
});

The form is built with yarcl components that take no styling props at all. Everything comes from the config: pick a theme, then override the brand color or any default, and every yarcl component on this page follows, the controls included. For a whole app, open the theme playground.

Picking a component library usually means adopting its design system. Most libraries let you go beyond colors: you can add variants, sizes and default props. But each change lives somewhere different: theme overrides here, a type augmentation there, a wrapper component for the rest. The further your design drifts from the library’s, the more of that glue you maintain.

The alternatives trade one problem for another. Copy-paste libraries give you full control, but you own and maintain every component. Headless libraries give you the mechanics and leave all the styling to you.

yarcl takes a different route: your design system is a single data file. It defines what exists (which sizes, colors, variants, densities), what each looks like, and the defaults, globally and per component. The components provide the hard parts (accessibility, keyboard support, positioning, state), and their types come straight from your file. Add a key and it’s usable everywhere; remove one and every place that uses it stops compiling. No type augmentation, no codegen, no wrappers.

Because a design system is just data, the same components can become four completely different products, and switch between them at runtime.

Everything hangs on one import inside the library:

// inside yarcl
import type config from '@yarcl/config';
export type Size = keyof (typeof config)['sizes'];
export type Color = keyof (typeof config)['colors'];

@yarcl/config isn’t a real package. It’s an injection point that two tools resolve to your file:

vite.config.ts
plugins: [yarcl({ config: 'src/yarcl.config.ts' })] // runtime values and generated CSS
tsconfig.json
"paths": { "@yarcl/config": ["./src/yarcl.config.ts"] } // types

It’s dependency injection at the type level. The library is compiled against your config, so its props accept exactly your keys, its components render your values, and the build generates CSS for them. There’s one source of truth, and if the wiring is missing you get a loud error, not silently wrong defaults. Why an alias and not module augmentation or codegen?

The full story behind this pattern: Type-Level Library Dependency Injection. Let the Consumer App Define the API.

Your config is the contract

Keys become the only valid prop values. Rename a size and every stale usage is a compile error, including the defaults inside the config itself.

Styles generated at build time

CSS variables and one class per key. Components only set class names: no inline styles, no runtime theming, strict CSP and SSR friendly.

Light and dark from one file

Every color is a light-dark() pair. Text colors are computed for contrast, and the build warns when a color fails WCAG AA.

Controls that line up

Buttons, inputs, selects, checkboxes and tabs share one size scale, so controls of the same size always have the same height.

Opinions per component

components: { Button: { radius: 'square' } } makes every button sharp without touching a single call site.

Accessible and documented

Native elements, keyboard support, focus management, and a DesignReference page generated from your config.