204 lines
5.5 KiB
Markdown
204 lines
5.5 KiB
Markdown
|
|
# 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.
|