Sync third-party and MCP marketplace plugins

Constraint: Public skills are published only by explicit administrator action unless they are tracked third-party market sources.
Confidence: high
Scope-risk: narrow
Directive: Keep private/internal skills out of the public marketplace and preserve normal incremental market Git history.
Tested: Marketplace validation passed.
This commit is contained in:
KeyInfo Bot committed 2026-10-10 00:02:42 +08:00
1 parent 1545ca085e
commit 453b104fe1
14 files changed
+1001 -13

No files matched your search

+6 -6
View File
@@ -132,8 +132,8 @@
"repo": "https://github.com/shadcn-ui/ui.git", "repo": "https://github.com/shadcn-ui/ui.git",
"ref": "main", "ref": "main",
"adapter": "claude-skill", "adapter": "claude-skill",
"commit": "c003e96852fa9534aee40b2cb85a96d8bd38732d", "commit": "2d3f1cd436b18ea12f24130de4df781355925b08",
"syncedAt": "2026-10-09T10:26:29Z" "syncedAt": "2026-10-09T16:00:00Z"
}, },
{ {
"id": "frontend-slides", "id": "frontend-slides",
@@ -177,8 +177,8 @@
"repo": "https://github.com/mattpocock/skills.git", "repo": "https://github.com/mattpocock/skills.git",
"ref": "main", "ref": "main",
"adapter": "skill-collection", "adapter": "skill-collection",
"commit": "b0618bc436ad893b3c5e84e55fba86586d34a404", "commit": "49dd158d1076134a641b33efb035946536778336",
"syncedAt": "2026-10-08T15:59:59Z" "syncedAt": "2026-10-09T16:00:00Z"
}, },
{ {
"id": "updating-gitea-repositories", "id": "updating-gitea-repositories",
@@ -258,8 +258,8 @@
"repo": "https://github.com/vercel/next.js.git", "repo": "https://github.com/vercel/next.js.git",
"ref": "canary", "ref": "canary",
"adapter": "skill-collection", "adapter": "skill-collection",
"commit": "76bed42c1b10c623dcfc63d2f6a80940bf79e157", "commit": "2d0da21235fdf3554244bf4c5c19369fbcd6b37b",
"syncedAt": "2026-10-09T10:26:29Z" "syncedAt": "2026-10-09T16:00:00Z"
}, },
{ {
"id": "shuorenhua", "id": "shuorenhua",
@@ -2,8 +2,8 @@
"sourceId": "grill-me", "sourceId": "grill-me",
"repo": "https://github.com/mattpocock/skills.git", "repo": "https://github.com/mattpocock/skills.git",
"ref": "main", "ref": "main",
"commit": "b0618bc436ad893b3c5e84e55fba86586d34a404", "commit": "49dd158d1076134a641b33efb035946536778336",
"adapter": "skill-collection", "adapter": "skill-collection",
"sourcePath": "skills/productivity", "sourcePath": "skills/productivity",
"syncedAt": "2026-10-08T15:59:59Z" "syncedAt": "2026-10-09T16:00:00Z"
} }
@@ -3,5 +3,5 @@
"name": "playwright浏览器自动化操作", "name": "playwright浏览器自动化操作",
"version": "20260605", "version": "20260605",
"keySource": "none", "keySource": "none",
"syncedAt": "2026-10-09T10:29:12Z" "syncedAt": "2026-10-09T16:02:42Z"
} }
@@ -2,8 +2,8 @@
"sourceId": "next-skills", "sourceId": "next-skills",
"repo": "https://github.com/vercel/next.js.git", "repo": "https://github.com/vercel/next.js.git",
"ref": "canary", "ref": "canary",
"commit": "76bed42c1b10c623dcfc63d2f6a80940bf79e157", "commit": "2d0da21235fdf3554244bf4c5c19369fbcd6b37b",
"adapter": "skill-collection", "adapter": "skill-collection",
"sourcePath": "skills", "sourcePath": "skills",
"syncedAt": "2026-10-09T10:26:29Z" "syncedAt": "2026-10-09T16:00:00Z"
} }
@@ -2,8 +2,8 @@
"sourceId": "shadcn", "sourceId": "shadcn",
"repo": "https://github.com/shadcn-ui/ui.git", "repo": "https://github.com/shadcn-ui/ui.git",
"ref": "main", "ref": "main",
"commit": "c003e96852fa9534aee40b2cb85a96d8bd38732d", "commit": "2d3f1cd436b18ea12f24130de4df781355925b08",
"adapter": "claude-skill", "adapter": "claude-skill",
"sourcePath": "skills/shadcn", "sourcePath": "skills/shadcn",
"syncedAt": "2026-10-09T10:26:29Z" "syncedAt": "2026-10-09T16:00:00Z"
} }
@@ -193,6 +193,17 @@ npx shadcn@latest docs button dialog select
- **Skip**: `npx shadcn@latest init --preset <code> --force --no-reinstall`. Only updates config and CSS, leaves components as-is. - **Skip**: `npx shadcn@latest init --preset <code> --force --no-reinstall`. Only updates config and CSS, leaves components as-is.
- **Important**: Always run preset commands inside the user's project directory. `apply` only works in an existing project with a `components.json` file. The CLI automatically preserves the current base (`base` vs `radix`) from `components.json`. If you must use a scratch/temp directory (e.g. for `--dry-run` comparisons), pass `--base <current-base>` explicitly — preset codes do not encode the base. - **Important**: Always run preset commands inside the user's project directory. `apply` only works in an existing project with a `components.json` file. The CLI automatically preserves the current base (`base` vs `radix`) from `components.json`. If you must use a scratch/temp directory (e.g. for `--dry-run` comparisons), pass `--base <current-base>` explicitly — preset codes do not encode the base.
## Design Systems from DESIGN.md
When the user asks for a design system from a `DESIGN.md` (or a brand spec), with or without a preset, follow [design-system.md](./design-system.md) end to end:
1. `init --preset <code> --template <vite|next> --name <app> --no-monorepo -y`. Without a preset code, pick the closest style and pass `--base base --preset <style>`. Then `add --all -y`.
2. Read the DESIGN.md frontmatter, Do's and Don'ts, and Known Gaps before editing.
3. Map its tokens onto the shadcn variables in `tailwindCssFile`. Pin the radius scale, shadows, fonts and `type-*` utilities.
4. Restyle components by editing their `cva` variants. Change control heights together.
5. Build the design system page per [design-system-page.md](./design-system-page.md): contrast-checked color roles, type specimens, pinned state matrices, edge cases, do/don't pairs, recipes. Check coverage with `scripts/showcase-coverage.mjs`.
6. Verify in a browser: typecheck, no console errors, loads at the top, no horizontal overflow, dark mode.
## Updating Components ## Updating Components
When the user asks to update a component from upstream while keeping their local changes, use `--dry-run` and `--diff` to intelligently merge. **NEVER fetch raw files from GitHub manually — always use the CLI.** When the user asks to update a component from upstream while keeping their local changes, use `--dry-run` and `--diff` to intelligently merge. **NEVER fetch raw files from GitHub manually — always use the CLI.**
@@ -212,6 +223,11 @@ When the user asks to update a component from upstream while keeping their local
npx shadcn@latest init --name my-app --preset base-nova npx shadcn@latest init --name my-app --preset base-nova
npx shadcn@latest init --name my-app --preset a2r6bw --template vite npx shadcn@latest init --name my-app --preset a2r6bw --template vite
# Create a design system app (then follow design-system.md).
npx shadcn@latest init --name my-ds --preset b0 --template vite --no-monorepo -y
npx shadcn@latest init --name my-ds --base base --preset maia --template vite --no-monorepo -y
npx shadcn@latest add --all -y
# Create a monorepo project. # Create a monorepo project.
npx shadcn@latest init --name my-app --preset base-nova --monorepo npx shadcn@latest init --name my-app --preset base-nova --monorepo
npx shadcn@latest init --name my-app --preset base-nova --template next --monorepo npx shadcn@latest init --name my-app --preset base-nova --template next --monorepo
@@ -275,3 +291,5 @@ npx shadcn@latest view owner/repo/item
- [cli.md](./cli.md) — Commands, flags, presets, templates - [cli.md](./cli.md) — Commands, flags, presets, templates
- [registry.md](./registry.md) — Authoring source registries, `include`, item definitions, dependencies, GitHub registry rules - [registry.md](./registry.md) — Authoring source registries, `include`, item definitions, dependencies, GitHub registry rules
- [customization.md](./customization.md) — Theming, CSS variables, extending components - [customization.md](./customization.md) — Theming, CSS variables, extending components
- [design-system.md](./design-system.md): DESIGN.md (with or without a preset) to a themed app: style picking, token mapping, radius, fonts, type scale, control heights, showcase traps, verification
- [design-system-page.md](./design-system-page.md): What goes on a design system page and how: foundations, component tiers, pinned state matrices, contrast pairs, edge cases, do/don't, recipes
@@ -0,0 +1,126 @@
import * as React from "react"
import { cn } from "@/lib/utils"
// Resolves any CSS color (hex, rgb, oklch, color-mix) to sRGB by painting one pixel.
// Translucent colors are composited over the backdrop, as the browser would.
function toRgb(color: string, backdrop = "#fff") {
const canvas = document.createElement("canvas")
canvas.width = canvas.height = 1
const context = canvas.getContext("2d", { willReadFrequently: true })
if (!context) {
return null
}
context.fillStyle = backdrop
context.fillRect(0, 0, 1, 1)
context.fillStyle = color
context.fillRect(0, 0, 1, 1)
const [r, g, b] = context.getImageData(0, 0, 1, 1).data
return [r, g, b]
}
function toHex(rgb: number[]) {
return `#${rgb.map((value) => value.toString(16).padStart(2, "0")).join("")}`
}
function luminance([r, g, b]: number[]) {
const [R, G, B] = [r, g, b].map((value) => {
const channel = value / 255
return channel <= 0.03928
? channel / 12.92
: ((channel + 0.055) / 1.055) ** 2.4
})
return 0.2126 * R + 0.7152 * G + 0.0722 * B
}
function contrast(a: number[], b: number[]) {
const [light, dark] = [luminance(a), luminance(b)].sort((x, y) => y - x)
return (light + 0.05) / (dark + 0.05)
}
export function ColorPair({
name,
background,
foreground,
className,
}: {
name: string
background: string
foreground: string
className?: string
}) {
const ref = React.useRef<HTMLDivElement>(null)
const [info, setInfo] = React.useState<{ ratio: number; bg: string } | null>(
null
)
React.useEffect(() => {
const element = ref.current
if (!element) {
return
}
// Re-measure when the theme class on <html> changes.
const measure = () => {
const style = getComputedStyle(element)
const page = getComputedStyle(document.body).backgroundColor
const bg = toRgb(style.backgroundColor, page)
if (!bg) {
return
}
const fg = toRgb(style.color, toHex(bg))
if (fg) {
setInfo({ ratio: contrast(bg, fg), bg: toHex(bg) })
}
}
measure()
const observer = new MutationObserver(measure)
observer.observe(document.documentElement, { attributes: true })
return () => observer.disconnect()
}, [])
const level = !info
? ""
: info.ratio >= 7
? "AAA"
: info.ratio >= 4.5
? "AA"
: info.ratio >= 3
? "AA Large"
: "Fail"
return (
<div
className={cn(
"flex flex-col overflow-hidden rounded-xl border",
className
)}
>
<div
ref={ref}
style={{
backgroundColor: `var(--${background})`,
color: `var(--${foreground})`,
}}
className="flex h-24 items-end justify-between p-4"
>
<span className="text-2xl font-medium">Aa</span>
{info && (
<span className="rounded-full border border-current/20 px-2 py-0.5 font-mono text-xs">
{info.ratio.toFixed(2)} {level}
</span>
)}
</div>
<div className="flex flex-col gap-0.5 bg-background p-3">
<span className="text-sm font-medium">{name}</span>
<span className="font-mono text-xs text-muted-foreground">
--{background} / --{foreground}
</span>
{info && (
<span className="font-mono text-xs text-muted-foreground">
{info.bg}
</span>
)}
</div>
</div>
)
}
@@ -0,0 +1,14 @@
/* Add to the app's global CSS after the dark variant. */
/* Lets the design system page pin interaction states with data-preview="hover focus active". */
@custom-variant hover {
@media (hover: hover) {
&:hover {
@slot;
}
}
&[data-preview~="hover"] {
@slot;
}
}
@custom-variant focus-visible (&:focus-visible, &[data-preview~="focus"]);
@custom-variant active (&:active, &[data-preview~="active"]);
@@ -0,0 +1,72 @@
import * as React from "react"
import { cn } from "@/lib/utils"
const defaultStates = [
"default",
"hover",
"focus",
"active",
"disabled",
] as const
export function StateMatrix<Row extends string>({
rows,
states = defaultStates,
render,
className,
}: {
rows: readonly Row[]
states?: readonly string[]
render: (row: Row, state: string) => React.ReactNode
className?: string
}) {
return (
<div className={cn("w-full overflow-x-auto", className)}>
<table className="w-full border-separate border-spacing-x-2 border-spacing-y-3">
<thead>
<tr>
<th />
{states.map((state) => (
<th
key={state}
scope="col"
className="text-left font-mono text-xs font-normal text-muted-foreground"
>
{state}
</th>
))}
</tr>
</thead>
<tbody>
{rows.map((row) => (
<tr key={row}>
<th
scope="row"
className="pr-4 text-left font-mono text-xs font-normal text-muted-foreground"
>
{row}
</th>
{states.map((state) => (
<td key={state}>{render(row, state)}</td>
))}
</tr>
))}
</tbody>
</table>
</div>
)
}
// Spread onto a component to pin a state without interaction.
export function previewState(state: string) {
if (state === "disabled") {
return { disabled: true }
}
if (["hover", "focus", "active"].includes(state)) {
return { "data-preview": state }
}
return {}
}
@@ -136,6 +136,8 @@ module.exports = {
`--radius` controls border radius globally. Components derive values from it (`rounded-lg` = `var(--radius)`, `rounded-md` = `calc(var(--radius) - 2px)`). `--radius` controls border radius globally. Components derive values from it (`rounded-lg` = `var(--radius)`, `rounded-md` = `calc(var(--radius) - 2px)`).
When a brand spec defines its own radius scale, pin each step in `@theme inline` (`--radius-sm` … `--radius-4xl`) instead of deriving from `--radius`. See [design-system.md](./design-system.md#radius).
--- ---
## Customizing Components ## Customizing Components
@@ -0,0 +1,268 @@
# The Design System Page
What goes on a design system page, and how to show it. Distilled from Primer, Carbon, Atlassian, GitLab Pajamas, Cloudscape, Twilio Paste, Salesforce Lightning, Material 3, Evergreen and Orbit, and from EightShapes' component documentation guidance.
## Contents
- Principles
- Page outline
- Foundations
- Components: tiers and block anatomy
- States without interaction
- Edge cases
- Do and Don't
- Recipes and example screens
- Helpers
- Checklist
---
## Principles
1. **Foundations, then components, then compositions.** Every mature system follows this order (Primer, Carbon, Paste, Cloudscape, M3).
2. **Roles, not palettes.** Show each color as a surface paired with its foreground and the measured contrast. The raw palette comes second.
3. **Lead with the typical case.** Each component opens with its most common use in real content. Then come variants in priority order, then shared axes (size, icon), then states.
4. **Pin states so reviewers can see them.** Hover, focus and pressed render side by side without interaction. Use one consistent label, and never put the state name inside the component.
5. **Don't render every combination.** Primitives get a variant × state matrix. Composed components get a typical example plus additions. Exhaustive combinations belong in tests (Material 3 cut its list kit from 700+ variants to about 45).
6. **Real content in the brand's voice.** No lorem ipsum. Include the awkward cases: long text, empty, error, loading.
7. **Composition proves the system.** End with the DESIGN.md's own recipes and one realistic screen built only from the tokens and components.
8. **Make the DESIGN.md's rules visible.** Turn its Do's and Don'ts into rendered do/don't pairs.
---
## Page Outline
| # | Section | Contents |
| --- | -------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ |
| 1 | Header | System name, "Themed from DESIGN.md", section anchors, light/dark toggle, primary CTA. |
| 2 | Overview | The DESIGN.md hero recipe: display type, one-line personality, its signature artifact. |
| 3 | Foundations | Color roles, palette, typography, spacing, radius, elevation, motion (if specified), icons. |
| 4 | Actions | Button, ButtonGroup, Toggle, ToggleGroup, Badge, Kbd. |
| 5 | Inputs | Field, Input, InputGroup, Textarea, Select, NativeSelect, Combobox, Checkbox, RadioGroup, Switch, Slider, InputOTP, Calendar, Label. |
| 6 | Navigation | Tabs, Breadcrumb, Pagination, NavigationMenu, Menubar, Sidebar. |
| 7 | Data display | Card, Table, Chart, Item, Avatar, Accordion, Collapsible, Carousel, ScrollArea, Resizable, AspectRatio, Separator. |
| 8 | Feedback | Alert, toast (and Sonner in Base UI projects), Progress, Spinner, Skeleton, Empty. |
| 9 | Overlays | Dialog, AlertDialog, Sheet, Drawer, Popover, HoverCard, Tooltip, DropdownMenu, ContextMenu, Command. |
| 10 | Conversation | MessageScroller, Message, Bubble, Attachment, Marker, Questionnaire. See [rules/chat.md](./rules/chat.md). |
| 11 | Do and Don't | Rendered pairs for the DESIGN.md's rules. |
| 12 | Recipes and example screen | The DESIGN.md's named components, then one realistic screen. |
| 13 | Footer | The DESIGN.md footer recipe. |
DirectionProvider goes wherever RTL matters (an RTL row in Inputs or Navigation).
Alternate section surfaces the way the DESIGN.md paces its bands. Use `className="dark"` on a section to render a dark band with the same components.
---
## Foundations
### Color roles
A grid of pairs, each rendered as the surface with "Aa" in its foreground color, plus the token names, resolved value and measured contrast (Paste, Material 3, Pajamas):
| Pair | Required |
| ------------------------------------- | ------------ |
| `background` / `foreground` | Yes |
| `card` / `card-foreground` | Yes |
| `popover` / `popover-foreground` | Yes |
| `primary` / `primary-foreground` | Yes |
| `secondary` / `secondary-foreground` | Yes |
| `muted` / `muted-foreground` | Yes |
| `accent` / `accent-foreground` | Yes |
| `destructive` on `background` | Yes |
| Each extra text token on `background` | When defined |
Use `ColorPair` from the helpers. It measures contrast live and re-measures when the theme changes. Report any pair below 4.5:1 for body-size text, even when the DESIGN.md specifies it. A brand primary at 3:1 behind 14px labels is a real finding.
### Palette
Swatches grouped the way the DESIGN.md groups them (brand, surfaces, text, status, accents). Label each with its DESIGN.md name and token, for example "Warm Stone · `surface-warm`". Show `border`, `input`, `ring`, `chart-1…5` and `sidebar-*` here too.
### Typography
A specimen table, one row per type token (Cloudscape, Carbon, Atlassian):
| Column | Content |
| -------- | ------------------------------------------------- |
| Token | `type-display-lg` |
| Spec | size / line height / weight / tracking |
| Use | "Section heads", taken from the DESIGN.md |
| Specimen | Real copy set in the token, truncated to one line |
Group the rows display → title → body → label/caption → code. Below the table, show one paragraph of running text at body size, with a link and inline code, so line length and rhythm are visible.
### Spacing
The base unit and the scale as labeled horizontal bars (Carbon, Atlassian), then a semantic ladder: inside a component, between related items, between groups, between sections (Pajamas). Skip it if the DESIGN.md defines no spacing.
### Radius
The same box at every step, labeled with the token, the value and what uses it (Material 3). Example: `md · 8px · buttons, inputs`.
### Elevation
Each level as a raised surface with the components that rest there (Pajamas, Material 3): flat for canvas and bands, hairline for cards, soft for popovers and menus, deep for dialogs.
### Motion and icons
Show motion only if the DESIGN.md defines durations or easings: a table of token, milliseconds and use. For icons, show one row with the icon library at each size used by components, in `foreground` and `muted-foreground`.
---
## Components: Tiers and Block Anatomy
### Tiers
| Tier | Components | How to show |
| ------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------- |
| Primitives | Button, Badge, Toggle, ToggleGroup, Input, Textarea, Select, NativeSelect, Checkbox, RadioGroup, Switch, Slider, Tabs | Variant × state matrix, size ladder, icon row. |
| Fields | Field, InputGroup, Combobox, InputOTP, Calendar, Label | Typical field, then description, error, disabled, required. |
| Composed | Card, Table, Item, Avatar, Alert, Empty, toast, Sonner, Accordion, Collapsible, Breadcrumb, Pagination, Progress, Skeleton, Spinner, Carousel, Chart, ScrollArea, Resizable, AspectRatio, Separator, Kbd, ButtonGroup | Typical example, then one element added at a time, then edge cases. |
| Overlays | Dialog, AlertDialog, Sheet, Drawer, Popover, HoverCard, Tooltip, DropdownMenu, ContextMenu, Menubar, NavigationMenu, Command | A real trigger per overlay, labeled with what it opens. |
| Shell | Sidebar, DirectionProvider | Inline in a framed preview (`collapsible="none"`). |
| Conversation | MessageScroller, Message, Bubble, Attachment, Marker, Questionnaire | One realistic thread, then variant rows. |
### Block anatomy
Each component block, in this order. Skip the parts that don't apply:
1. **Name and one line on when to use it.** Take the wording from the DESIGN.md recipe when there is one.
2. **Typical example** in real content.
3. **Variants** in priority order (primary before secondary before ghost), each with a one-line "when".
4. **Sizes** as a ladder, smallest to largest.
5. **With icon**: leading, trailing, icon-only.
6. **States**: the matrix for primitives, single examples for the rest.
7. **Edge cases** (see below).
Wide components (Table, NavigationMenu, Menubar, Sidebar, Chart, MessageScroller) span the full row.
---
## States Without Interaction
Pin interaction states with a `data-preview` attribute. Override the hover, focus-visible and active variants so each also matches the attribute. Copy [assets/showcase/preview-states.css](./assets/showcase/preview-states.css) into `tailwindCssFile`:
```css
@custom-variant hover {
@media (hover: hover) {
&:hover {
@slot;
}
}
&[data-preview~="hover"] {
@slot;
}
}
@custom-variant focus-visible (&:focus-visible, &[data-preview~="focus"]);
@custom-variant active (&:active, &[data-preview~="active"]);
```
If the theme adds an unlayered focus rule (for an outline-style focus), add `[data-preview~="focus"]` to its selector too.
Render the matrix with `StateMatrix` and `previewState` from the helpers:
```tsx
const variants = ["default", "secondary", "outline", "ghost", "destructive", "link"] as const
<StateMatrix
rows={variants}
render={(variant, state) => (
<Button variant={variant} {...previewState(state)}>
Save draft
</Button>
)}
/>
```
The columns are default, hover, focus, active and disabled. Add `loading` with Spinner + `disabled` for Button, and `invalid` (`aria-invalid`) for fields:
```tsx
<StateMatrix
rows={["input"] as const}
states={["default", "hover", "focus", "disabled", "invalid"]}
render={(_, state) => (
<Input
placeholder="you@company.com"
aria-invalid={state === "invalid" || undefined}
{...previewState(state)}
/>
)}
/>
```
If the DESIGN.md says a state doesn't change ("primary darkens on press only"), leave that column identical. The matrix shows the spec is followed.
---
## Edge Cases
Show these next to the component they stress:
| Case | Where |
| ------------- | ------------------------------------------------------------------------- |
| Long text | Button label, Badge, Item title, Table cell, Breadcrumb (truncation). |
| Empty | Table with no rows → Empty, Combobox with no matches, empty Command. |
| Error | Field with FieldError, Alert destructive, Attachment `state="error"`. |
| Loading | Button with Spinner, Skeleton for Card and Item, Progress, toast loading. |
| Many items | AvatarGroup with count, Pagination with ellipsis, ScrollArea list. |
| Missing image | Avatar falling back to AvatarFallback. |
| Disabled | Every field and its Field `data-disabled`. |
| RTL | One row inside `DirectionProvider` with `dir="rtl"`. |
---
## Do and Don't
Pick the two to four DESIGN.md rules that are easiest to break and render each as a pair (Primer, Orbit, Carbon):
```tsx
<div className="grid gap-4 md:grid-cols-2">
<Example title="Do">
<Button>Get started</Button>
<Button variant="outline">Contact sales</Button>
</Example>
<Example title="Don't">
<Button>Get started</Button>
<Button>Contact sales</Button>
</Example>
</div>
```
Caption each with the rule in one line, for example "One primary action per group." Mark Don't with `destructive` text, not a new color.
---
## Recipes and Example Screens
**Recipes.** Build each named component in the DESIGN.md (`hero-band`, `feature-card`, `pricing-tier-card`, `callout-card-coral`, `footer`) from shadcn components and tokens. These are the spec's acceptance tests. If a recipe can't be built without raw values, the token mapping is incomplete.
**Example screen.** One realistic screen in the brand's domain (Cloudscape demos, Paste page templates): a settings page (FieldGroup, Switch, Select, Button) or a list view (Table, Badge, Pagination, Empty). Use only tokens and components.
---
## Helpers
Copy from `assets/showcase/` into the showcase folder:
| File | Purpose |
| -------------------- | -------------------------------------------------------------------------- |
| `preview-states.css` | Variant overrides that pin hover, focus and active through `data-preview`. |
| `state-matrix.tsx` | `StateMatrix` (labeled variant × state table) and `previewState(state)`. |
| `color-pair.tsx` | `ColorPair`: surface + foreground with live contrast ratio and WCAG level. |
They import `cn` from `@/lib/utils`. Rewrite that path to the project's `utils` alias.
---
## Checklist
- [ ] Every installed component renders (run the coverage script).
- [ ] Color roles shown as pairs with contrast; any pair under 4.5:1 is noted in the report.
- [ ] Type specimen covers every DESIGN.md type token, in real copy.
- [ ] Radius and elevation ladders name the components that use each step.
- [ ] Primitives have a state matrix; Button and fields include loading and invalid.
- [ ] Edge cases: long text, empty, error, loading, missing image.
- [ ] Do/Don't pairs for the DESIGN.md's key rules.
- [ ] DESIGN.md recipes rebuilt, plus one example screen.
- [ ] Page-level theme toggle works, and at least one scoped dark band if the DESIGN.md has dark surfaces.
- [ ] No lorem ipsum, and no state names inside components.
@@ -0,0 +1,386 @@
# Design Systems from DESIGN.md
Build a new app from a preset, install every component, restyle it from a `DESIGN.md`, then render a design system page.
Trigger phrases: "build me a design system using <preset>", "apply this DESIGN.md", "create a showcase / design system page", "make shadcn look like <brand>".
## Contents
- Inputs to confirm
- Step 1: Scaffold
- Step 2: Install everything
- Step 3: Read the DESIGN.md
- Step 4: Map tokens to the theme
- Step 5: Map component recipes to variants
- Step 6: Build the design system page ([design-system-page.md](./design-system-page.md))
- Step 7: Verify
- Report back
---
## Inputs to Confirm
| Input | Default | Notes |
| --------- | ----------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
| Preset | Optional | Code (`b0`), style name (`luma`) or URL. Pass it through. Never decode it by hand. Without one, pick a style (see Step 1). |
| DESIGN.md | Required | A path, URL or pasted content. Copy it into the new app root as `DESIGN.md`. |
| Template | `vite` | Use `next` when the user says Next.js, App Router or SSR. |
| App name | Derived from the brand in DESIGN.md | Kebab-case. |
| Base | `base` | Pass `--base` when no preset code is given. Use `radix` only when the user asks. `info --json` after init decides `render` vs `asChild`. |
If the DESIGN.md path can't be read (macOS privacy blocks `~/Downloads` and `~/Desktop` for some hosts), ask the user to copy it into the working directory. Don't guess its contents.
---
## Step 1: Scaffold
With a preset code from the user:
```bash
npx shadcn@latest init --preset <code> --template vite --name <app> --no-monorepo -y
```
Without one, pick the style whose geometry is closest to the DESIGN.md and pass it with `--base`:
```bash
npx shadcn@latest init --base base --preset <style> --template vite --name <app> --no-monorepo -y
```
Then:
```bash
cd <app>
npx shadcn@latest info --json
```
`init` with neither `--preset` nor `--base` stops at an interactive library prompt, even with `-y`. Always pass one of them, plus `--no-monorepo`.
### Picking a style
The theme gets replaced by the DESIGN.md anyway. The style decides geometry the CSS variables can't reach: control padding, density, how rounded and how raised the components are. Read the DESIGN.md's radius scale, control heights, shadows and layout density, then pick:
| Style | Character | Pick when the DESIGN.md has |
| ------ | ----------------------------------------------------- | ---------------------------------------------------------------------- |
| `vega` | The classic shadcn/ui look. | Neutral, conventional SaaS geometry; nothing extreme. |
| `nova` | Reduced padding and margins for compact layouts. | 32–36px controls, 6–8px radius, hairlines over shadows. |
| `maia` | Soft and rounded, with generous spacing. | 40px+ controls, 12px+ card radius, generous padding, editorial pacing. |
| `lyra` | Boxy and sharp. | 0–4px radius, hard edges, mono or technical type. |
| `mira` | Compact. | Dense data UIs, 28–32px controls, tight tables. |
| `luma` | Rounded geometry, soft elevation, breathable layouts. | Pill buttons, soft layered shadows, glassy or macOS-like surfaces. |
| `rhea` | Luma, but more compact. | Luma's softness with product-UI density. |
| `sera` | Editorial and typographic. | Serif display type, editorial pacing, magazine-like hierarchy. |
State the choice and the reason in one line ("Picked maia: 40px controls, 12–16px cards, generous padding"). If the user names a style, use it. Read `base`, `style`, `iconLibrary`, `tailwindCssFile` and `aliases` from `info` before writing any code.
## Step 2: Install Everything
```bash
npx shadcn@latest add --all -y
ls <resolvedPaths.ui>/*.tsx | wc -l
```
Count the component files on disk. Never quote a component count from memory.
Then wire up the providers in the app root:
- Wrap the app in `TooltipProvider`.
- Base UI: wrap the app in `Toaster` from the `toast` component and call `toast.add({ title, description, type })`. Radix: render `<Toaster />` from `sonner`.
Then typecheck the app sources. In Vite apps, the root `tsc --noEmit` checks nothing because the root tsconfig only has project references:
```bash
npx tsc -p tsconfig.app.json --noEmit # Vite.
npx tsc --noEmit # Next.js.
```
Fix generated files that fail strict checks (for example, an unused `React` import) before moving on, or `vite build` will fail.
---
## Step 3: Read the DESIGN.md
DESIGN.md files usually carry YAML frontmatter (`colors`, `typography`, `rounded`, `spacing`, `components`) and prose sections (Overview, Colors, Typography, Layout, Elevation, Shapes, Components, Do's and Don'ts, Responsive, Known Gaps). Some only have prose with `{colors.x}` references. Handle both.
Long files exceed one read. Grep the headings first, then read frontmatter, **Do's and Don'ts**, **Iteration Guide** and **Known Gaps** in full. Those sections hold the rules that override defaults.
Extract into a working table before editing anything:
| Extract | Look for |
| --------------------- | --------------------------------------------------------------------- |
| Canvas and ink | `canvas`, `surface`, `ink`, "page floor", "body text" |
| Action color + states | `primary`, `-hover`, `-active`, `-pressed`, `-disabled`, `on-primary` |
| Secondary actions | `button-secondary` fill, text and border |
| Surfaces | card fills, soft bands, dark product surfaces |
| Lines | `hairline`, border alpha, "1px solid" |
| Focus | outline vs ring, color, width, offset, alpha |
| Type families | display, body, mono, and the documented **substitutes** |
| Type scale | size, weight, line height, tracking per token |
| Radius scale | every value and what it applies to |
| Elevation | each shadow tier, verbatim |
| Control heights | button, input, ghost, icon-button heights and padding |
| Hard rules | every "Don't" and "never" |
---
## Step 4: Map Tokens to the Theme
Edit only `tailwindCssFile`. Keep hex values when the DESIGN.md gives hex. Don't convert them to OKLCH.
### Role mapping
| DESIGN.md role | shadcn variable |
| -------------------------------------------- | ----------------------------------------------------------- |
| canvas / surface / page floor | `--background` |
| ink / headline text | `--foreground`, `--card-foreground`, `--popover-foreground` |
| primary CTA fill / on-primary | `--primary` / `--primary-foreground` |
| primary hover, pressed or active fill | new `--primary-active` (or `--primary-pressed`) |
| secondary button fill / text | `--secondary` / `--secondary-foreground` |
| soft band, alternating section | `--muted` |
| secondary or descriptive text | `--muted-foreground` |
| ghost hover wash, active tab, menu highlight | `--accent` / `--accent-foreground` |
| card / feature card fill | `--card` |
| floating layer (menus, dialogs) | `--popover` (usually the canvas) |
| hairline / structural border | `--border`, `--input` |
| focus color | `--ring` |
| error / validation | `--destructive` |
| illustration accents | `--chart-1` … `--chart-5`, plus named tokens |
| sidebar or nav rail surface | `--sidebar-*` |
Every token without a shadcn slot gets a variable in `:root` (and `.dark` when it changes) and a registration in `@theme inline`:
```css
:root {
--primary-active: #a9583e;
--surface-card: #efe9de;
}
@theme inline {
--color-primary-active: var(--primary-active);
--color-surface-card: var(--surface-card);
}
```
### Radius
A DESIGN.md radius scale rarely fits the `--radius` multipliers. Pin each step in `@theme inline` instead. These are the classes base-nova components use:
| Class | Used by |
| ------------- | ----------------------------------------------- |
| `rounded-sm` | Small accents |
| `rounded-md` | Menu, select and command items |
| `rounded-lg` | Buttons, inputs, selects, tabs, popovers, menus |
| `rounded-xl` | Cards, dialogs, command |
| `rounded-2xl` | Toasts, large containers |
| `rounded-4xl` | Badges (pill) |
```css
@theme inline {
/* DESIGN.md step → Tailwind class used by that component. */
--radius-sm: 4px; /* xs: accents. */
--radius-md: 6px; /* sm: menu items. */
--radius-lg: 8px; /* md: buttons, inputs. */
--radius-xl: 12px; /* lg: cards, dialogs. */
--radius-2xl: 16px; /* xl: toasts, hero containers. */
--radius-3xl: 16px;
--radius-4xl: 9999px; /* pill: badges. */
}
```
Replace the existing `--radius-*` lines in `@theme inline` rather than adding a second block. Confirm the classes for the installed style:
```bash
grep -ohE "rounded-(\[[^ \"]+\]|[a-z0-9]+)" <ui>/*.tsx | sort | uniq -c
```
`rounded-[min(var(--radius-md),Npx)]` caps small buttons and select triggers. Edit the cap when the DESIGN.md wants rounder small controls.
### Elevation
Override Tailwind's shadow scale in `@theme`, not `@theme inline`. Copy multi-layer shadows verbatim. Set `--shadow-xs` to `0 0 #0000` when the system is hairline-only, because inputs and outline buttons use it.
```css
@theme {
--shadow-xs: 0 0 #0000;
--shadow-sm: 0 1px 3px rgb(20 20 19 / 0.08);
--shadow-lg: 0 1px 3px rgb(20 20 19 / 0.08), 0 8px 24px rgb(20 20 19 / 0.06);
}
```
### Fonts
Brand fonts are usually licensed. Use the substitutes the DESIGN.md names, installed from Fontsource with the project's package manager, and import every face you reference at the top of the CSS file:
```bash
npm install @fontsource-variable/inter @fontsource-variable/cormorant-garamond @fontsource-variable/jetbrains-mono
```
```css
@import "@fontsource-variable/inter";
@import "@fontsource-variable/cormorant-garamond";
@import "@fontsource-variable/jetbrains-mono";
@theme inline {
--font-sans: "Inter Variable", -apple-system, system-ui, sans-serif;
--font-serif: "Cormorant Garamond Variable", Garamond, serif;
--font-mono: "JetBrains Mono Variable", ui-monospace, monospace;
--font-heading: var(--font-sans);
}
```
`--font-heading` styles small component titles (`CardTitle`, `DialogTitle`, `SheetTitle`). Point it at the display face only when that face reads well at 16px. Otherwise keep it on the sans and use display tokens for page headings.
### Type scale
Turn every typography token into a `type-*` utility. Don't use `--text-*` theme keys for this, because `cn()` treats unknown `text-*` classes as colors and drops whichever comes first when one sits next to `text-muted-foreground`.
```css
@utility type-display-lg {
font-family: var(--font-serif);
font-size: clamp(34px, 3vw + 16px, 48px);
font-weight: 500;
line-height: 1.1;
letter-spacing: -0.021em;
}
@utility type-caption-upper {
font-size: 12px;
font-weight: 500;
letter-spacing: 1.5px;
text-transform: uppercase;
}
```
- Clamp display sizes so mobile works. Convert pixel tracking to `em` (`-1px / 48px = -0.021em`) so the ratio holds as the size shrinks.
- Respect the rules on tracking (none below a size) and weight (display never bold).
### Dark mode
- **DESIGN.md defines dark surfaces** (product mockups, footers): put them in `.dark`. Because shadcn's `@custom-variant dark (&:is(.dark *))` scopes to descendants, `className="dark"` on any section renders every component inside it with dark tokens. Use it for dark bands, featured pricing cards and footers.
- **DESIGN.md is light-only**: derive `.dark` from the brand's known dark surfaces and tell the user it's derived.
### Focus
Match the documented focus treatment:
- **Ring with alpha** (for example "3px coral at 15%"): replace `ring-ring/50` with the documented alpha on field components (`input`, `textarea`, `input-group`, `select`, `native-select`, `combobox`, `input-otp`).
- **Solid outline with offset**: add an unlayered rule so it beats `outline-none`, and cancel the ring shadow:
```css
:is(
button,
a[href],
[role="button"],
[role="tab"],
[role="checkbox"],
[role="radio"],
[role="switch"],
[role="slider"]
):focus-visible {
outline: 2px solid var(--ring);
outline-offset: 2px;
--tw-ring-shadow: 0 0 #0000;
}
```
This covers actions only. Field controls keep their ring. When the DESIGN.md applies the outline to fields too, add `input, textarea, select, [role="combobox"]` to the selector.
---
## Step 5: Map Component Recipes to Variants
Restyle by editing the `cva` variants in the installed component source. Keep existing variant names so usage stays standard. Add a variant only when the DESIGN.md defines a distinct recipe.
| DESIGN.md recipe | shadcn target |
| ------------------------------------ | -------------------------------------------- |
| `button-primary` | `Button` `default` |
| `button-secondary` (tinted fill) | `Button` `secondary` |
| `button-secondary` (canvas + border) | `Button` `outline` |
| `button-ghost`, nav links | `Button` `ghost` |
| text link | `Button` `link` |
| circular icon button | `Button` `icon*` sizes + `rounded-full` |
| `badge-pill`, `badge-<accent>` | `Badge` `secondary`, `default`, new variants |
| `card`, `feature-card` | `Card` (fill, ring, `--card-spacing`) |
| `text-input`, `-focused` | `Input`, `InputGroup`, `Select`, `Textarea` |
| `category-tab`, filter row | `Tabs` `default` list + trigger |
**Control heights move together.** When the button height changes, change the field controls to match, or forms misalign. In base-nova the 32px default lives in:
| File | Class |
| ------------------- | -------------------------------- |
| `button.tsx` | `h-8`, `size-8` (icon) |
| `input.tsx` | `h-8` |
| `input-group.tsx` | `h-8` |
| `select.tsx` | `data-[size=default]:h-8` |
| `native-select.tsx` | `h-8` |
| `combobox.tsx` | `min-h-8` (chips) |
| `toggle.tsx` | `h-8 min-w-8` |
| `input-otp.tsx` | `size-8` (slot) |
| `tabs.tsx` | `group-data-horizontal/tabs:h-8` |
| `menubar.tsx` | `h-8` |
| `command.tsx` | `h-8!` (input group) |
Re-grep before editing (`grep -n "\bh-8\b\|size-8\b" <ui>/*.tsx`); other styles use different values.
**States.** Follow the documented states exactly. If the DESIGN.md says "darken on press only", use `active:` instead of `hover:`. Keep hover on ghost buttons and menu items even when the spec omits it, and tell the user you did.
**Hard rules.** Turn each "Don't" into a check before you finish, for example: no radius outside the scale, no accent color on buttons, no bold display type.
---
## Step 6: Build the Design System Page
Follow [design-system-page.md](./design-system-page.md). It sets the section outline, how to present each foundation, the component tiers and block anatomy, pinned state matrices, edge cases, do/don't pairs, and recipes.
In short:
1. Foundations first: color roles as contrast-checked pairs, palette, type specimen table, spacing, radius and elevation ladders.
2. Components by tier: primitives get a variant × state matrix, size ladder and icon row. Composed components get a typical example, additions and edge cases. Overlays get real triggers.
3. Then rendered do/don't pairs for the DESIGN.md's rules, the DESIGN.md's named recipes rebuilt from components, and one example screen.
Copy the helpers from `assets/showcase/` (`preview-states.css`, `state-matrix.tsx`, `color-pair.tsx`). Put sections in `src/showcase/<section>.tsx` (Vite) or `app/design-system/` (Next.js). Write copy in the brand's voice.
### Known traps
| Component | Trap | Fix |
| ------------------- | --------------------------------------------------------------------------- | --------------------------------------------------------------------------- |
| `Command` inline | cmdk scrolls its first item into view on mount, which jumps the page to it. | Control it: `value={value} onValueChange={setValue}` with initial `"none"`. |
| `Sidebar` | Default sidebar is `fixed` and escapes the preview frame. | `<SidebarProvider className="min-h-0">` + `<Sidebar collapsible="none">`. |
| `MessageFooter` | Placed beside `MessageContent`, it becomes a squeezed flex column. | Put it inside `MessageContent`. |
| `Select` (base) | `SelectValue` shows the raw value. | Pass `items` to `Select`. |
| `Resizable` | react-resizable-panels v4 dropped `direction` and numeric sizes. | `orientation="horizontal"`, `defaultSize="40%"`. |
| `DirectionProvider` | Provider alone doesn't flip layout. | Also set `dir="rtl"` on the wrapper. |
| `NavigationMenu` | The content is clipped by the example frame. | Leave bottom padding in that frame. |
| Header nav links | `Button` renders a `<button>`. | Base: `nativeButton={false} render={<a href="#…" />}`. Radix: `asChild`. |
### Coverage check
```bash
node <skill-dir>/scripts/showcase-coverage.mjs src/components/ui src # Vite.
node <skill-dir>/scripts/showcase-coverage.mjs components/ui app # Next.js.
```
It lists every component file that no showcase file imports. Fix the list until it's empty. `add --all` installs both `toast` and `sonner` in Base UI projects; show `sonner` in Feedback next to `toast` so the list can reach zero.
---
## Step 7: Verify
Start the dev server and check it in a browser:
1. App typecheck passes (see Step 2).
2. No console errors.
3. Loads at `scrollY === 0`. Anything else means a component is stealing scroll on mount.
4. No horizontal overflow at 375px and 1280px: `document.documentElement.scrollWidth <= innerWidth`.
5. Screenshot the hero, Foundations, one form section and one dark band. Compare against the DESIGN.md: canvas color, CTA color, display font, radius, focus ring.
6. Toggle dark mode once.
---
## Report Back
- App path, dev URL, component count from disk.
- The style picked (when no preset was given) and why.
- What maps where: colors, fonts (and substitutes), radius, shadows, focus.
- Contrast pairs under 4.5:1, including ones the DESIGN.md itself specifies.
- Every component file you edited and why.
- Every deliberate deviation from the DESIGN.md (derived dark mode, kept hover, substituted fonts).
@@ -72,6 +72,55 @@
"Uses Message + Bubble + BubbleContent for the conversation rows instead of hand-rolled bubble divs", "Uses Message + Bubble + BubbleContent for the conversation rows instead of hand-rolled bubble divs",
"Uses semantic color tokens and gap-* spacing; includes \"use client\" (isRSC)" "Uses semantic color tokens and gap-* spacing; includes \"use client\" (isRSC)"
] ]
},
{
"id": 6,
"prompt": "Build me a design system using preset b0. Use the DESIGN.md in this folder (a productivity-app spec: white canvas, #0075DE primary, 8px buttons, 12px cards, multi-layer soft shadows, 2px blue focus outline with 2px offset).",
"expected_output": "A new Vite app created with init --preset b0 --template vite, every component installed, a design system page that renders all of them, and the DESIGN.md tokens mapped onto the shadcn theme.",
"files": [],
"expectations": [
"Creates the app with init --preset b0 --template vite --name <app> and passes the preset code through without decoding it",
"Installs every component with add --all and reports the component count from disk",
"Reads the DESIGN.md Do's and Don'ts and Known Gaps before editing the theme",
"Maps DESIGN.md colors onto shadcn variables in the existing tailwindCssFile and registers extra tokens in @theme inline instead of creating a new CSS file",
"Pins the radius scale in @theme inline and overrides the shadow scale with the documented multi-layer shadows",
"Implements the 2px outline focus with an offset instead of keeping the default ring",
"Changes field control heights together with the button height",
"Renders every installed component on the design system page and runs the coverage check",
"Verifies in a browser: typecheck, no console errors, page loads at the top, no horizontal overflow",
"Shows color roles as surface/foreground pairs with measured contrast ratios",
"Shows a variant × state matrix for Button with hover, focus, active and disabled pinned via data-preview",
"Renders do/don't pairs for rules from the DESIGN.md"
]
},
{
"id": 7,
"prompt": "Create a Next.js design system app called editorial from preset b0 and apply this DESIGN.md: cream canvas #faf9f5, coral primary #cc785c that darkens only on press, licensed serif display (substitute Cormorant Garamond), Inter body, dark #181715 product surfaces, 40px buttons and inputs.",
"expected_output": "A Next.js app with all components, serif display type utilities, coral primary with an active-only darker state, dark bands scoped with the dark class, and a showcase page covering every component.",
"files": [],
"expectations": [
"Uses --template next with --preset b0 and --name editorial",
"Installs the documented font substitutes from Fontsource instead of linking licensed fonts",
"Defines type-* utilities for the display scale with serif family, weight 400-500 and negative tracking, instead of --text-* theme keys",
"Uses active: rather than hover: for the primary darker state, per the DESIGN.md",
"Raises button and input heights to 40px across button, input, input-group, select and related controls",
"Uses className=\"dark\" on sections or cards to render dark product surfaces with the same components",
"Avoids the cmdk mount scroll jump on the inline Command example",
"Reports deliberate deviations from the DESIGN.md (font substitutes, kept hover, derived values)"
]
},
{
"id": 8,
"prompt": "Create a new design system using shadcn and the following DESIGN.md. <DESIGN.md: editorial cream canvas, coral primary, 40px controls, 12px cards, 16px hero container, pill badges, shadows rare, generous 32px card padding>",
"expected_output": "A new app scaffolded without a preset code by picking a style that matches the DESIGN.md geometry, then themed, with a full design system page.",
"files": [],
"expectations": [
"Does not ask for or invent a preset code; passes --base base --preset <style> so init doesn't stop at an interactive prompt",
"Picks a style matching the DESIGN.md geometry (e.g. maia for generous spacing and 12px+ cards, or sera for editorial serif type) and states the reason in one line",
"Passes --no-monorepo and --template vite (or next when asked)",
"Installs all components and builds the design system page per design-system-page.md",
"Reports contrast pairs below 4.5:1, including the DESIGN.md's own primary/on-primary pair if it fails"
]
} }
] ]
} }
@@ -0,0 +1,53 @@
#!/usr/bin/env node
// Lists installed UI components that no file in the showcase imports.
// Usage: node showcase-coverage.mjs <ui-dir> <showcase-dir...>
import { readdirSync, readFileSync, statSync } from "node:fs"
import { basename, extname, join, resolve } from "node:path"
const [uiArg, ...roots] = process.argv.slice(2)
if (!uiArg || roots.length === 0) {
console.error("Usage: node showcase-coverage.mjs <ui-dir> <showcase-dir...>")
process.exit(1)
}
const uiDir = resolve(uiArg)
const components = readdirSync(uiDir)
.filter((file) => [".tsx", ".ts"].includes(extname(file)))
.map((file) => basename(file, extname(file)))
function walk(path) {
if (statSync(path).isFile()) {
return [path]
}
return readdirSync(path).flatMap((entry) => {
const child = join(path, entry)
// Skip the ui directory so components don't count as using each other.
if (entry === "node_modules" || child === uiDir) {
return []
}
return walk(child)
})
}
const source = roots
.map((root) => resolve(root))
.flatMap(walk)
.filter((file) => /\.(tsx?|jsx?)$/.test(file))
.map((file) => readFileSync(file, "utf8"))
.join("\n")
const missing = components.filter(
(name) => !new RegExp(`/ui/${name}["']`).test(source)
)
console.log(
`${components.length - missing.length}/${components.length} components shown.`
)
if (missing.length > 0) {
console.log(`Missing: ${missing.join(", ")}`)
process.exit(1)
}