Skip to content
eugenioenkoPublic

About

Yet another react component library. Your config file is the design system: every key becomes a typed prop and a rendered style.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

yarcl

A React component library where the consumer's config is the design system.

The library ships mechanisms. Your config file makes the decisions: which colors, sizes, radii, variants, spacing, text styles and densities exist. Every key you define is at the same time

  • a valid prop value: <Button size="talla-xl" /> compiles only if talla-xl is in your config, and
  • a rendered style: the plugin generates CSS for it at build time.

No codegen, no module augmentation, no provider. One file.

// src/yarcl.config.ts
import { defineConfig } from '@yarcl/react/define';
import defaults from '@yarcl/react/defaults';

export default defineConfig({
  ...defaults,
  colors: {
    ink: { light: '#1c1917', dark: '#f5f5f4' },
    clay: { light: '#a4441f', dark: '#f0a07a' },
  },
  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' },
  },
  defaults: { ...defaults.defaults, size: 'talla-m', color: 'ink' },
});
<Button size="talla-m" color="clay" />  // ✓
<Button size="md" />                    // ✗ Type '"md"' is not assignable to type '"talla-s" | "talla-m"'

How it works

  1. as const + keyof typeof: defineConfig keeps literal types, and the library derives Size, Color, Variant, … from them.
  2. A module alias: the library imports @yarcl/config. The build plugin points it at your config file at runtime, and a matching paths entry in your tsconfig.json does the same for types. If your file doesn't exist, both fall back to the library defaults.
  3. Build-time CSS: the plugin loads your config and serves @yarcl/react/styles.css: CSS variables on :root plus one yarcl-{group}-{key} class per key. Components only set class names; no inline styles, nothing computed at runtime. Editing the config hot-reloads the CSS.

Why an alias instead of module augmentation or codegen: see ADR 0001.

Setup

From an existing React project using Vite, webpack, Rspack, Rollup or esbuild:

npx @yarcl/react init

This installs the package, detects the build tool, connects it and TypeScript to the same config, and creates src/yarcl.config.ts. For manual Vite setup:

npm install @yarcl/react
// vite.config.ts
import react from '@vitejs/plugin-react';
import yarcl from '@yarcl/react/vite';

export default defineConfig({ plugins: [react(), yarcl({ config: 'src/yarcl.config.ts' })] });

Use the matching @yarcl/react/webpack, /rspack, /rollup or /esbuild entry point for another build tool. See the installation guide for complete examples.

// tsconfig.json
{
  "compilerOptions": {
    "paths": {
      "@yarcl/config": ["./src/yarcl.config.ts", "./node_modules/@yarcl/react/dist/yarcl.config.d.ts"]
    }
  }
}

What the config controls

Group Keys Used by
colors open, { light, dark, on? } → light-dark() color prop everywhere
neutrals bg, surface, text, muted, border + any surfaces, text, borders
sizes open, { height, paddingX, fontSize, iconSize } every control; same size = same height
radii open; conventionally sm, md, lg, xl plus exceptions (square, rounded) radius prop; defaults.radius (e.g. md) applies to every component
variants open recipes: background / border / text variant prop (Button, Badge, Alert, …)
spacing open gap, padding
shadows open shadow prop, floating panels
density open, { paddingX, paddingY, fontSize } Table
modalSizes open, widths Dialog and Drawer size
typography fontFaces, families, fonts, styles (open), headings h1–h6 Text, Heading, labels
zIndex, motion, borders required keys + any layering, transitions
focusRing, defaults references to keys above focus outline, omitted props
components per-component defaults, e.g. { Button: { radius: 'square' } } omitted props, before defaults

A prop resolves as: the prop → an enclosing group (ButtonGroup, RadioGroup, ToggleGroup) → components.<Name> → defaults.

radii: { square: '0', sm: '0.25rem', md: '0.375rem', lg: '0.5rem', rounded: '9999px' },
defaults: { radius: 'md', … },                   // every component: md corners
components: { Button: { radius: 'square' } },   // except buttons: always sharp

defineConfig checks at compile time that every color has both modes, that references (defaults, components, headings, focusRing.color, text style families) point at existing keys, that components only names known components and props they have, and that required keys exist. The plugin warns when a color's foreground fails WCAG AA contrast.

Components

Input accepts startContent and endContent for icons, text and buttons inside its border. Padding belongs to the editable input; slot content controls its own spacing. Direct buttons have square inner corners. Native form attributes and refs target the input; className and style target the outer control.

Pagination supports numbered pages with ellipses and layout="compact" for a live page summary with previous and next buttons at the end.

Controls: Button, IconButton, ButtonGroup, SplitButton, ToggleGroup, Input, NumberInput, ColorPicker, Textarea, Checkbox, Radio, RadioGroup, Switch, Slider, Select, Combobox, DatePicker, Field, Label Navigation: NavItem, NavSection, TreeView, Stepper

Typography & layout: Text, Heading, Link, AppLayout, SplitPane, Stack, Inline, Grid, Card, Divider, VisuallyHidden Floating: Tooltip, HoverCard, Popover, Menu Overlays: Dialog, Drawer, CommandPalette, Toast Data & navigation: Avatar, AvatarGroup, Tabs, Accordion, Table, Pagination, Breadcrumb

Table includes typed column resizing and visibility controls through useTableColumns and native compound parts. Feedback: Badge, Alert, Spinner, Skeleton, Progress, EmptyState Reference: DesignReference from @yarcl/react/reference renders your whole design system from your config.

Extending components

Define component recipes in the config with typed token references, slots, variants and defaults:

export default defineConfig(defaults, (yarcl) => ({
  recipes: {
    Action: {
      slots: { root: { borderRadius: yarcl.radii.rounded } },
      variants: {
        emphasis: { strong: { root: { fontWeight: 700 } } },
      },
      defaults: { emphasis: 'strong' },
    },
  },
}));
import { Button, createComponent } from '@yarcl/react';

const Action = createComponent('Action', Button);
<Action emphasis="strong">Save changes</Action>

The plugin generates CSS and the recipe adds typed props while preserving Button's behavior. Use custom markup with named slots or useRecipe for your own behavior. See the component extension guide.

Themes

@yarcl/react/themes ships nine themes that share the library defaults' keys, so they're interchangeable: Brutalist, Bloom, Compact, Editorial, Cyberpunk, Vermeer, Art Deco, Circuit and Studio. Cyberpunk uses neon accents and luminous panels; Vermeer pairs blue and ochre with full serif typography; Art Deco uses geometric type and gold double frames; Circuit combines cyan and navy with monospace labels; Studio uses heavy typography, large controls and cobalt with citrus accents. Every theme includes light and dark modes.

export { editorial as default } from '@yarcl/react/themes';

@yarcl/react/css exports generateCss, the plugin's generator, for switching themes at runtime. The docs include a theme playground: a full dashboard you can re-theme live.

Repository

Path What
library/ the @yarcl/react package: components, defineConfig, build plugins, CSS generator
consumer/ demo app with one design system; every component, light and dark
docs-web/ documentation site (Astro + Starlight) with live examples and a config playground
e2e/ browser tests; e2e/consumer/ is a fixture app with completely different keys ("Maison Talla"), proving the types come from each app's own config
docs/decisions/ architecture decision records
pnpm install
pnpm dev          # consumer on :5173
pnpm typecheck    # library + both consumers, including @ts-expect-error contract checks
pnpm size         # gzip budgets per package entry and typical component imports
pnpm test         # Vitest browser mode in headless Chrome: behavior, styling and axe audits, light and dark
pnpm test:visual  # screenshot comparisons in the pinned Playwright Linux image, requires Docker
pnpm docs:dev     # documentation site on :4321
pnpm docs:build   # static docs site → docs-web/dist
pnpm docs:api     # API reference from JSDoc → docs/api

The consumer's Bundled themes view and the full theme playground have screenshot baselines for every theme in both color schemes, at desktop and mobile widths. See visual regression tests for filtering runs, reviewing diffs and explicitly updating baselines.

Limitations

  • One config per build. Two brands in one bundle would need a Provider for the runtime values.
  • Types are a development-time guarantee. Keep config changes and deploys in the same build.

Responsive layout

Spacing, layout alignment and Grid columns accept typed maps of your configured breakpoints. CSS applies them while form state and focus stay in place.

<Stack gap={{ base: 'sm', lg: 'lg' }}>
  <Grid columns={{ base: 1, md: 2, lg: 4 }}>
    <Card padding={{ base: 'sm', lg: 'lg' }}>Overview</Card>
  </Grid>
</Stack>

See the responsive props guide for supported props, defaults and custom breakpoint names.

About

Yet another react component library. Your config file is the design system: every key becomes a typed prop and a rendered style.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages