Wylie Dog
01 · Architecture·pnpm workspaces

The monorepo that ships everything.

Two packages, two apps, one cascade. Tokens flow into UI, UI flows into Storybook and the public showcase — all wired at install time, no copy-paste between repos.

Packages
2+ 2 apps
Token tiers
3primitive → comp.
Cold build
11stokens + ui
Dependency graph
App10.2.1

apps/storybook

component docs

AppNext.js 16

apps/showcase

this site

Package · componentsv1.4.0

packages/ui

42 React components on Radix

Package · tokensv1.4.0

packages/tokens

1184 OKLCH design tokens

Figma plugin

Token Bridge

workspace depsync onlygraph in sync · 4m ago
4 nodes · 3 edges
01 · Workspaces

Two packages, two apps.

Each workspace has one job. Tokens publish raw values; UI publishes typed components; Storybook documents them; Showcase puts them in front of the world. No package reaches across — all dependencies flow upward through @wyliedog/*.

@wyliedog/tokens

packages/tokens

v1.4.0

The single source of truth. Source JSON files plus a Style Dictionary config that emits tokens.css with primitive, semantic, and component tiers as CSS custom properties.

Stack
Style Dictionary · TypeScript
Outputs
tokens.css · tokens.js · tokens.d.ts
Depended on by
@wyliedog/ui
1184 tokens · 6 categories

@wyliedog/ui

packages/ui

v1.4.0

42 typed React components, each a forwardRef wrapper around a Radix primitive with CVA variants. Imports tokens.css and re-exports it as @wyliedog/ui/styles.

Stack
React 19 · TS 5.9 · Vite 7
Foundation
Radix UI · CVA · Slot
Depended on by
storybook · showcase
tree-shakeable · subpath exports

@wyliedog/storybook

apps/storybook

v10.2.1

Component documentation and the pattern playground. Every export from @wyliedog/ui has a story; every story is its own a11y harness, prop matrix, and visual diff target.

Stack
Storybook 10.2 · Vite 7
Consumes
@wyliedog/ui
Deploy
wyliedogstorybook.com · Vercel
184 stories · 0 a11y warnings

@wyliedog/showcase

apps/showcase

Next 16

The page you're reading. A Next.js 16 App Router site that consumes @wyliedog/ui as its only component dependency — the marketing site eats its own design system.

Stack
Next.js 16 · App Router
Consumes
@wyliedog/ui
Deploy
wyliedog.dev · Vercel edge
Lighthouse 100 · LCP 0.8s
02 · Token pipeline

From JSON to CSS variables, in three tiers.

Source files describe the design intent in human terms. Style Dictionary expands them into raw values, role aliases, and component-specific surfaces — all emitted as a single tokens.css that Tailwind 4 reads at build time.

01 · source
packages/tokens/src/

color.primitive.json

{
"color": {
"violet": {
"500": { "oklch(54% .18 274)" }
}
}
}
184 primitive entries
02 · transform
style-dictionary.config.ts

build pipeline

  • 1parse JSON refs
  • 2resolve alias chains
  • 3attribute/cti naming
  • 4format → css/variables
  • style-dictionary build2.4s
1184 vars emitted
03 · output
packages/tokens/dist/

tokens.css

/* primitive */
--color-violet-500: oklch(54% .18 274);
/* semantic */
--color-interactive-primary:
var(--color-violet-500);
/* component */
--color-button-primary-bg:
var(--color-interactive-primary);
184 primitive · 112 semantic · 44 component
04 · consume
JSX · Tailwind 4

Button.tsx

<button
className="
bg-(--color-button-primary-bg)
text-(--color-text-inverse)
px-4 h-10
">
Save changes
</button>

render

Tier 1 · primitive

Raw OKLCH ramps. --color-violet-500 doesn't know what it'll be used for — it just is.

Tier 2 · semantic

Role aliases. --color-interactive-primary maps to violet-500 in light, violet-400 in dark, and re-resolves under any future theme.

Tier 3 · component

Surface-specific. --color-button-primary-bg points at the semantic role — never at the primitive directly.

03 · Build flow

One command, four artifacts.

Turbo orchestrates the graph. Tokens build first, UI rebuilds against the new CSS, and Storybook + Showcase pick up the changes through their workspace symlinks. No package republishes — everything resolves through pnpm.

~/wyliedog · mainzsh
$ pnpm build
turbo run build --filter=...^...
@wyliedog/tokens#build style-dictionary
→ dist/tokens.css 1184 vars
→ dist/tokens.js
→ dist/tokens.d.ts
✓ done in 2.4s
@wyliedog/ui#build vite build
→ dist/index.js 194 KB
→ dist/index.d.ts
✓ done in 8.6s
@wyliedog/storybook#build
✓ cached (no input change)
@wyliedog/showcase#build
✓ cached (no input change)
Tasks: 4 successful, 4 total
Time: 11.0s >>> FULL TURBO
$

Build dependency graph

  1. 1

    Tokens build

    2.4s · style-dictionary

    Reads JSON source files, resolves aliases, emits tokens.css with all 1184 custom properties plus JS and TypeScript types.

  2. 2

    UI build

    8.6s · vite build

    Picks up the fresh tokens.css via workspace symlink. Vite bundles all 42 components into a tree-shakeable ESM output.

  3. 3

    Storybook

    cached · no change

    Turborepo cache hit — no input files changed, no rebuild needed. Stories stay live against the new token values.

  4. 4

    Showcase

    cached · no change

    Next.js build also cached. The public site reflects the new token values without a full rebuild.

04 · Component layer

Every component, the same five layers.

No bespoke component shapes. Every export from @wyliedog/ui is a forwardRef wrapper around a Radix primitive, with CVA drives variants, Tailwind drives the prose, and an asChild escape hatch through Slot.

Button.tsx
import { forwardRef } from "react"
import { Slot } from "@radix-ui/react-slot"
import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "./lib/utils"
const buttonVariants = cva(
"inline-flex items-center justify-center rounded-md font-medium",
{
variants: {
variant: {
default: "bg-(--color-interactive-primary) text-(--color-text-inverse)",
outline: "border border-(--color-border-primary) bg-transparent",
},
}
}
)
export const Button = forwardRef<HTMLButtonElement, ButtonProps>(
({ className, variant, size, asChild, ...props }, ref) => {
const Comp = asChild ? Slot : "button"
return <Comp ref={ref} className={cn(buttonVariants({ variant, size }), className)} { ...props }/>
}
)

forwardRef-first wrapper types

Every component accepts a ref, enabling integration with form libraries and animation tools without wrapper divs.

CVA variants, compact type strings

Class variance authority keeps variant logic co-located with the component, generating precise TypeScript types automatically.

className merges, static class strings

cn() merges caller className last so any consumer can override without !important or specificity battles.

Base classes: component + token

Base styles use semantic token variables — bg-(--color-interactive-primary) — so every component automatically respects the active theme.

asChild via @radix-ui/react-slot

Pass asChild to render any element or component as the root, preserving all Radix accessibility behavior without an extra DOM node.

Everything wires together at install time.

Two packages. One install. Everything flows through the Figma-to-git path, and ends up in the same place.