apps/shade/src/docs/architecture.mdx
import { Meta } from '@storybook/addon-docs/blocks';
<Meta title="Architecture" /> <div className="sb-doc">If you are not sure where code belongs, use this:
tokens: visual values only (color, type, radius, motion)primitives: layout structure only (Stack, Inline, Box, Container, Grid, Text)components: reusable generic controls (button, input, dialog)patterns: repeated product workflows (filters, resource flows)app: app shell and transitional domain exportsutils: design-system-safe generic helpers onlyFast decision rule:
primitives.components.patterns./apps/shade/
├── src/
│ ├── components/
│ │ ├── ui/ # Base ShadCN components
│ │ ├── layout/ # Composed, reusable layout components
│ │ └── features/ # Feature-specific, higher-level components
│ ├── hooks/ # Custom React hooks
│ ├── providers/ # Context providers
│ ├── lib/ds-utils.ts # DS-safe utilities
│ ├── lib/app-utils.ts # Transitional domain utilities
│ └── docs/ # Documentation
├── .storybook/ # Storybook configuration
├── styles.css # Tailwind imports + runtime variables
└── tailwind.theme.css # CSS-first token definitions (@theme)
Shade now uses layered package entrypoints:
@tryghost/shade/tokens and @tryghost/shade/tokens.css@tryghost/shade/primitives@tryghost/shade/components@tryghost/shade/patterns@tryghost/shade/app@tryghost/shade/utilsThe root @tryghost/shade entrypoint remains a compatibility lane during migration, but new code should import from a layer-specific subpath.
The root entrypoint now keeps compatibility for DS layers only (tokens, primitives, components, patterns).
app and utils are intentionally subpath-only.
tokens: token definitions and helpers only (CSS variables, token names, token lookup helpers).primitives: structure/layout building blocks with no product/domain behavior.components: reusable UI controls and visual assets/icons.patterns: feature-level reusable compositions and interaction rules.app: app shell/provider/context APIs and transitional domain exports.utils: DS-safe helpers, generic hooks, and third-party namespaces.Run this order before implementing UI:
tokens -> primitives -> components -> patterns).Stop and re-evaluate when:
components and patterns is unclearWhen to add code to Shade:
primitives when the abstraction is structural.components when the abstraction is a reusable control.patterns when repeated product workflows emerge.utils; route them through app during migration and then toward app-local/pattern ownership.Primitive composition has these fixed rules:
Stack, Inline, Box, Container, Grid, Text.none | xs | sm | md | lg | xl | 2xl.Text primitive with compatibility heading wrappers (H1-H4, HTable).apps/shade/src/components/layout/* only.@tryghost/shade/primitives exports available during migration and deprecate without hard removals.This layer keeps shared control APIs explicit and stable in components/ui:
Button, Input, Field, Select, Tabs, Table, Dialog, DropdownMenu, Card).Located in /src/components/ui/:
Located in /src/components/layout/:
Located in /src/components/features/:
⚠️ Note: When using third-party libraries that might have naming conflicts (like Recharts), we alias all exports (e.g.,
export * as Recharts from "recharts").
PascalCasecamelCase⚠️ Note: ShadCN/UI CLI creates files in kebab-case. We accept this inconsistency due to case-sensitivity issues.
// Basic component structure
import { cn } from "@/lib/utils"
import { cva } from "class-variance-authority"
const componentVariants = cva(
"base-styles",
{
variants: {
variant: {
default: "variant-styles",
// Add variants...
}
},
defaultVariants: {
variant: "default"
}
}
)
export interface ComponentProps {
className?: string;
// Other props...
}
export function Component({ className, ...props }: ComponentProps) {
return (
<div className={cn(componentVariants(), className)} {...props} />
)
}
className propShade defaults to using Lucide icons. Since all exports are aliased as LucideIcon in index.ts, the way to use icons in apps happens through referencing the LucideIcon package:
import { LucideIcon } from 'lucide-react';
<LucideIcon.Search size={16} />
Key configuration in components.json:
{
"style": "new-york",
"baseColor": "gray",
"cssVariables": true
}
@theme tokensStart with ShadCN/UI
Add Documentation
Export Component
// index.ts
export * from './components/ui/component';