docs: document architecture and usage

Explain installation, styles, exports, and validation.

Record the component wrapper design and implementation plan.
This commit is contained in:
Maofeng
2026-07-25 06:03:20 +08:00
parent 4efe3f3c64
commit 08e293d5f7
3 changed files with 961 additions and 0 deletions
@@ -0,0 +1,652 @@
# Base UI Shadcn Wrapper Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** Build a source-only internal `components/ui` library that wraps all Base UI React components with Tailwind styles and supports both Base UI-style default namespace imports and shadcn-style named imports.
**Architecture:** Each UI component is a folder. Base UI parts are split into focused files such as `root.tsx`, `trigger.tsx`, and `popup.tsx`; `index.ts` assembles the default namespace export and named shadcn-style exports. Shared class merging lives in `lib/utils.ts`, and styles are static Tailwind class strings adapted from the Base UI documentation examples.
**Tech Stack:** React, TypeScript, `@base-ui/react`, Tailwind CSS, `clsx`, `tailwind-merge`, `class-variance-authority`.
---
## File Map
- Create `package.json`: local validation scripts and dependencies only.
- Create `tsconfig.json`: framework-neutral React TypeScript settings.
- Create `components/ui/**`: source templates, one folder per Base UI component.
- Create `lib/utils.ts`: `cn()` helper used by every styled part.
- Create `examples/smoke.tsx`: compile-only examples for default namespace and named shadcn-style APIs.
- Create `scripts/check-ui-exports.mjs`: verifies every planned component folder exists and exports a default.
- Create `README.md`: usage, dependency, import, and customization guidance.
## Component Folders
Create these component folders under `components/ui`:
```txt
accordion
alert-dialog
autocomplete
avatar
button
checkbox
checkbox-group
collapsible
combobox
context-menu
dialog
drawer
field
fieldset
form
input
menu
menubar
meter
navigation-menu
number-field
otp-field
popover
preview-card
progress
radio
scroll-area
select
separator
slider
switch
tabs
toast
toggle
toggle-group
toolbar
tooltip
```
## Task 1: Project Skeleton
**Files:**
- Create: `package.json`
- Create: `tsconfig.json`
- Create: `lib/utils.ts`
- Create: `components/ui/.gitkeep`
- [ ] **Step 1: Create `package.json`**
```json
{
"private": true,
"type": "module",
"scripts": {
"typecheck": "tsc --noEmit",
"check:exports": "node scripts/check-ui-exports.mjs",
"check": "npm run typecheck && npm run check:exports"
},
"dependencies": {
"@base-ui/react": "^1.6.0",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"tailwind-merge": "^3.3.1"
},
"devDependencies": {
"@types/react": "^19.0.0",
"@types/react-dom": "^19.0.0",
"typescript": "^5.8.0"
}
}
```
- [ ] **Step 2: Create `tsconfig.json`**
```json
{
"compilerOptions": {
"allowSyntheticDefaultImports": true,
"baseUrl": ".",
"declaration": false,
"esModuleInterop": true,
"jsx": "react-jsx",
"lib": ["ES2022", "DOM", "DOM.Iterable"],
"module": "ESNext",
"moduleResolution": "Bundler",
"noEmit": true,
"paths": {
"@/*": ["./*"]
},
"skipLibCheck": true,
"strict": true,
"target": "ES2022"
},
"include": ["components", "examples", "lib", "scripts"]
}
```
- [ ] **Step 3: Create `lib/utils.ts`**
```ts
import { clsx, type ClassValue } from "clsx"
import { twMerge } from "tailwind-merge"
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs))
}
```
- [ ] **Step 4: Install dependencies**
Run: `npm install`
Expected: `package-lock.json` and `node_modules` are created.
- [ ] **Step 5: Run typecheck**
Run: `npm run typecheck`
Expected: TypeScript exits successfully, or fails only because component files are not present yet.
## Task 2: Export Checker
**Files:**
- Create: `scripts/check-ui-exports.mjs`
- [ ] **Step 1: Create `scripts/check-ui-exports.mjs`**
```js
import { access, readFile } from "node:fs/promises"
import path from "node:path"
const components = [
"accordion",
"alert-dialog",
"autocomplete",
"avatar",
"button",
"checkbox",
"checkbox-group",
"collapsible",
"combobox",
"context-menu",
"dialog",
"drawer",
"field",
"fieldset",
"form",
"input",
"menu",
"menubar",
"meter",
"navigation-menu",
"number-field",
"otp-field",
"popover",
"preview-card",
"progress",
"radio",
"scroll-area",
"select",
"separator",
"slider",
"switch",
"tabs",
"toast",
"toggle",
"toggle-group",
"toolbar",
"tooltip",
]
const failures = []
for (const component of components) {
const indexPath = path.join("components", "ui", component, "index.ts")
try {
await access(indexPath)
const source = await readFile(indexPath, "utf8")
if (!source.includes("export default")) {
failures.push(`${indexPath}: missing default export`)
}
} catch {
failures.push(`${indexPath}: missing file`)
}
}
if (failures.length > 0) {
console.error(failures.join("\n"))
process.exit(1)
}
console.log(`Checked ${components.length} component exports.`)
```
- [ ] **Step 2: Run checker and verify it fails before components exist**
Run: `npm run check:exports`
Expected: FAIL with missing `components/ui/*/index.ts` files.
## Task 3: Implement Foundation Components
**Files:**
- Create: `components/ui/button/root.tsx`
- Create: `components/ui/button/index.ts`
- Create: `components/ui/input/root.tsx`
- Create: `components/ui/input/index.ts`
- Create: `components/ui/separator/root.tsx`
- Create: `components/ui/separator/index.ts`
- [ ] **Step 1: Implement Button**
`components/ui/button/root.tsx`:
```tsx
"use client"
import * as React from "react"
import { Button as BaseButton } from "@base-ui/react/button"
import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "@/lib/utils"
const buttonVariants = cva(
"inline-flex h-10 items-center justify-center rounded-md border border-neutral-950 bg-neutral-950 px-4 text-sm font-medium text-white select-none hover:bg-neutral-800 active:bg-neutral-700 data-disabled:pointer-events-none data-disabled:opacity-50 focus-visible:outline-2 focus-visible:-outline-offset-1 focus-visible:outline-neutral-950 dark:border-white dark:bg-white dark:text-neutral-950 dark:hover:bg-neutral-200 dark:active:bg-neutral-300 dark:focus-visible:outline-white",
{
variants: {
variant: {
default: "",
outline:
"border-neutral-300 bg-transparent text-neutral-950 hover:bg-neutral-100 active:bg-neutral-200 dark:border-neutral-700 dark:text-white dark:hover:bg-neutral-900 dark:active:bg-neutral-800",
ghost:
"border-transparent bg-transparent text-neutral-950 hover:bg-neutral-100 active:bg-neutral-200 dark:text-white dark:hover:bg-neutral-900 dark:active:bg-neutral-800",
destructive:
"border-red-600 bg-red-600 text-white hover:bg-red-700 active:bg-red-800 focus-visible:outline-red-600 dark:border-red-500 dark:bg-red-500 dark:hover:bg-red-600 dark:active:bg-red-700 dark:focus-visible:outline-red-500",
},
size: {
sm: "h-8 px-3 text-sm",
default: "h-10 px-4",
lg: "h-11 px-5",
icon: "size-10 px-0",
},
},
defaultVariants: {
variant: "default",
size: "default",
},
},
)
export type ButtonProps = React.ComponentPropsWithoutRef<typeof BaseButton> &
VariantProps<typeof buttonVariants>
export const Button = React.forwardRef<
React.ElementRef<typeof BaseButton>,
ButtonProps
>(({ className, variant, size, ...props }, ref) => (
<BaseButton
ref={ref}
className={cn(buttonVariants({ variant, size }), className)}
{...props}
/>
))
Button.displayName = "Button"
```
`components/ui/button/index.ts`:
```ts
export { Button, type ButtonProps } from "./root"
export { Button as default } from "./root"
```
- [ ] **Step 2: Implement Input**
`components/ui/input/root.tsx`:
```tsx
"use client"
import * as React from "react"
import { Input as BaseInput } from "@base-ui/react/input"
import { cn } from "@/lib/utils"
export type InputProps = React.ComponentPropsWithoutRef<typeof BaseInput>
export const Input = React.forwardRef<
React.ElementRef<typeof BaseInput>,
InputProps
>(({ className, ...props }, ref) => (
<BaseInput
ref={ref}
className={cn(
"h-10 w-full rounded-md border border-neutral-300 bg-transparent px-3 text-base text-neutral-950 outline-none placeholder:text-neutral-500 focus-visible:outline-2 focus-visible:-outline-offset-1 focus-visible:outline-neutral-950 disabled:cursor-not-allowed disabled:opacity-50 dark:border-neutral-700 dark:text-white dark:placeholder:text-neutral-400 dark:focus-visible:outline-white",
className,
)}
{...props}
/>
))
Input.displayName = "Input"
```
`components/ui/input/index.ts`:
```ts
export { Input, type InputProps } from "./root"
export { Input as default } from "./root"
```
- [ ] **Step 3: Implement Separator**
`components/ui/separator/root.tsx`:
```tsx
"use client"
import * as React from "react"
import { Separator as BaseSeparator } from "@base-ui/react/separator"
import { cn } from "@/lib/utils"
export type SeparatorProps = React.ComponentPropsWithoutRef<
typeof BaseSeparator
>
export const Separator = React.forwardRef<
React.ElementRef<typeof BaseSeparator>,
SeparatorProps
>(({ className, orientation = "horizontal", ...props }, ref) => (
<BaseSeparator
ref={ref}
orientation={orientation}
className={cn(
"shrink-0 bg-neutral-200 data-[orientation=horizontal]:h-px data-[orientation=horizontal]:w-full data-[orientation=vertical]:h-full data-[orientation=vertical]:w-px dark:bg-neutral-800",
className,
)}
{...props}
/>
))
Separator.displayName = "Separator"
```
`components/ui/separator/index.ts`:
```ts
export { Separator, type SeparatorProps } from "./root"
export { Separator as default } from "./root"
```
- [ ] **Step 4: Run typecheck**
Run: `npm run typecheck`
Expected: PASS for the files added in this task.
## Task 4: Implement Dialog Pattern
**Files:**
- Create: `components/ui/dialog/root.tsx`
- Create: `components/ui/dialog/trigger.tsx`
- Create: `components/ui/dialog/portal.tsx`
- Create: `components/ui/dialog/backdrop.tsx`
- Create: `components/ui/dialog/viewport.tsx`
- Create: `components/ui/dialog/popup.tsx`
- Create: `components/ui/dialog/title.tsx`
- Create: `components/ui/dialog/description.tsx`
- Create: `components/ui/dialog/close.tsx`
- Create: `components/ui/dialog/content.tsx`
- Create: `components/ui/dialog/index.ts`
- [ ] **Step 1: Create styled primitive files**
Use `@base-ui/react/dialog` parts. Keep the class model from the Base UI dialog examples: neutral trigger button, fixed translucent backdrop, centered popup, compact title and muted description. `content.tsx` composes `Portal`, `Backdrop`, and `Popup`.
- [ ] **Step 2: Create namespace and named exports**
`index.ts` must default export:
```ts
const DialogPrimitive = {
Root: DialogRoot,
Trigger: DialogTrigger,
Portal: DialogPortal,
Backdrop: DialogBackdrop,
Viewport: DialogViewport,
Popup: DialogPopup,
Title: DialogTitle,
Description: DialogDescription,
Close: DialogClose,
}
```
It must also named export `Dialog`, `DialogTrigger`, `DialogPortal`, `DialogBackdrop`, `DialogViewport`, `DialogPopup`, `DialogContent`, `DialogTitle`, `DialogDescription`, and `DialogClose`.
- [ ] **Step 3: Run typecheck**
Run: `npm run typecheck`
Expected: PASS.
## Task 5: Implement Dialog-Like Overlay Components
**Files:**
- Create folders and part files for `alert-dialog`, `drawer`, `popover`, `preview-card`, and `tooltip`.
- [ ] **Step 1: Implement Alert Dialog**
Mirror the dialog folder pattern with `@base-ui/react/alert-dialog`. Include styled parts for `Root`, `Trigger`, `Portal`, `Backdrop`, `Viewport` if present, `Popup`, `Title`, `Description`, and `Close`. Include shadcn-style `AlertDialog`, `AlertDialogTrigger`, `AlertDialogContent`, `AlertDialogTitle`, `AlertDialogDescription`, and `AlertDialogClose`.
- [ ] **Step 2: Implement Drawer**
Mirror Base UI Drawer anatomy. Split each part into a file. Preserve snap/gesture props by extending Base UI part props. Use Tailwind classes from Base UI drawer examples for backdrop, popup, handle, title, and description.
- [ ] **Step 3: Implement Popover and Preview Card**
Split `Root`, `Trigger`, `Portal`, `Positioner`, `Popup`, `Arrow`, and close/action parts where documented. Include shadcn-style `PopoverContent` and `PreviewCardContent` convenience components.
- [ ] **Step 4: Implement Tooltip**
Split `Provider` if documented, `Root`, `Trigger`, `Portal`, `Positioner`, `Popup`, and `Arrow`. Include `TooltipContent` as the shadcn-style convenience component.
- [ ] **Step 5: Run typecheck**
Run: `npm run typecheck`
Expected: PASS.
## Task 6: Implement Selection and Menu Components
**Files:**
- Create folders and part files for `select`, `combobox`, `autocomplete`, `menu`, `menubar`, `context-menu`, and `navigation-menu`.
- [ ] **Step 1: Implement Select**
Split every documented Base UI Select part into files. Include styled trigger, value, icon, portal, positioner, popup, item, item indicator, item text, group, label, separator, and scroll buttons where documented. Add shadcn-style named exports such as `Select`, `SelectTrigger`, `SelectValue`, `SelectContent`, `SelectItem`, `SelectGroup`, `SelectLabel`, and `SelectSeparator`.
- [ ] **Step 2: Implement Combobox and Autocomplete**
Split root, input, trigger, portal, positioner, popup, list, item, item text, item indicator, status, empty, and group/label parts where documented. Use Base UI example classes for popup, list, and highlighted item states.
- [ ] **Step 3: Implement Menu, Menubar, and Context Menu**
Use the same menu part style vocabulary across the three components: trigger buttons, popups, items, checkbox/radio items, separators, labels, groups, submenus, arrows, and positioners. Preserve all Base UI part props and shadcn-style convenience exports.
- [ ] **Step 4: Implement Navigation Menu**
Split root, list, item, trigger, content, viewport, positioner, link, indicator, and portal parts where documented. Keep layout classes static and allow `className` overrides on every part.
- [ ] **Step 5: Run typecheck**
Run: `npm run typecheck`
Expected: PASS.
## Task 7: Implement Form and Field Components
**Files:**
- Create folders and part files for `form`, `field`, `fieldset`, `checkbox`, `checkbox-group`, `radio`, `switch`, `number-field`, `otp-field`, and `slider`.
- [ ] **Step 1: Implement Field, Fieldset, and Form**
Split root/control/label/description/error/validity parts according to Base UI docs. Use compact label, muted description, and red error text classes from the examples.
- [ ] **Step 2: Implement Checkbox and Checkbox Group**
Split root, indicator, group, label, description, and error parts where documented. Include shadcn-style `Checkbox` with indicator composed inside when appropriate, plus primitive namespace exports.
- [ ] **Step 3: Implement Radio and Switch**
Split radio root/indicator/group parts and switch root/thumb parts. Use Base UI data attribute styling for checked, unchecked, and disabled states.
- [ ] **Step 4: Implement Number Field, OTP Field, and Slider**
Split increment/decrement buttons, input, scrubbing area, group, track, thumb, indicator, and item slots according to each anatomy page. Preserve CSS variable driven styles for slider and number-field controls.
- [ ] **Step 5: Run typecheck**
Run: `npm run typecheck`
Expected: PASS.
## Task 8: Implement Disclosure, Navigation, and Feedback Components
**Files:**
- Create folders and part files for `accordion`, `collapsible`, `tabs`, `toolbar`, `toggle`, `toggle-group`, `toast`, `progress`, `meter`, `avatar`, and `scroll-area`.
- [ ] **Step 1: Implement Accordion and Collapsible**
Split root, item, header, trigger, panel/content parts. Use Base UI example classes for border grouping, trigger focus, and panel transition states.
- [ ] **Step 2: Implement Tabs and Toolbar**
Split list, tab, panel, indicator, root, group, button, link, and separator parts where documented. Use data selected/orientation selectors for visual states.
- [ ] **Step 3: Implement Toggle and Toggle Group**
Use `class-variance-authority` for `variant` and `size` on single toggle controls, preserving the Base UI example as `default`.
- [ ] **Step 4: Implement Toast**
Split provider, viewport, root, title, description, close, action, and portal parts where documented. Preserve swipe/animation data attributes and static Tailwind classes.
- [ ] **Step 5: Implement Progress, Meter, Avatar, and Scroll Area**
Split root, indicator, image, fallback, viewport, scrollbar, thumb, and corner parts where documented. Use Base UI CSS variables for progress/meter values and scroll area sizing.
- [ ] **Step 6: Run typecheck**
Run: `npm run typecheck`
Expected: PASS.
## Task 9: Smoke Examples and Export Verification
**Files:**
- Create: `examples/smoke.tsx`
- Modify: `scripts/check-ui-exports.mjs` if any official component folder name changed during implementation.
- [ ] **Step 1: Create `examples/smoke.tsx`**
```tsx
import Dialog, {
Dialog as ShadcnDialog,
DialogContent,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog"
import Button, { Button as UIButton } from "@/components/ui/button"
export function Smoke() {
return (
<div>
<Button focusableWhenDisabled>Primitive-style button</Button>
<UIButton variant="outline" size="sm">
Named button
</UIButton>
<Dialog.Root>
<Dialog.Trigger>Primitive dialog</Dialog.Trigger>
<Dialog.Portal>
<Dialog.Backdrop />
<Dialog.Popup>
<Dialog.Title>Primitive dialog</Dialog.Title>
<Dialog.Description>Default namespace API.</Dialog.Description>
<Dialog.Close>Close</Dialog.Close>
</Dialog.Popup>
</Dialog.Portal>
</Dialog.Root>
<ShadcnDialog>
<DialogTrigger>Named dialog</DialogTrigger>
<DialogContent>
<DialogTitle>Named dialog</DialogTitle>
</DialogContent>
</ShadcnDialog>
</div>
)
}
```
- [ ] **Step 2: Run export checker**
Run: `npm run check:exports`
Expected: `Checked 37 component exports.`
- [ ] **Step 3: Run full check**
Run: `npm run check`
Expected: TypeScript and export checks pass.
## Task 10: README
**Files:**
- Create: `README.md`
- [ ] **Step 1: Create README**
Include:
```md
# Base UI Tailwind Components
Source-owned Base UI React wrappers styled with Tailwind CSS.
## Install Dependencies
\`\`\`bash
npm install @base-ui/react clsx tailwind-merge class-variance-authority
\`\`\`
## Import Styles
Default imports expose styled Base UI part namespaces:
\`\`\`tsx
import Dialog from "@/components/ui/dialog"
\`\`\`
Named imports expose shadcn-style components:
\`\`\`tsx
import { Dialog, DialogContent, DialogTrigger } from "@/components/ui/dialog"
\`\`\`
Every styled part accepts \`className\`, and custom classes are merged after defaults.
```
- [ ] **Step 2: Run full check**
Run: `npm run check`
Expected: PASS.
## Self-Review Checklist
- [ ] Every component in the spec has a folder in `components/ui`.
- [ ] Every folder has `index.ts`.
- [ ] Every `index.ts` has a default export.
- [ ] Composite components split Base UI parts into separate files.
- [ ] Named exports support shadcn-style usage.
- [ ] `examples/smoke.tsx` compiles.
- [ ] README explains default and named imports.
@@ -0,0 +1,203 @@
# Base UI Shadcn Wrapper Design
## Goal
Build an internal source-template component library that wraps Base UI React with Tailwind CSS styles. The library should feel like shadcn/ui for application developers while preserving a Base UI-style part API for people who prefer Base UI's composition model.
The first version is source-only. It will not publish to npm, provide a CLI, or maintain registry metadata.
## Inputs
- Base library: `@base-ui/react`
- Styling: Tailwind CSS utility classes
- Default visual style: Base UI React documentation examples
- Consumer model: copy source files into internal React projects
- Framework target: framework-neutral React source templates
## Component Scope
The first version covers every component listed in the Base UI React component documentation:
- Accordion
- Alert Dialog
- Autocomplete
- Avatar
- Button
- Checkbox
- Checkbox Group
- Collapsible
- Combobox
- Context Menu
- Dialog
- Drawer
- Field
- Fieldset
- Form
- Input
- Menu
- Menubar
- Meter
- Navigation Menu
- Number Field
- OTP Field
- Popover
- Preview Card
- Progress
- Radio
- Scroll Area
- Select
- Separator
- Slider
- Switch
- Tabs
- Toast
- Toggle
- Toggle Group
- Toolbar
- Tooltip
## Directory Shape
Each component is a folder under `components/ui`. Composite components split every Base UI part into its own file.
Example:
```txt
components/ui/dialog/
index.ts
root.tsx
trigger.tsx
portal.tsx
backdrop.tsx
popup.tsx
content.tsx
title.tsx
description.tsx
close.tsx
```
Single-part components also use folders for consistency:
```txt
components/ui/button/
index.ts
root.tsx
```
Shared helpers live in:
```txt
lib/utils.ts
```
## Export Model
Every component module supports two API styles from the same implementation.
Default export is the styled Base UI namespace:
```tsx
import Dialog from "@/components/ui/dialog"
<Dialog.Root>
<Dialog.Trigger>Open</Dialog.Trigger>
<Dialog.Portal>
<Dialog.Backdrop />
<Dialog.Popup />
</Dialog.Portal>
</Dialog.Root>
```
Named exports are the shadcn-style flattened API:
```tsx
import {
Dialog,
DialogTrigger,
DialogContent,
DialogTitle,
} from "@/components/ui/dialog"
<Dialog>
<DialogTrigger>Open</DialogTrigger>
<DialogContent>
<DialogTitle>Title</DialogTitle>
</DialogContent>
</Dialog>
```
For single-part components:
```tsx
import Button from "@/components/ui/button"
import { Button as UIButton } from "@/components/ui/button"
```
`Button` as the default export is the styled primitive for Base UI-style import habits. `Button` as a named export is the shadcn-style import. For single-part components these can reference the same underlying styled component.
Part file names should mirror Base UI part names where possible. Additional shadcn-style convenience components, such as `DialogContent`, can live beside the primitive part files when they compose multiple parts.
## Implementation Rules
- Styled parts wrap Base UI parts directly.
- Each part owns its own Tailwind classes.
- Shadcn-style convenience components compose styled parts instead of duplicating styles.
- The internal library does not re-export unstyled Base UI primitives. Consumers who need unstyled primitives can import from `@base-ui/react` directly.
- Every styled part accepts `className` and merges it after the default classes using `cn`.
- Forward refs where the underlying Base UI part supports refs.
- Use Base UI data attributes and CSS variables for stateful styling.
- Add `"use client"` to interactive component files so copied source works in Next.js projects while remaining harmless in ordinary React projects.
## Styling Rules
Default styles should mirror Base UI documentation examples:
- Neutral black/white visual language
- Clear borders
- Compact control sizing
- `focus-visible` outlines
- `data-*` selectors for open, highlighted, checked, disabled, selected, and similar states
- Dark mode classes where Base UI examples provide them
- Tailwind classes in source files, not CSS Modules
Components should not add shadcn-style variant systems unless Base UI's official examples require that abstraction. The default implementation should stay close to the official Base UI Tailwind demos, and local variation should happen through `className` overrides.
## TypeScript Rules
- Component props extend the corresponding Base UI part props.
- Variant props are added only where the component defines variants.
- Export prop types for public components where useful.
- Avoid hiding Base UI capabilities such as `render`, `nativeButton`, `focusableWhenDisabled`, positioning props, and controlled-state props.
## Documentation
Add a README that explains:
- Required dependencies
- Tailwind requirements
- The default namespace export
- The named shadcn-style export
- The source-copy workflow
- How to customize classes safely
The README should include a short example for a single-part component and a composite component.
## Verification
The first implementation should verify:
- TypeScript compiles.
- Every component folder exports a default namespace or default primitive.
- Every component folder provides shadcn-style named exports where appropriate.
- Tailwind class strings are static enough for Tailwind scanning.
- Representative examples compile for both a single-part component and a composite component.
Visual verification can be added after the component set exists, using a local demo page or Storybook-like fixture.
## Non-Goals
- No npm package publishing.
- No CLI.
- No registry metadata.
- No attempt to create a separate design language.
- No unstyled primitive re-export layer.