Files
netto/docs/superpowers/specs/2026-07-05-base-ui-shadcn-wrapper-design.md
T

204 lines
5.5 KiB
Markdown
Raw Normal View History

2026-07-25 06:03:20 +08:00
# 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.