Initial commit
This commit is contained in:
14
.claude-plugin/plugin.json
Normal file
14
.claude-plugin/plugin.json
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"name": "panda-css-workflows",
|
||||||
|
"description": "Expert Panda CSS workflows for React + Vite projects - setup, tokens, recipes, and components",
|
||||||
|
"version": "1.2.0",
|
||||||
|
"author": {
|
||||||
|
"name": "Shaun Fox"
|
||||||
|
},
|
||||||
|
"skills": [
|
||||||
|
"./skills/"
|
||||||
|
],
|
||||||
|
"agents": [
|
||||||
|
"./agents/panda-architect.md"
|
||||||
|
]
|
||||||
|
}
|
||||||
3
README.md
Normal file
3
README.md
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
# panda-css-workflows
|
||||||
|
|
||||||
|
Expert Panda CSS workflows for React + Vite projects - setup, tokens, recipes, and components
|
||||||
225
agents/panda-architect.md
Normal file
225
agents/panda-architect.md
Normal file
@@ -0,0 +1,225 @@
|
|||||||
|
---
|
||||||
|
name: panda-architect
|
||||||
|
description: Specialized agent for complex Panda CSS architectural work including setting up new projects, refactoring to Panda CSS, and designing component libraries
|
||||||
|
type: general-purpose
|
||||||
|
model: sonnet
|
||||||
|
tools:
|
||||||
|
- "*"
|
||||||
|
skills:
|
||||||
|
- panda-component-impl
|
||||||
|
- panda-create-stories
|
||||||
|
- panda-form-architecture
|
||||||
|
- panda-recipe-patterns
|
||||||
|
- panda-review-component
|
||||||
|
- panda-setup-config
|
||||||
|
- panda-token-architecture
|
||||||
|
---
|
||||||
|
|
||||||
|
You are a Panda CSS architecture expert specializing in React + Vite projects. You handle complex, multi-step architectural work like project setup, token system design, and CSS refactoring.
|
||||||
|
|
||||||
|
## Core Expertise
|
||||||
|
|
||||||
|
### 1. Panda CSS Configuration & Setup
|
||||||
|
- panda.config.ts architecture and best practices
|
||||||
|
- Preset creation for design systems
|
||||||
|
- Build integration with Vite
|
||||||
|
- TypeScript integration and path aliases
|
||||||
|
- strictTokens enforcement
|
||||||
|
|
||||||
|
### 2. Token Architecture
|
||||||
|
- Base tokens vs semantic tokens (two-layer system)
|
||||||
|
- Color palettes with numeric scales (0-100)
|
||||||
|
- Unified spacing/sizing scales
|
||||||
|
- Typography systems (fonts, weights, sizes, line heights)
|
||||||
|
- Theme-aware semantic tokens for light/dark modes
|
||||||
|
- Text styles for typography presets
|
||||||
|
|
||||||
|
### 3. Recipe Patterns
|
||||||
|
- Regular recipes for single-part components
|
||||||
|
- Slot recipes for multi-part components
|
||||||
|
- Variant design (size, variant, state)
|
||||||
|
- Compound variants for complex combinations
|
||||||
|
- Shared base styles between related recipes
|
||||||
|
- Dynamic variant generation from tokens
|
||||||
|
|
||||||
|
### 4. Component Implementation
|
||||||
|
- Box component as polymorphic foundation
|
||||||
|
- splitProps utility for CSS/HTML prop separation
|
||||||
|
- Recipe integration in React components
|
||||||
|
- TypeScript patterns with Panda CSS
|
||||||
|
- Accessibility best practices (ARIA, keyboard, focus)
|
||||||
|
- Icon systems and SVG sprites
|
||||||
|
|
||||||
|
### 5. Design System Patterns
|
||||||
|
- Conditions (pseudo-classes, states, responsive, container queries)
|
||||||
|
- Custom patterns (icon sizing, containers)
|
||||||
|
- Global styles and CSS reset
|
||||||
|
- Component composition strategies
|
||||||
|
|
||||||
|
## Your Working Approach
|
||||||
|
|
||||||
|
### Always Start with Planning
|
||||||
|
1. **Create a TodoWrite checklist** for any multi-step task
|
||||||
|
2. Break down complex work into clear, trackable steps
|
||||||
|
3. Mark progress as you complete each step
|
||||||
|
4. Don't skip accessibility or testing steps
|
||||||
|
|
||||||
|
### Reference Documentation When Needed
|
||||||
|
Use Context7 MCP to fetch up-to-date Panda CSS documentation:
|
||||||
|
- Ask it to resolve the library ID for "panda-css"
|
||||||
|
- Then fetch relevant docs for specific topics (setup, tokens, recipes, etc.)
|
||||||
|
- Reference official patterns when making architectural decisions
|
||||||
|
|
||||||
|
### Use Working Examples
|
||||||
|
Look at the `examples/` directory in the plugin for concrete patterns:
|
||||||
|
- **Configuration**: `panda.config.ts` - Full config with preset integration
|
||||||
|
- **Preset architecture**: `preset.ts` - Complete preset structure
|
||||||
|
- **Base tokens**: `primitives/` - Color scales, typography, sizing, animation
|
||||||
|
- **Semantic tokens**: `semantics/` - Theme-aware token layer
|
||||||
|
- **Utilities**: `utils/splitProps.ts`, `utils/ThemeContext.tsx`
|
||||||
|
- **Text styles**: `textStyles.ts`
|
||||||
|
- **Conditions**: `conditions.ts`
|
||||||
|
|
||||||
|
Read these files when you need concrete examples of implementation patterns.
|
||||||
|
|
||||||
|
### Follow Best Practices
|
||||||
|
- **strictTokens: true** - No hard-coded values allowed
|
||||||
|
- **Two-layer tokens** - Base tokens → semantic tokens
|
||||||
|
- **Semantic HTML first** - ARIA only to fill accessibility gaps
|
||||||
|
- **Visible focus states** - Use `_focusVisible` condition
|
||||||
|
- **Theme-aware tokens** - Structure: `{ base: '...', _dark: '...' }`
|
||||||
|
- **Recipe-based styling** - Avoid inline CSS prop usage
|
||||||
|
|
||||||
|
## Common Task Patterns
|
||||||
|
|
||||||
|
### Setting Up Panda CSS in a New Project
|
||||||
|
1. Verify React + Vite environment
|
||||||
|
2. Create TodoWrite checklist with setup steps
|
||||||
|
3. Install `@pandacss/dev` as dev dependency
|
||||||
|
4. Run `npx panda init --postcss`
|
||||||
|
5. Configure `panda.config.ts`:
|
||||||
|
- Set `strictTokens: true`
|
||||||
|
- Set `jsxFramework: 'react'`
|
||||||
|
- Set `jsxStyleProps: 'all'`
|
||||||
|
- Configure output paths
|
||||||
|
6. Update `vite.config.ts` with path aliases
|
||||||
|
7. Update `tsconfig.json` with path mappings
|
||||||
|
8. Add Panda build scripts to package.json
|
||||||
|
9. Create initial token structure (colors, spacing, typography)
|
||||||
|
10. Build and validate with a test component
|
||||||
|
|
||||||
|
### Designing a Complete Token System
|
||||||
|
1. Create TodoWrite checklist for token architecture
|
||||||
|
2. Define **base tokens**:
|
||||||
|
- Color scales (0-100 or similar numeric system)
|
||||||
|
- Spacing scale (unified for spacing, sizes, radii)
|
||||||
|
- Typography tokens (fonts, weights, sizes, line heights)
|
||||||
|
- Animation tokens (durations, easings)
|
||||||
|
3. Create **semantic token layer**:
|
||||||
|
- Reference base tokens via `{colors.name.shade}` syntax
|
||||||
|
- Define theme-aware tokens: `{ base: '...', _dark: '...' }`
|
||||||
|
- Organize by purpose (backgrounds, borders, text, etc.)
|
||||||
|
4. Set up **text styles** for typography presets
|
||||||
|
5. Configure responsive breakpoints
|
||||||
|
6. Create example components/recipes to validate tokens
|
||||||
|
7. Enable `strictTokens` and verify no violations
|
||||||
|
|
||||||
|
### Creating Component Recipes
|
||||||
|
1. Determine recipe type (regular vs slot)
|
||||||
|
2. Define base styles (shared foundations)
|
||||||
|
3. Create variants (size, variant, visual state)
|
||||||
|
4. Add compound variants for complex combinations
|
||||||
|
5. Set default variants
|
||||||
|
6. Extract shared bases if multiple related recipes
|
||||||
|
7. Test all variant combinations
|
||||||
|
8. Verify theme switching works correctly
|
||||||
|
|
||||||
|
### Building React Components with Recipes
|
||||||
|
1. Import recipe and variant types
|
||||||
|
2. Use Box component as polymorphic base if needed
|
||||||
|
3. Apply recipe with variant props
|
||||||
|
4. Use splitProps to separate CSS from HTML props
|
||||||
|
5. Add TypeScript types (props + variant types)
|
||||||
|
6. Implement accessibility:
|
||||||
|
- Semantic HTML elements
|
||||||
|
- ARIA attributes where needed
|
||||||
|
- Keyboard navigation
|
||||||
|
- Focus management with `_focusVisible`
|
||||||
|
7. Test in light and dark themes
|
||||||
|
|
||||||
|
### Refactoring Existing CSS to Panda
|
||||||
|
1. Create TodoWrite checklist for migration
|
||||||
|
2. Audit existing styles and extract all design values
|
||||||
|
3. Create Panda token system from extracted values
|
||||||
|
4. Convert component styles to recipes (one component at a time)
|
||||||
|
5. Update component implementations to use recipes
|
||||||
|
6. Enable `strictTokens: true` and fix violations
|
||||||
|
7. Run visual regression tests to validate parity
|
||||||
|
8. Remove old CSS-in-JS dependencies
|
||||||
|
|
||||||
|
## Key Architecture Decisions
|
||||||
|
|
||||||
|
**Config Structure**:
|
||||||
|
```typescript
|
||||||
|
// Separate concerns into modules
|
||||||
|
import { tokens } from './tokens'
|
||||||
|
import { semanticTokens } from './semantic-tokens'
|
||||||
|
import { recipes } from './recipes'
|
||||||
|
import { textStyles } from './text-styles'
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
strictTokens: true,
|
||||||
|
jsxFramework: 'react',
|
||||||
|
theme: { extend: { tokens, semanticTokens, recipes, textStyles } }
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
**Token Organization**:
|
||||||
|
```typescript
|
||||||
|
// Base tokens: numeric scales
|
||||||
|
colors: {
|
||||||
|
brand: { 0: '#fff', 10: '#...', ..., 100: '#000' }
|
||||||
|
}
|
||||||
|
|
||||||
|
// Semantic tokens: reference base
|
||||||
|
colors: {
|
||||||
|
bg: {
|
||||||
|
primary: { base: '{colors.brand.10}', _dark: '{colors.brand.90}' }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Recipe Structure**:
|
||||||
|
```typescript
|
||||||
|
// Extract shared bases
|
||||||
|
const buttonBase = { ... }
|
||||||
|
|
||||||
|
export const button = defineRecipe({
|
||||||
|
base: buttonBase,
|
||||||
|
variants: { size: {...}, variant: {...} },
|
||||||
|
defaultVariants: { size: 'md', variant: 'solid' }
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
**Component Pattern**:
|
||||||
|
```typescript
|
||||||
|
import { button, type ButtonVariants } from '../styled-system/recipes'
|
||||||
|
import { splitProps } from '../utils/splitProps'
|
||||||
|
|
||||||
|
type ButtonProps = ButtonVariants & React.ComponentProps<'button'>
|
||||||
|
|
||||||
|
export const Button = (props: ButtonProps) => {
|
||||||
|
const [variantProps, htmlProps] = splitProps(props, ['size', 'variant'])
|
||||||
|
return <button className={button(variantProps)} {...htmlProps} />
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Your Communication Style
|
||||||
|
|
||||||
|
- **Proactive**: Anticipate needs and suggest improvements
|
||||||
|
- **Systematic**: Use TodoWrite for complex work, check off progress
|
||||||
|
- **Thorough**: Don't skip steps, especially accessibility and testing
|
||||||
|
- **Practical**: Show concrete code examples, not just theory
|
||||||
|
- **Educational**: Explain *why* certain patterns are best practices
|
||||||
|
|
||||||
|
When you complete a task, summarize what was done, point out key decisions made, and suggest next steps if appropriate.
|
||||||
73
plugin.lock.json
Normal file
73
plugin.lock.json
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
{
|
||||||
|
"$schema": "internal://schemas/plugin.lock.v1.json",
|
||||||
|
"pluginId": "gh:shaunrfox/okshaun-claude-marketplace:panda-css-workflows",
|
||||||
|
"normalized": {
|
||||||
|
"repo": null,
|
||||||
|
"ref": "refs/tags/v20251128.0",
|
||||||
|
"commit": "93e30a6a26bb8ea14afb4f0c9da39a101c2a39a5",
|
||||||
|
"treeHash": "fa508dd0b281e8f377b82c767b21a21e992696ff177086acb972369fba6c1f60",
|
||||||
|
"generatedAt": "2025-11-28T10:28:17.959769Z",
|
||||||
|
"toolVersion": "publish_plugins.py@0.2.0"
|
||||||
|
},
|
||||||
|
"origin": {
|
||||||
|
"remote": "git@github.com:zhongweili/42plugin-data.git",
|
||||||
|
"branch": "master",
|
||||||
|
"commit": "aa1497ed0949fd50e99e70d6324a29c5b34f9390",
|
||||||
|
"repoRoot": "/Users/zhongweili/projects/openmind/42plugin-data"
|
||||||
|
},
|
||||||
|
"manifest": {
|
||||||
|
"name": "panda-css-workflows",
|
||||||
|
"description": "Expert Panda CSS workflows for React + Vite projects - setup, tokens, recipes, and components",
|
||||||
|
"version": "1.2.0"
|
||||||
|
},
|
||||||
|
"content": {
|
||||||
|
"files": [
|
||||||
|
{
|
||||||
|
"path": "README.md",
|
||||||
|
"sha256": "ce5eeb7d0b08ec8420a33aab420cee9942957205e26eb57784151582ae40aa66"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "agents/panda-architect.md",
|
||||||
|
"sha256": "9959e4d98b17c4d7bbfaa8d7aa4e25f4390e2d841a3e370cea75f6c79076c58a"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": ".claude-plugin/plugin.json",
|
||||||
|
"sha256": "03be85148fed92f785b4578cea894f3a7e372c4bc11cb17e54ddc882200be0ce"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "skills/panda-token-architecture.md",
|
||||||
|
"sha256": "07fbaf523c37ac1d5f43f21153aa4d06e8507e375a8b4b03e932b2c9f48d9e7d"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "skills/panda-form-architecture.md",
|
||||||
|
"sha256": "26d971369550ad0903ac46591927dd7c49b6ff260101f1f58ae2a300b41225fa"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "skills/panda-review-component.md",
|
||||||
|
"sha256": "88ab46bbaab0ae957324d933f45071799bad2281ea3e3b07c0d438184d02f0cf"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "skills/panda-setup-config.md",
|
||||||
|
"sha256": "ae7b6457a418f10e49642b1e4d02c22bccf715d5866dceb964ae349a6d8320ae"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "skills/panda-create-stories.md",
|
||||||
|
"sha256": "26bb1309c8464d254437bc7bbd95d2b1097a994deae1b69d2734ca63973f7c83"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "skills/panda-recipe-patterns.md",
|
||||||
|
"sha256": "a9b4b5f3a6e4cbc8b4160462c092a7ca7cf176ecfb0f5d7d6b37d8c3d7aebbde"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "skills/panda-component-impl.md",
|
||||||
|
"sha256": "756c833f36b739c5048383a3ea51fe864b06af108c6b35a1e85bcdbaf04e11de"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"dirSha256": "fa508dd0b281e8f377b82c767b21a21e992696ff177086acb972369fba6c1f60"
|
||||||
|
},
|
||||||
|
"security": {
|
||||||
|
"scannedAt": null,
|
||||||
|
"scannerVersion": null,
|
||||||
|
"flags": []
|
||||||
|
}
|
||||||
|
}
|
||||||
709
skills/panda-component-impl.md
Normal file
709
skills/panda-component-impl.md
Normal file
@@ -0,0 +1,709 @@
|
|||||||
|
---
|
||||||
|
name: panda-component-impl
|
||||||
|
description: Build React components that properly use Panda CSS patterns, recipes, TypeScript integration, and accessibility best practices
|
||||||
|
---
|
||||||
|
|
||||||
|
# Panda CSS Component Implementation
|
||||||
|
|
||||||
|
## When to Use This Skill
|
||||||
|
|
||||||
|
Use this skill when:
|
||||||
|
- Building React components with Panda CSS styling
|
||||||
|
- Implementing recipe-based component variants
|
||||||
|
- Creating polymorphic components (components that can render as different elements)
|
||||||
|
- Integrating TypeScript with Panda CSS types
|
||||||
|
- Implementing accessible components with Panda CSS
|
||||||
|
- Setting up component file structure
|
||||||
|
|
||||||
|
For creating the recipes themselves, use the **panda-recipe-patterns** skill first.
|
||||||
|
|
||||||
|
## Component File Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
src/
|
||||||
|
components/
|
||||||
|
Button/
|
||||||
|
Button.tsx # Component implementation
|
||||||
|
index.tsx # Public exports
|
||||||
|
Button.stories.tsx # Storybook documentation (optional)
|
||||||
|
Icon/
|
||||||
|
Icon.tsx
|
||||||
|
index.tsx
|
||||||
|
svg/ # SVG source files (for icon systems)
|
||||||
|
CheckBox/
|
||||||
|
CheckBox.tsx
|
||||||
|
index.tsx
|
||||||
|
```
|
||||||
|
|
||||||
|
**Pattern**: Each component in its own directory with implementation + exports.
|
||||||
|
|
||||||
|
## Base Component Pattern (Box)
|
||||||
|
|
||||||
|
The Box component is the foundation - a polymorphic element that accepts all Panda CSS style props.
|
||||||
|
|
||||||
|
Create: `src/components/Box/Box.tsx`
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { createElement } from 'react'
|
||||||
|
import type { ComponentPropsWithoutRef, ElementType, ReactNode } from 'react'
|
||||||
|
import { cx } from '@styled-system/css'
|
||||||
|
import { box } from '@styled-system/patterns'
|
||||||
|
import type { SystemStyleObject } from '@styled-system/types'
|
||||||
|
import { splitProps } from '~/utils/splitProps'
|
||||||
|
|
||||||
|
// Box can render as any HTML element
|
||||||
|
export type BoxProps =
|
||||||
|
Omit<ComponentPropsWithoutRef<ElementType>, 'as'> &
|
||||||
|
SystemStyleObject & // Enable all Panda CSS style props
|
||||||
|
{
|
||||||
|
as?: ElementType
|
||||||
|
children?: ReactNode
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Box = ({ as = 'div', ...props }: BoxProps) => {
|
||||||
|
// Separate Panda CSS props from HTML props
|
||||||
|
const [className, otherProps] = splitProps(props)
|
||||||
|
|
||||||
|
// Combine box pattern with custom className
|
||||||
|
const comboClassName = cx(box({}), className)
|
||||||
|
|
||||||
|
return createElement(as, { className: comboClassName, ...otherProps })
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Key Points**:
|
||||||
|
- `as` prop: Polymorphic rendering (div, button, a, span, etc.)
|
||||||
|
- `SystemStyleObject`: Enables all Panda CSS style props (bg, px, fontSize, etc.)
|
||||||
|
- `splitProps`: Utility to separate CSS props from HTML props
|
||||||
|
- `createElement`: Dynamic element creation based on `as` prop
|
||||||
|
|
||||||
|
## The splitProps Utility
|
||||||
|
|
||||||
|
Critical utility for separating Panda CSS props from HTML attributes.
|
||||||
|
|
||||||
|
Create: `src/utils/splitProps.ts`
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { cx, css, splitCssProps } from '@styled-system/css'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Splits component props into Panda CSS props and HTML props.
|
||||||
|
* Returns [className, otherProps].
|
||||||
|
*/
|
||||||
|
export const splitProps = (
|
||||||
|
props: Record<string, any>
|
||||||
|
): [string, Record<string, any>] => {
|
||||||
|
// Panda's utility: splits CSS props from other props
|
||||||
|
const [cssProps, otherProps] = splitCssProps(props)
|
||||||
|
|
||||||
|
// Extract css prop separately
|
||||||
|
const { css: cssProp, ...styleProps } = cssProps
|
||||||
|
|
||||||
|
// Generate className from CSS props
|
||||||
|
const generatedClassName = css(cssProp, styleProps)
|
||||||
|
|
||||||
|
// Merge with existing className if present
|
||||||
|
const existingClassName = otherProps.className || ''
|
||||||
|
const mergedClassName = cx(existingClassName, generatedClassName)
|
||||||
|
|
||||||
|
// Remove className from otherProps (it's now in mergedClassName)
|
||||||
|
const { className, ...remainingProps } = otherProps
|
||||||
|
|
||||||
|
return [mergedClassName, remainingProps]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Why**: Enables inline style props on components while keeping clean HTML output.
|
||||||
|
|
||||||
|
**Usage**:
|
||||||
|
```typescript
|
||||||
|
// Component receives both CSS and HTML props
|
||||||
|
<Button bg="blue.50" px="20" onClick={handleClick} disabled>
|
||||||
|
|
||||||
|
// splitProps separates them:
|
||||||
|
// cssProps: { bg: 'blue.50', px: '20' }
|
||||||
|
// htmlProps: { onClick: handleClick, disabled: true }
|
||||||
|
```
|
||||||
|
|
||||||
|
## Recipe-Based Components
|
||||||
|
|
||||||
|
### Simple Recipe Component
|
||||||
|
|
||||||
|
Create: `src/components/Button/Button.tsx`
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { type FC } from 'react'
|
||||||
|
import { cx } from '@styled-system/css'
|
||||||
|
import { button, type ButtonVariantProps } from '@styled-system/recipes'
|
||||||
|
import { Box, type BoxProps } from '../Box/Box'
|
||||||
|
import { splitProps } from '~/utils/splitProps'
|
||||||
|
|
||||||
|
export type ButtonProps =
|
||||||
|
BoxProps &
|
||||||
|
ButtonVariantProps &
|
||||||
|
{
|
||||||
|
loading?: boolean
|
||||||
|
disabled?: boolean
|
||||||
|
href?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Button: FC<ButtonProps> = ({
|
||||||
|
variant,
|
||||||
|
size,
|
||||||
|
loading = false,
|
||||||
|
disabled = false,
|
||||||
|
href,
|
||||||
|
...props
|
||||||
|
}) => {
|
||||||
|
// Separate Panda CSS props from HTML props
|
||||||
|
const [className, otherProps] = splitProps(props)
|
||||||
|
|
||||||
|
// Determine element type
|
||||||
|
const as = href ? 'a' : 'button'
|
||||||
|
|
||||||
|
// Combine recipe className with custom className
|
||||||
|
const comboClassName = cx(
|
||||||
|
button({ variant, size }), // Recipe styles
|
||||||
|
className // Custom overrides
|
||||||
|
)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
as={as}
|
||||||
|
className={comboClassName}
|
||||||
|
disabled={loading || disabled}
|
||||||
|
href={href}
|
||||||
|
{...otherProps}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Pattern Breakdown**:
|
||||||
|
1. Import recipe and its variant types from `@styled-system/recipes`
|
||||||
|
2. Extend `BoxProps` with `ButtonVariantProps` for full type safety
|
||||||
|
3. Use `splitProps` to separate CSS from HTML props
|
||||||
|
4. Apply recipe with `button({ variant, size })`
|
||||||
|
5. Merge recipe className with custom className using `cx`
|
||||||
|
6. Pass to Box component for rendering
|
||||||
|
|
||||||
|
### Slot Recipe Component
|
||||||
|
|
||||||
|
Multi-part components use slot recipes.
|
||||||
|
|
||||||
|
Create: `src/components/CheckBox/CheckBox.tsx`
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { type FC, type InputHTMLAttributes } from 'react'
|
||||||
|
import { checkbox, type CheckboxVariantProps } from '@styled-system/recipes'
|
||||||
|
import { Box } from '../Box/Box'
|
||||||
|
import { Icon } from '../Icon/Icon'
|
||||||
|
|
||||||
|
export type CheckBoxProps =
|
||||||
|
Omit<InputHTMLAttributes<HTMLInputElement>, 'size'> &
|
||||||
|
CheckboxVariantProps &
|
||||||
|
{
|
||||||
|
label?: string
|
||||||
|
indeterminate?: boolean
|
||||||
|
error?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export const CheckBox: FC<CheckBoxProps> = ({
|
||||||
|
size,
|
||||||
|
label,
|
||||||
|
indeterminate = false,
|
||||||
|
error = false,
|
||||||
|
checked,
|
||||||
|
...props
|
||||||
|
}) => {
|
||||||
|
// Get slot class names from recipe
|
||||||
|
const { container, input, indicator } = checkbox({ size })
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box as="label" className={container}>
|
||||||
|
<Box
|
||||||
|
as="input"
|
||||||
|
type="checkbox"
|
||||||
|
className={input}
|
||||||
|
checked={checked}
|
||||||
|
// Data attributes for custom states
|
||||||
|
{...(indeterminate && { 'data-indeterminate': true })}
|
||||||
|
{...(error && { 'data-error': true })}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Different icons for different states */}
|
||||||
|
<Icon className={indicator} name="checkbox" data-state="unchecked" />
|
||||||
|
<Icon className={indicator} name="checkbox-checked" data-state="checked" />
|
||||||
|
<Icon className={indicator} name="checkbox-indeterminate" data-state="indeterminate" />
|
||||||
|
|
||||||
|
{label && (
|
||||||
|
<Box as="span" className={checkbox().label}>
|
||||||
|
{label}
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Slot Recipe Pattern**:
|
||||||
|
1. Destructure slot classes: `{ container, input, indicator }`
|
||||||
|
2. Apply each slot class to corresponding element
|
||||||
|
3. Use data attributes for custom states: `data-indeterminate`, `data-error`
|
||||||
|
4. Recipe CSS targets these data attributes via conditions
|
||||||
|
|
||||||
|
### Component with Conditional Rendering
|
||||||
|
|
||||||
|
Create: `src/components/Button/Button.tsx` (with loading state)
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { type FC, type ReactNode } from 'react'
|
||||||
|
import { cx } from '@styled-system/css'
|
||||||
|
import { button, type ButtonVariantProps } from '@styled-system/recipes'
|
||||||
|
import { Box, type BoxProps } from '../Box/Box'
|
||||||
|
import { Spinner } from '../Spinner/Spinner'
|
||||||
|
import { splitProps } from '~/utils/splitProps'
|
||||||
|
|
||||||
|
export type ButtonProps =
|
||||||
|
BoxProps &
|
||||||
|
ButtonVariantProps &
|
||||||
|
{
|
||||||
|
loading?: boolean
|
||||||
|
leftIcon?: ReactNode
|
||||||
|
rightIcon?: ReactNode
|
||||||
|
children: ReactNode
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Button: FC<ButtonProps> = ({
|
||||||
|
variant,
|
||||||
|
size,
|
||||||
|
loading = false,
|
||||||
|
disabled = false,
|
||||||
|
leftIcon,
|
||||||
|
rightIcon,
|
||||||
|
children,
|
||||||
|
...props
|
||||||
|
}) => {
|
||||||
|
const [className, otherProps] = splitProps(props)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
as="button"
|
||||||
|
className={cx(button({ variant, size }), className)}
|
||||||
|
disabled={loading || disabled}
|
||||||
|
aria-busy={loading} // Accessibility: announce loading state
|
||||||
|
{...otherProps}
|
||||||
|
>
|
||||||
|
{/* Show spinner when loading */}
|
||||||
|
{loading && <Spinner size={size} />}
|
||||||
|
|
||||||
|
{/* Show left icon if not loading */}
|
||||||
|
{!loading && leftIcon}
|
||||||
|
|
||||||
|
{/* Button text */}
|
||||||
|
<span>{children}</span>
|
||||||
|
|
||||||
|
{/* Right icon */}
|
||||||
|
{!loading && rightIcon}
|
||||||
|
</Box>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## TypeScript Patterns
|
||||||
|
|
||||||
|
### Extract Recipe Types
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { button, type ButtonVariantProps } from '@styled-system/recipes'
|
||||||
|
|
||||||
|
// ButtonVariantProps includes:
|
||||||
|
// - variant?: 'primary' | 'secondary' | 'outline' | 'ghost'
|
||||||
|
// - size?: 'small' | 'medium' | 'large'
|
||||||
|
```
|
||||||
|
|
||||||
|
**Pattern**: Always use generated variant types for prop types.
|
||||||
|
|
||||||
|
### Omit Conflicting Props
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { text, type TextVariantProps } from '@styled-system/recipes'
|
||||||
|
|
||||||
|
// Avoid prop conflicts between Box and recipe
|
||||||
|
export type TextProps =
|
||||||
|
Omit<BoxProps, keyof TextVariantProps> & // Remove conflicts
|
||||||
|
TextVariantProps &
|
||||||
|
{
|
||||||
|
children: ReactNode
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Why**: Prevents TypeScript errors when Box and recipe define same props.
|
||||||
|
|
||||||
|
### Conditional Value Types
|
||||||
|
|
||||||
|
For responsive/theme-aware props:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { type ConditionalValue } from '@styled-system/types'
|
||||||
|
import { type ColorToken } from '@styled-system/tokens'
|
||||||
|
|
||||||
|
export type IconProps = {
|
||||||
|
fill?: ConditionalValue<ColorToken> // Enables: fill="blue.50" or fill={{ base: 'blue.50', _dark: 'blue.40' }}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Component Props Pattern
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { type ComponentPropsWithoutRef } from 'react'
|
||||||
|
|
||||||
|
// Get all props for a specific HTML element
|
||||||
|
export type InputProps = ComponentPropsWithoutRef<'input'> & {
|
||||||
|
// Custom props
|
||||||
|
}
|
||||||
|
|
||||||
|
// For polymorphic components
|
||||||
|
export type BoxProps = ComponentPropsWithoutRef<ElementType> & {
|
||||||
|
as?: ElementType
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Accessibility Patterns
|
||||||
|
|
||||||
|
### Always Include focusVisible
|
||||||
|
|
||||||
|
In recipes:
|
||||||
|
```typescript
|
||||||
|
base: {
|
||||||
|
_focusVisible: {
|
||||||
|
outlineWidth: '2',
|
||||||
|
outlineOffset: '1',
|
||||||
|
outlineColor: { base: 'blue.50', _dark: 'blue.40' }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Why**: Provides visible focus indication for keyboard navigation.
|
||||||
|
|
||||||
|
### ARIA Attributes
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
export const Button: FC<ButtonProps> = ({ loading, disabled, ...props }) => {
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
disabled={loading || disabled}
|
||||||
|
aria-disabled={disabled}
|
||||||
|
aria-busy={loading}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Common ARIA Attributes**:
|
||||||
|
- `aria-label`: Label for screen readers
|
||||||
|
- `aria-disabled`: Disabled state
|
||||||
|
- `aria-busy`: Loading state
|
||||||
|
- `aria-checked`: Checkbox/radio state
|
||||||
|
- `aria-expanded`: Collapsed/expanded state
|
||||||
|
- `aria-pressed`: Toggle button state
|
||||||
|
|
||||||
|
### Keyboard Interaction
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
export const MenuItem: FC<MenuItemProps> = ({ onClick, ...props }) => {
|
||||||
|
const handleKeyDown = (e: KeyboardEvent) => {
|
||||||
|
if (e.key === ' ' || e.key === 'Spacebar' || e.key === 'Enter') {
|
||||||
|
e.preventDefault()
|
||||||
|
onClick?.(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
role="button"
|
||||||
|
tabIndex={0}
|
||||||
|
onKeyDown={handleKeyDown}
|
||||||
|
onClick={onClick}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Why**: Ensure keyboard users can interact with custom components.
|
||||||
|
|
||||||
|
### Match Multiple State Selectors
|
||||||
|
|
||||||
|
In recipes, support both native and custom states:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
conditions: {
|
||||||
|
checked: '&:is(:checked, [data-checked], [aria-checked=true], [data-state="checked"])'
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Why**: Works with native inputs AND custom components.
|
||||||
|
|
||||||
|
## Icon Component Pattern
|
||||||
|
|
||||||
|
Icons often need special handling for sizing and color.
|
||||||
|
|
||||||
|
Create: `src/components/Icon/Icon.tsx`
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { type FC } from 'react'
|
||||||
|
import { cx } from '@styled-system/css'
|
||||||
|
import { icon } from '@styled-system/patterns'
|
||||||
|
import type { ConditionalValue } from '@styled-system/types'
|
||||||
|
import type { ColorToken } from '@styled-system/tokens'
|
||||||
|
import { Box, type BoxProps } from '../Box/Box'
|
||||||
|
import { splitProps } from '~/utils/splitProps'
|
||||||
|
import { numericSizes } from '~/styles/tokens'
|
||||||
|
|
||||||
|
// Constrain size to numeric tokens only
|
||||||
|
export type AllowedIconSizes = keyof typeof numericSizes
|
||||||
|
|
||||||
|
export type IconProps =
|
||||||
|
Omit<BoxProps, 'size'> & // Remove BoxProps size
|
||||||
|
{
|
||||||
|
name: string // Icon identifier
|
||||||
|
size?: AllowedIconSizes
|
||||||
|
fill?: ConditionalValue<ColorToken>
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Icon: FC<IconProps> = ({
|
||||||
|
name,
|
||||||
|
size = '24',
|
||||||
|
fill = 'currentColor',
|
||||||
|
...props
|
||||||
|
}) => {
|
||||||
|
const [className, otherProps] = splitProps(props)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
as="svg"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
className={cx(icon({ size }), className)} // icon pattern sets width + height
|
||||||
|
fill="none"
|
||||||
|
stroke={fill === 'currentColor' ? fill : undefined}
|
||||||
|
{...otherProps}
|
||||||
|
>
|
||||||
|
{/* SVG sprite reference */}
|
||||||
|
<use xlinkHref={`/sprite.svg#${name}`} />
|
||||||
|
</Box>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Pattern**: Custom icon pattern enforces square sizing via tokens.
|
||||||
|
|
||||||
|
## Responsive Components
|
||||||
|
|
||||||
|
### Responsive Props
|
||||||
|
|
||||||
|
Use object syntax for breakpoint-based props:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
<Box
|
||||||
|
px={{ base: '16', md: '20', lg: '24' }}
|
||||||
|
fontSize={{ base: 'sm', md: 'md' }}
|
||||||
|
display={{ base: 'block', lg: 'flex' }}
|
||||||
|
/>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Container Queries
|
||||||
|
|
||||||
|
For component-level responsive design:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
<Box
|
||||||
|
containerType="inline-size" // Enable container queries
|
||||||
|
width="full"
|
||||||
|
>
|
||||||
|
<Box
|
||||||
|
// Responsive based on CONTAINER size, not viewport
|
||||||
|
p={{ base: '12', '@container(min-width: 400px)': '20' }}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Component Composition
|
||||||
|
|
||||||
|
### Compose with Box
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
export const Card: FC<BoxProps> = (props) => {
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
bg={{ base: 'white', _dark: 'slate.90' }}
|
||||||
|
borderRadius="8"
|
||||||
|
boxShadow="md"
|
||||||
|
p="20"
|
||||||
|
{...props} // Allow overrides
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Pattern**: Provide sensible defaults, allow prop overrides.
|
||||||
|
|
||||||
|
### Compound Components
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Menu.tsx
|
||||||
|
export const Menu: FC<MenuProps> = ({ children, ...props }) => {
|
||||||
|
const { container } = menu()
|
||||||
|
return <Box className={container} {...props}>{children}</Box>
|
||||||
|
}
|
||||||
|
|
||||||
|
// MenuItem.tsx
|
||||||
|
export const MenuItem: FC<MenuItemProps> = ({ children, ...props }) => {
|
||||||
|
const { item } = menu() // Access same recipe
|
||||||
|
return <Box className={item} {...props}>{children}</Box>
|
||||||
|
}
|
||||||
|
|
||||||
|
// Usage
|
||||||
|
<Menu>
|
||||||
|
<MenuItem>Item 1</MenuItem>
|
||||||
|
<MenuItem>Item 2</MenuItem>
|
||||||
|
</Menu>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Best Practices Checklist
|
||||||
|
|
||||||
|
Create TodoWrite items when building components:
|
||||||
|
|
||||||
|
- [ ] Use Box as foundation for polymorphic components
|
||||||
|
- [ ] Apply splitProps to separate CSS from HTML props
|
||||||
|
- [ ] Import and use recipe variant types for TypeScript
|
||||||
|
- [ ] Include ARIA attributes for accessibility
|
||||||
|
- [ ] Add keyboard interaction for custom interactive elements
|
||||||
|
- [ ] Use _focusVisible for visible focus states
|
||||||
|
- [ ] Test component in light AND dark themes
|
||||||
|
- [ ] Validate all variant combinations work correctly
|
||||||
|
- [ ] Test with keyboard-only navigation
|
||||||
|
- [ ] Test with screen reader (basic check)
|
||||||
|
|
||||||
|
## Common Pitfalls
|
||||||
|
|
||||||
|
### Avoid: Mixing CSS Approaches
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// BAD: Mixing inline styles, Panda props, and classes
|
||||||
|
<Box
|
||||||
|
style={{ backgroundColor: 'red' }} // Inline style (avoid)
|
||||||
|
bg="blue.50" // Panda CSS (good)
|
||||||
|
className="custom-class" // External CSS (avoid)
|
||||||
|
/>
|
||||||
|
|
||||||
|
// GOOD: Use Panda CSS exclusively
|
||||||
|
<Box bg="blue.50" px="20" />
|
||||||
|
```
|
||||||
|
|
||||||
|
### Avoid: Not Using Recipe Types
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// BAD: Manual prop types (out of sync with recipe)
|
||||||
|
type ButtonProps = {
|
||||||
|
variant?: 'primary' | 'secondary'
|
||||||
|
}
|
||||||
|
|
||||||
|
// GOOD: Use generated types
|
||||||
|
import { type ButtonVariantProps } from '@styled-system/recipes'
|
||||||
|
type ButtonProps = ButtonVariantProps
|
||||||
|
```
|
||||||
|
|
||||||
|
### Avoid: Missing Accessibility
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// BAD: No keyboard support, no ARIA
|
||||||
|
<div onClick={handleClick}>Click me</div>
|
||||||
|
|
||||||
|
// GOOD: Proper semantics and keyboard support
|
||||||
|
<button onClick={handleClick} aria-label="Action button">
|
||||||
|
Click me
|
||||||
|
</button>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Avoid: Over-wrapping
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// BAD: Unnecessary div wrappers
|
||||||
|
<Box>
|
||||||
|
<Box>
|
||||||
|
<Box>Content</Box>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
// GOOD: Minimal, semantic structure
|
||||||
|
<Box>Content</Box>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Accessing Official Panda CSS Docs
|
||||||
|
|
||||||
|
For component implementation patterns:
|
||||||
|
|
||||||
|
1. **Resolve library ID**: `mcp__MCP_DOCKER__resolve-library-id` with `libraryName: "panda-css"`
|
||||||
|
2. **Fetch docs**: `mcp__MCP_DOCKER__get-library-docs` with:
|
||||||
|
- `topic: "recipes"` - Using recipes in components
|
||||||
|
- `topic: "typescript"` - TypeScript patterns
|
||||||
|
- `topic: "patterns"` - Built-in patterns
|
||||||
|
|
||||||
|
## Exporting Components
|
||||||
|
|
||||||
|
Create: `src/components/Button/index.tsx`
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
export { Button } from './Button'
|
||||||
|
export type { ButtonProps } from './Button'
|
||||||
|
```
|
||||||
|
|
||||||
|
Create: `src/index.ts` (main library export)
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Components
|
||||||
|
export { Box } from './components/Box'
|
||||||
|
export { Button, IconButton } from './components/Button'
|
||||||
|
export { Icon } from './components/Icon'
|
||||||
|
export { CheckBox } from './components/CheckBox'
|
||||||
|
// ... more components
|
||||||
|
|
||||||
|
// Types
|
||||||
|
export type { BoxProps } from './components/Box'
|
||||||
|
export type { ButtonProps, IconButtonProps } from './components/Button'
|
||||||
|
export type { IconProps } from './components/Icon'
|
||||||
|
// ... more types
|
||||||
|
```
|
||||||
|
|
||||||
|
**Pattern**: Export both components and their prop types for consuming projects.
|
||||||
|
|
||||||
|
## Working Examples
|
||||||
|
|
||||||
|
Reference these files in the `examples/` directory for production-tested patterns:
|
||||||
|
|
||||||
|
**Utility Functions:**
|
||||||
|
- `examples/utils/splitProps.ts` - CSS/HTML prop separation utility
|
||||||
|
```typescript
|
||||||
|
// Separates Panda CSS props from HTML props
|
||||||
|
const [className, htmlProps] = splitProps(props);
|
||||||
|
// Uses splitCssProps, css(), and cx() to merge classNames
|
||||||
|
```
|
||||||
|
- `examples/utils/ThemeContext.tsx` - Theme provider with localStorage persistence
|
||||||
|
```typescript
|
||||||
|
// Manages light/dark theme with system preference detection
|
||||||
|
// Persists user preference to localStorage
|
||||||
|
// Applies theme class to document root
|
||||||
|
```
|
||||||
|
|
||||||
|
**Token & Configuration Integration:**
|
||||||
|
- `examples/preset.ts` - Shows how to integrate tokens with components via preset
|
||||||
|
- `examples/conditions.ts` - Custom conditions for component states
|
||||||
|
- `examples/textStyles.ts` - Typography presets for text components
|
||||||
|
|
||||||
|
**For Complete Component Examples:**
|
||||||
|
While this plugin focuses on architecture patterns, you can reference:
|
||||||
|
- The skills themselves (this file, panda-recipe-patterns) contain inline component examples
|
||||||
|
- Use the **panda-architect** agent for full component implementations
|
||||||
1323
skills/panda-create-stories.md
Normal file
1323
skills/panda-create-stories.md
Normal file
File diff suppressed because it is too large
Load Diff
930
skills/panda-form-architecture.md
Normal file
930
skills/panda-form-architecture.md
Normal file
@@ -0,0 +1,930 @@
|
|||||||
|
---
|
||||||
|
name: panda-form-architecture
|
||||||
|
description: Design and implement composable form component architectures using atomic design principles with Panda CSS
|
||||||
|
---
|
||||||
|
|
||||||
|
# Panda CSS Form Architecture
|
||||||
|
|
||||||
|
## When to Use This Skill
|
||||||
|
|
||||||
|
Use this skill when:
|
||||||
|
- Building a form component system from scratch
|
||||||
|
- Refactoring existing forms to use composable patterns
|
||||||
|
- Creating form field wrappers for consistent accessibility and error handling
|
||||||
|
- Implementing a design system's form components
|
||||||
|
- Establishing form component hierarchy and composition patterns
|
||||||
|
|
||||||
|
For implementing individual form components (buttons, inputs), also reference **panda-component-impl**.
|
||||||
|
For creating recipes for these components, use **panda-recipe-patterns**.
|
||||||
|
|
||||||
|
## Form Component Composability Philosophy
|
||||||
|
|
||||||
|
Form components should follow a progressive composition model where simpler components combine into more complex ones. This atomic design approach creates:
|
||||||
|
- **Reusability**: Foundational components work in multiple contexts
|
||||||
|
- **Consistency**: Shared primitives ensure visual and behavioral uniformity
|
||||||
|
- **Flexibility**: Compose components differently for different use cases
|
||||||
|
- **Maintainability**: Changes to primitives cascade to all consumers
|
||||||
|
|
||||||
|
## Three-Layer Architecture
|
||||||
|
|
||||||
|
### Layer 1: Atomic Components (Primitives)
|
||||||
|
|
||||||
|
The foundational styled elements that map directly to HTML form controls.
|
||||||
|
|
||||||
|
**Characteristics:**
|
||||||
|
- Single responsibility (one HTML element)
|
||||||
|
- No internal composition
|
||||||
|
- Accept Panda CSS style props for customization
|
||||||
|
- Minimal logic (mostly styling)
|
||||||
|
|
||||||
|
**Components:**
|
||||||
|
```typescript
|
||||||
|
<Box> // Polymorphic base (any HTML element)
|
||||||
|
<Button> // Styled <button> or <a>
|
||||||
|
<TextInput> // Styled <input type="text">
|
||||||
|
<Textarea> // Styled <textarea>
|
||||||
|
<CheckBox> // Styled <input type="checkbox">
|
||||||
|
<Toggle> // Styled <input type="checkbox"> for toggle switches
|
||||||
|
<Radio> // Styled <input type="radio">
|
||||||
|
<Text> // Styled text for labels, help text, errors
|
||||||
|
<Label> // Styled <label> element
|
||||||
|
```
|
||||||
|
|
||||||
|
**Example Implementation (Atomic):**
|
||||||
|
|
||||||
|
Create: `src/components/TextInput/TextInput.tsx`
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { type FC, type InputHTMLAttributes } from 'react'
|
||||||
|
import { cx } from '@styled-system/css'
|
||||||
|
import { textInput, type TextInputVariantProps } from '@styled-system/recipes'
|
||||||
|
import { Box, type BoxProps } from '../Box/Box'
|
||||||
|
import { splitProps } from '~/utils/splitProps'
|
||||||
|
|
||||||
|
export type TextInputProps =
|
||||||
|
Omit<InputHTMLAttributes<HTMLInputElement>, 'size'> &
|
||||||
|
BoxProps &
|
||||||
|
TextInputVariantProps &
|
||||||
|
{
|
||||||
|
error?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export const TextInput: FC<TextInputProps> = ({
|
||||||
|
size,
|
||||||
|
error = false,
|
||||||
|
...props
|
||||||
|
}) => {
|
||||||
|
const [className, otherProps] = splitProps(props)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
as="input"
|
||||||
|
type="text"
|
||||||
|
className={cx(textInput({ size }), className)}
|
||||||
|
{...(error && { 'data-error': true })}
|
||||||
|
aria-invalid={error}
|
||||||
|
{...otherProps}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Key Points:**
|
||||||
|
- Simple wrapper around native input
|
||||||
|
- Applies Panda CSS recipe for styling
|
||||||
|
- Supports error state via data attribute
|
||||||
|
- Includes ARIA attribute for accessibility
|
||||||
|
- Allows style overrides via props
|
||||||
|
|
||||||
|
### Layer 2: Molecular Components (Composed Primitives)
|
||||||
|
|
||||||
|
Combine atomic components for common patterns. These handle basic composition without complex logic.
|
||||||
|
|
||||||
|
**Characteristics:**
|
||||||
|
- Combine 2-3 atomic components
|
||||||
|
- Handle common use cases (input + label)
|
||||||
|
- Still relatively simple
|
||||||
|
- Improve ergonomics (less boilerplate for consumers)
|
||||||
|
|
||||||
|
**Components:**
|
||||||
|
```typescript
|
||||||
|
<ToggleInput> // Toggle + Label
|
||||||
|
<CheckboxInput> // CheckBox + Label
|
||||||
|
<RadioInput> // Radio + Label
|
||||||
|
```
|
||||||
|
|
||||||
|
**Example Implementation (Molecular):**
|
||||||
|
|
||||||
|
Create: `src/components/CheckboxInput/CheckboxInput.tsx`
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { type FC, type InputHTMLAttributes, type ReactNode } from 'react'
|
||||||
|
import { cx } from '@styled-system/css'
|
||||||
|
import { checkboxInput } from '@styled-system/recipes'
|
||||||
|
import { CheckBox, type CheckBoxProps } from '../CheckBox/CheckBox'
|
||||||
|
import { Box } from '../Box/Box'
|
||||||
|
import { Text } from '../Text/Text'
|
||||||
|
|
||||||
|
export type CheckboxInputProps =
|
||||||
|
Omit<CheckBoxProps, 'label'> &
|
||||||
|
{
|
||||||
|
label?: ReactNode
|
||||||
|
description?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export const CheckboxInput: FC<CheckboxInputProps> = ({
|
||||||
|
label,
|
||||||
|
description,
|
||||||
|
size,
|
||||||
|
id,
|
||||||
|
...props
|
||||||
|
}) => {
|
||||||
|
// Generate ID if not provided (for label/input association)
|
||||||
|
const inputId = id || `checkbox-${Math.random().toString(36).substr(2, 9)}`
|
||||||
|
|
||||||
|
const { container, labelText, descriptionText } = checkboxInput({ size })
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box as="label" htmlFor={inputId} className={container}>
|
||||||
|
<CheckBox
|
||||||
|
id={inputId}
|
||||||
|
size={size}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{label && (
|
||||||
|
<Box className={labelText}>
|
||||||
|
{label}
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{description && (
|
||||||
|
<Text className={descriptionText} color="gray.60">
|
||||||
|
{description}
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Pattern Breakdown:**
|
||||||
|
1. Wraps CheckBox (atomic) with label and description
|
||||||
|
2. Auto-generates ID for accessibility if not provided
|
||||||
|
3. Uses slot recipe for layout styling
|
||||||
|
4. Accepts ReactNode for flexible label content
|
||||||
|
5. Optional description for additional context
|
||||||
|
|
||||||
|
**When to Use Molecular vs Atomic:**
|
||||||
|
- Use **atomic** when you need maximum flexibility and custom layouts
|
||||||
|
- Use **molecular** for standard form layouts (label beside/above input)
|
||||||
|
- Both should be available in your component library
|
||||||
|
|
||||||
|
### Layer 3: Organism Components (Complex Wrappers)
|
||||||
|
|
||||||
|
Higher-level components that provide structure, accessibility features, and common patterns like error handling.
|
||||||
|
|
||||||
|
**Characteristics:**
|
||||||
|
- Orchestrate multiple components
|
||||||
|
- Provide consistent patterns (labels, help text, errors)
|
||||||
|
- Handle accessibility concerns (ARIA attributes, ID linking)
|
||||||
|
- Accept children for maximum flexibility
|
||||||
|
|
||||||
|
**Primary Component: FormField**
|
||||||
|
|
||||||
|
`FormField` is a critical wrapper that provides:
|
||||||
|
- Consistent label/input/error layout
|
||||||
|
- Automatic accessibility (aria-describedby, aria-invalid)
|
||||||
|
- Error and help text display
|
||||||
|
- Required field indication
|
||||||
|
|
||||||
|
**Example Implementation (Organism):**
|
||||||
|
|
||||||
|
Create: `src/components/FormField/FormField.tsx`
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { type FC, type ReactNode, type ReactElement, cloneElement, isValidElement } from 'react'
|
||||||
|
import { formField } from '@styled-system/recipes'
|
||||||
|
import { Box } from '../Box/Box'
|
||||||
|
import { Label } from '../Label/Label'
|
||||||
|
import { Text } from '../Text/Text'
|
||||||
|
|
||||||
|
export type FormFieldProps = {
|
||||||
|
label: string
|
||||||
|
helpText?: string
|
||||||
|
errorText?: string
|
||||||
|
required?: boolean
|
||||||
|
children: ReactNode
|
||||||
|
htmlFor?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export const FormField: FC<FormFieldProps> = ({
|
||||||
|
label,
|
||||||
|
helpText,
|
||||||
|
errorText,
|
||||||
|
required = false,
|
||||||
|
children,
|
||||||
|
htmlFor,
|
||||||
|
}) => {
|
||||||
|
// Generate IDs for accessibility linking
|
||||||
|
const fieldId = htmlFor || `field-${Math.random().toString(36).substr(2, 9)}`
|
||||||
|
const helpTextId = `${fieldId}-help`
|
||||||
|
const errorTextId = `${fieldId}-error`
|
||||||
|
|
||||||
|
const { container, labelSlot, helpTextSlot, errorTextSlot } = formField()
|
||||||
|
|
||||||
|
// Clone children to inject ARIA attributes
|
||||||
|
const enhancedChildren = isValidElement(children)
|
||||||
|
? cloneElement(children as ReactElement<any>, {
|
||||||
|
id: fieldId,
|
||||||
|
'aria-describedby': [
|
||||||
|
helpText ? helpTextId : null,
|
||||||
|
errorText ? errorTextId : null,
|
||||||
|
]
|
||||||
|
.filter(Boolean)
|
||||||
|
.join(' ') || undefined,
|
||||||
|
'aria-invalid': !!errorText,
|
||||||
|
'aria-required': required,
|
||||||
|
})
|
||||||
|
: children
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box className={container}>
|
||||||
|
<Label htmlFor={fieldId} className={labelSlot}>
|
||||||
|
{label}
|
||||||
|
{required && (
|
||||||
|
<Text as="span" color="red.50" aria-label="required">
|
||||||
|
{' '}*
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
|
</Label>
|
||||||
|
|
||||||
|
{helpText && (
|
||||||
|
<Text id={helpTextId} className={helpTextSlot} color="gray.60">
|
||||||
|
{helpText}
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{enhancedChildren}
|
||||||
|
|
||||||
|
{errorText && (
|
||||||
|
<Text
|
||||||
|
id={errorTextId}
|
||||||
|
className={errorTextSlot}
|
||||||
|
color="red.50"
|
||||||
|
role="alert"
|
||||||
|
>
|
||||||
|
{errorText}
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Advanced Pattern Breakdown:**
|
||||||
|
1. **Auto-generates IDs**: Ensures proper label/input/error association
|
||||||
|
2. **Clones children**: Injects ARIA attributes into child input
|
||||||
|
3. **aria-describedby**: Links input to help text and errors
|
||||||
|
4. **aria-invalid**: Marks input as invalid when error present
|
||||||
|
5. **aria-required**: Indicates required fields to screen readers
|
||||||
|
6. **role="alert"**: Announces errors to screen readers immediately
|
||||||
|
|
||||||
|
**Accessibility Features:**
|
||||||
|
- Proper label/input association via `htmlFor` and `id`
|
||||||
|
- Help text linked via `aria-describedby`
|
||||||
|
- Error text linked via `aria-describedby` and marked as `role="alert"`
|
||||||
|
- Required fields indicated both visually (*) and semantically
|
||||||
|
- Screen reader support through proper ARIA attributes
|
||||||
|
|
||||||
|
## Full Form Implementation Example
|
||||||
|
|
||||||
|
Here's how all three layers compose into a complete, accessible form:
|
||||||
|
|
||||||
|
Create: `src/pages/UserProfileForm.tsx`
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { type FC, type FormEvent, useState } from 'react'
|
||||||
|
import { Box } from '~/components/Box/Box'
|
||||||
|
import { FormField } from '~/components/FormField/FormField'
|
||||||
|
import { TextInput } from '~/components/TextInput/TextInput'
|
||||||
|
import { Textarea } from '~/components/Textarea/Textarea'
|
||||||
|
import { RadioInput } from '~/components/RadioInput/RadioInput'
|
||||||
|
import { CheckboxInput } from '~/components/CheckboxInput/CheckboxInput'
|
||||||
|
import { Button } from '~/components/Button/Button'
|
||||||
|
|
||||||
|
export const UserProfileForm: FC = () => {
|
||||||
|
const [errors, setErrors] = useState<Record<string, string>>({})
|
||||||
|
|
||||||
|
const favColors = ['blue', 'red', 'yellow', 'green']
|
||||||
|
|
||||||
|
const handleSubmit = (e: FormEvent<HTMLFormElement>) => {
|
||||||
|
e.preventDefault()
|
||||||
|
|
||||||
|
// Form validation logic here
|
||||||
|
const formData = new FormData(e.currentTarget)
|
||||||
|
|
||||||
|
// Example validation
|
||||||
|
const firstName = formData.get('firstName') as string
|
||||||
|
if (!firstName) {
|
||||||
|
setErrors({ firstName: 'First name is required' })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Submit form...
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
as="form"
|
||||||
|
onSubmit={handleSubmit}
|
||||||
|
display="flex"
|
||||||
|
flexDirection="column"
|
||||||
|
gap="24"
|
||||||
|
maxWidth="600px"
|
||||||
|
margin="0 auto"
|
||||||
|
p="32"
|
||||||
|
>
|
||||||
|
<FormField
|
||||||
|
label="First name"
|
||||||
|
required
|
||||||
|
errorText={errors.firstName}
|
||||||
|
helpText="Enter your legal first name"
|
||||||
|
>
|
||||||
|
<TextInput
|
||||||
|
name="firstName"
|
||||||
|
placeholder="John"
|
||||||
|
error={!!errors.firstName}
|
||||||
|
/>
|
||||||
|
</FormField>
|
||||||
|
|
||||||
|
<FormField
|
||||||
|
label="Last name"
|
||||||
|
required
|
||||||
|
errorText={errors.lastName}
|
||||||
|
>
|
||||||
|
<TextInput
|
||||||
|
name="lastName"
|
||||||
|
placeholder="Doe"
|
||||||
|
error={!!errors.lastName}
|
||||||
|
/>
|
||||||
|
</FormField>
|
||||||
|
|
||||||
|
<FormField
|
||||||
|
label="Bio"
|
||||||
|
helpText="Tell us about yourself (optional)"
|
||||||
|
>
|
||||||
|
<Textarea
|
||||||
|
name="bio"
|
||||||
|
placeholder="I'm a developer who loves..."
|
||||||
|
rows={4}
|
||||||
|
/>
|
||||||
|
</FormField>
|
||||||
|
|
||||||
|
<FormField label="Favorite color" required>
|
||||||
|
<Box display="flex" flexDirection="column" gap="12">
|
||||||
|
{favColors.map((color) => (
|
||||||
|
<RadioInput
|
||||||
|
key={color}
|
||||||
|
name="favoriteColor"
|
||||||
|
value={color}
|
||||||
|
label={color.charAt(0).toUpperCase() + color.slice(1)}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</Box>
|
||||||
|
</FormField>
|
||||||
|
|
||||||
|
<CheckboxInput
|
||||||
|
name="newsletter"
|
||||||
|
label="Subscribe to newsletter"
|
||||||
|
description="Receive updates about new features and releases"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<CheckboxInput
|
||||||
|
name="terms"
|
||||||
|
label="I agree to the terms and conditions"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Box display="flex" justifyContent="flex-end" gap="12">
|
||||||
|
<Button type="button" variant="secondary">
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
<Button type="submit" variant="primary">
|
||||||
|
Save Profile
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Form Pattern Highlights:**
|
||||||
|
1. **FormField wrapper**: Consistent layout for all fields
|
||||||
|
2. **Error handling**: Centralized error state, passed to FormField
|
||||||
|
3. **Help text**: Contextual guidance for users
|
||||||
|
4. **Required indicators**: Visual and semantic marking
|
||||||
|
5. **Accessible structure**: Proper labels, ARIA attributes, semantic HTML
|
||||||
|
6. **Flexible composition**: Mix atomic and molecular components as needed
|
||||||
|
7. **Layout control**: Panda CSS props for spacing and arrangement
|
||||||
|
|
||||||
|
## Recipe Architecture for Form Components
|
||||||
|
|
||||||
|
### Atomic Component Recipes
|
||||||
|
|
||||||
|
Each atomic component should have its own recipe:
|
||||||
|
|
||||||
|
Create: `src/styles/recipes/text-input.recipe.ts`
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { defineRecipe } from '@pandacss/dev'
|
||||||
|
|
||||||
|
export const textInputRecipe = defineRecipe({
|
||||||
|
className: 'textInput',
|
||||||
|
description: 'Text input field styles',
|
||||||
|
|
||||||
|
base: {
|
||||||
|
width: 'full',
|
||||||
|
px: '12',
|
||||||
|
py: '8',
|
||||||
|
fontSize: 'md',
|
||||||
|
fontFamily: 'body',
|
||||||
|
borderWidth: '1',
|
||||||
|
borderColor: { base: 'gray.30', _dark: 'gray.70' },
|
||||||
|
borderRadius: '6',
|
||||||
|
bg: { base: 'white', _dark: 'slate.90' },
|
||||||
|
color: { base: 'gray.90', _dark: 'gray.10' },
|
||||||
|
transition: 'all 0.2s',
|
||||||
|
|
||||||
|
_placeholder: {
|
||||||
|
color: { base: 'gray.50', _dark: 'gray.60' },
|
||||||
|
},
|
||||||
|
|
||||||
|
_focus: {
|
||||||
|
outline: 'none',
|
||||||
|
borderColor: { base: 'blue.50', _dark: 'blue.40' },
|
||||||
|
boxShadow: '0 0 0 3px token(colors.blue.20)',
|
||||||
|
},
|
||||||
|
|
||||||
|
_disabled: {
|
||||||
|
opacity: 0.5,
|
||||||
|
cursor: 'not-allowed',
|
||||||
|
bg: { base: 'gray.10', _dark: 'gray.80' },
|
||||||
|
},
|
||||||
|
|
||||||
|
// Error state
|
||||||
|
'&[data-error=true]': {
|
||||||
|
borderColor: { base: 'red.50', _dark: 'red.40' },
|
||||||
|
_focus: {
|
||||||
|
boxShadow: '0 0 0 3px token(colors.red.20)',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
variants: {
|
||||||
|
size: {
|
||||||
|
sm: {
|
||||||
|
px: '8',
|
||||||
|
py: '6',
|
||||||
|
fontSize: 'sm',
|
||||||
|
},
|
||||||
|
md: {
|
||||||
|
px: '12',
|
||||||
|
py: '8',
|
||||||
|
fontSize: 'md',
|
||||||
|
},
|
||||||
|
lg: {
|
||||||
|
px: '16',
|
||||||
|
py: '12',
|
||||||
|
fontSize: 'lg',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
defaultVariants: {
|
||||||
|
size: 'md',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
**Recipe Best Practices:**
|
||||||
|
- Use data attributes for custom states (`data-error`)
|
||||||
|
- Include all interactive states (_focus, _disabled, _hover)
|
||||||
|
- Support both light and dark themes
|
||||||
|
- Use semantic token references
|
||||||
|
- Provide size variants
|
||||||
|
|
||||||
|
### Molecular Component Slot Recipes
|
||||||
|
|
||||||
|
Molecular components need layout coordination between primitives:
|
||||||
|
|
||||||
|
Create: `src/styles/recipes/checkbox-input.recipe.ts`
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { defineSlotRecipe } from '@pandacss/dev'
|
||||||
|
|
||||||
|
export const checkboxInputRecipe = defineSlotRecipe({
|
||||||
|
className: 'checkboxInput',
|
||||||
|
description: 'Checkbox with label composition',
|
||||||
|
|
||||||
|
slots: ['container', 'labelText', 'descriptionText'],
|
||||||
|
|
||||||
|
base: {
|
||||||
|
container: {
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'flex-start',
|
||||||
|
gap: '12',
|
||||||
|
cursor: 'pointer',
|
||||||
|
|
||||||
|
_hover: {
|
||||||
|
'& input': {
|
||||||
|
borderColor: { base: 'blue.40', _dark: 'blue.50' },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
labelText: {
|
||||||
|
fontSize: 'md',
|
||||||
|
fontWeight: 'medium',
|
||||||
|
color: { base: 'gray.90', _dark: 'gray.10' },
|
||||||
|
lineHeight: '1.5',
|
||||||
|
userSelect: 'none',
|
||||||
|
},
|
||||||
|
|
||||||
|
descriptionText: {
|
||||||
|
fontSize: 'sm',
|
||||||
|
color: { base: 'gray.60', _dark: 'gray.50' },
|
||||||
|
mt: '4',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
variants: {
|
||||||
|
size: {
|
||||||
|
sm: {
|
||||||
|
container: { gap: '8' },
|
||||||
|
labelText: { fontSize: 'sm' },
|
||||||
|
descriptionText: { fontSize: 'xs' },
|
||||||
|
},
|
||||||
|
md: {
|
||||||
|
container: { gap: '12' },
|
||||||
|
labelText: { fontSize: 'md' },
|
||||||
|
descriptionText: { fontSize: 'sm' },
|
||||||
|
},
|
||||||
|
lg: {
|
||||||
|
container: { gap: '16' },
|
||||||
|
labelText: { fontSize: 'lg' },
|
||||||
|
descriptionText: { fontSize: 'md' },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
defaultVariants: {
|
||||||
|
size: 'md',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
**Slot Recipe Pattern:**
|
||||||
|
- Define all component parts as slots
|
||||||
|
- Coordinate sizing across slots with variants
|
||||||
|
- Include hover states that affect children
|
||||||
|
- Use userSelect: 'none' on labels for better UX
|
||||||
|
|
||||||
|
### Organism Component Slot Recipes
|
||||||
|
|
||||||
|
FormField needs comprehensive slot management:
|
||||||
|
|
||||||
|
Create: `src/styles/recipes/form-field.recipe.ts`
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { defineSlotRecipe } from '@pandacss/dev'
|
||||||
|
|
||||||
|
export const formFieldRecipe = defineSlotRecipe({
|
||||||
|
className: 'formField',
|
||||||
|
description: 'Form field wrapper with label, help text, and error text',
|
||||||
|
|
||||||
|
slots: ['container', 'labelSlot', 'helpTextSlot', 'errorTextSlot'],
|
||||||
|
|
||||||
|
base: {
|
||||||
|
container: {
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
gap: '8',
|
||||||
|
width: 'full',
|
||||||
|
},
|
||||||
|
|
||||||
|
labelSlot: {
|
||||||
|
fontSize: 'sm',
|
||||||
|
fontWeight: 'semibold',
|
||||||
|
color: { base: 'gray.90', _dark: 'gray.10' },
|
||||||
|
mb: '4',
|
||||||
|
},
|
||||||
|
|
||||||
|
helpTextSlot: {
|
||||||
|
fontSize: 'sm',
|
||||||
|
color: { base: 'gray.60', _dark: 'gray.50' },
|
||||||
|
mt: '4',
|
||||||
|
},
|
||||||
|
|
||||||
|
errorTextSlot: {
|
||||||
|
fontSize: 'sm',
|
||||||
|
fontWeight: 'medium',
|
||||||
|
color: { base: 'red.60', _dark: 'red.40' },
|
||||||
|
mt: '4',
|
||||||
|
|
||||||
|
// Icon support
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: '6',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
## Best Practices Checklist
|
||||||
|
|
||||||
|
When building form architecture, create TodoWrite items for:
|
||||||
|
|
||||||
|
- [ ] Create atomic components (TextInput, CheckBox, Radio, etc.)
|
||||||
|
- [ ] Create recipes for atomic components with all variants
|
||||||
|
- [ ] Implement molecular compositions (CheckboxInput, RadioInput, etc.)
|
||||||
|
- [ ] Create slot recipes for molecular components
|
||||||
|
- [ ] Build FormField organism with accessibility features
|
||||||
|
- [ ] Create FormField slot recipe
|
||||||
|
- [ ] Test all components with keyboard navigation
|
||||||
|
- [ ] Test all components with screen reader
|
||||||
|
- [ ] Verify ARIA attributes are properly applied
|
||||||
|
- [ ] Test error states and error announcements
|
||||||
|
- [ ] Verify required field indicators work
|
||||||
|
- [ ] Test form submission and validation flow
|
||||||
|
- [ ] Verify light and dark theme support
|
||||||
|
- [ ] Test responsive behavior on mobile devices
|
||||||
|
- [ ] Create Storybook stories for all form components
|
||||||
|
- [ ] Document composition patterns and usage examples
|
||||||
|
|
||||||
|
## Common Pitfalls
|
||||||
|
|
||||||
|
### Avoid: Skipping the FormField Wrapper
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// BAD: Manual label/error handling (inconsistent, inaccessible)
|
||||||
|
<div>
|
||||||
|
<label htmlFor="email">Email</label>
|
||||||
|
<TextInput id="email" />
|
||||||
|
{error && <span style={{ color: 'red' }}>{error}</span>}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
// GOOD: Use FormField for consistency
|
||||||
|
<FormField label="Email" errorText={error}>
|
||||||
|
<TextInput />
|
||||||
|
</FormField>
|
||||||
|
```
|
||||||
|
|
||||||
|
**Why**: FormField ensures consistent accessibility, ARIA attributes, and visual design.
|
||||||
|
|
||||||
|
### Avoid: Not Linking Help Text and Errors
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// BAD: Screen readers can't connect help text to input
|
||||||
|
<Label>First name</Label>
|
||||||
|
<Text>Enter your legal name</Text>
|
||||||
|
<TextInput />
|
||||||
|
|
||||||
|
// GOOD: FormField automatically links via aria-describedby
|
||||||
|
<FormField label="First name" helpText="Enter your legal name">
|
||||||
|
<TextInput />
|
||||||
|
</FormField>
|
||||||
|
```
|
||||||
|
|
||||||
|
**Why**: Proper ARIA linking helps screen reader users understand context.
|
||||||
|
|
||||||
|
### Avoid: Over-composing Too Early
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// BAD: Creating rigid mega-components
|
||||||
|
<MagicFormInput
|
||||||
|
label="Email"
|
||||||
|
type="email"
|
||||||
|
helpText="..."
|
||||||
|
errorText="..."
|
||||||
|
icon="email"
|
||||||
|
suffix="@company.com"
|
||||||
|
tooltip="..."
|
||||||
|
/>
|
||||||
|
|
||||||
|
// GOOD: Compose flexibly from primitives
|
||||||
|
<FormField label="Email" helpText="..." errorText="...">
|
||||||
|
<Box display="flex" alignItems="center">
|
||||||
|
<Icon name="email" />
|
||||||
|
<TextInput type="email" />
|
||||||
|
<Text>@company.com</Text>
|
||||||
|
</Box>
|
||||||
|
</FormField>
|
||||||
|
```
|
||||||
|
|
||||||
|
**Why**: Flexible composition beats rigid mega-components. Keep primitives simple, compose as needed.
|
||||||
|
|
||||||
|
### Avoid: Inconsistent Error Handling
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// BAD: Different error patterns across forms
|
||||||
|
<TextInput className={error ? 'error' : ''} />
|
||||||
|
<CheckBox style={{ borderColor: error ? 'red' : 'gray' }} />
|
||||||
|
|
||||||
|
// GOOD: Consistent error prop
|
||||||
|
<TextInput error={!!errors.email} />
|
||||||
|
<CheckBox error={!!errors.terms} />
|
||||||
|
```
|
||||||
|
|
||||||
|
**Why**: Consistent error APIs make forms predictable and maintainable.
|
||||||
|
|
||||||
|
## Form Validation Integration
|
||||||
|
|
||||||
|
FormField works seamlessly with form libraries:
|
||||||
|
|
||||||
|
### React Hook Form Example
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { useForm } from 'react-hook-form'
|
||||||
|
|
||||||
|
export const ProfileForm = () => {
|
||||||
|
const {
|
||||||
|
register,
|
||||||
|
handleSubmit,
|
||||||
|
formState: { errors },
|
||||||
|
} = useForm()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<form onSubmit={handleSubmit(onSubmit)}>
|
||||||
|
<FormField
|
||||||
|
label="Email"
|
||||||
|
required
|
||||||
|
errorText={errors.email?.message}
|
||||||
|
>
|
||||||
|
<TextInput
|
||||||
|
{...register('email', {
|
||||||
|
required: 'Email is required',
|
||||||
|
pattern: {
|
||||||
|
value: /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i,
|
||||||
|
message: 'Invalid email address',
|
||||||
|
},
|
||||||
|
})}
|
||||||
|
error={!!errors.email}
|
||||||
|
/>
|
||||||
|
</FormField>
|
||||||
|
</form>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Formik Example
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { Formik, Form } from 'formik'
|
||||||
|
|
||||||
|
export const ProfileForm = () => {
|
||||||
|
return (
|
||||||
|
<Formik
|
||||||
|
initialValues={{ email: '' }}
|
||||||
|
onSubmit={handleSubmit}
|
||||||
|
>
|
||||||
|
{({ errors, touched, values, handleChange }) => (
|
||||||
|
<Form>
|
||||||
|
<FormField
|
||||||
|
label="Email"
|
||||||
|
required
|
||||||
|
errorText={touched.email && errors.email}
|
||||||
|
>
|
||||||
|
<TextInput
|
||||||
|
name="email"
|
||||||
|
value={values.email}
|
||||||
|
onChange={handleChange}
|
||||||
|
error={!!(touched.email && errors.email)}
|
||||||
|
/>
|
||||||
|
</FormField>
|
||||||
|
</Form>
|
||||||
|
)}
|
||||||
|
</Formik>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Progressive Enhancement
|
||||||
|
|
||||||
|
Start simple, add complexity as needed:
|
||||||
|
|
||||||
|
**Phase 1: Atomic components only**
|
||||||
|
```typescript
|
||||||
|
<Box as="form">
|
||||||
|
<Label htmlFor="email">Email</Label>
|
||||||
|
<TextInput id="email" name="email" />
|
||||||
|
<Button type="submit">Submit</Button>
|
||||||
|
</Box>
|
||||||
|
```
|
||||||
|
|
||||||
|
**Phase 2: Add molecular compositions**
|
||||||
|
```typescript
|
||||||
|
<Box as="form">
|
||||||
|
<CheckboxInput name="terms" label="I agree" />
|
||||||
|
<Button type="submit">Submit</Button>
|
||||||
|
</Box>
|
||||||
|
```
|
||||||
|
|
||||||
|
**Phase 3: Add organism wrapper**
|
||||||
|
```typescript
|
||||||
|
<Box as="form">
|
||||||
|
<FormField label="Email" helpText="...">
|
||||||
|
<TextInput name="email" />
|
||||||
|
</FormField>
|
||||||
|
<Button type="submit">Submit</Button>
|
||||||
|
</Box>
|
||||||
|
```
|
||||||
|
|
||||||
|
**Phase 4: Full validation and error handling**
|
||||||
|
```typescript
|
||||||
|
<Box as="form" onSubmit={handleSubmit}>
|
||||||
|
<FormField
|
||||||
|
label="Email"
|
||||||
|
required
|
||||||
|
helpText="We'll never share your email"
|
||||||
|
errorText={errors.email}
|
||||||
|
>
|
||||||
|
<TextInput
|
||||||
|
name="email"
|
||||||
|
error={!!errors.email}
|
||||||
|
/>
|
||||||
|
</FormField>
|
||||||
|
<Button type="submit" loading={isSubmitting}>
|
||||||
|
Submit
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Testing Form Components
|
||||||
|
|
||||||
|
### Accessibility Testing
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { render, screen } from '@testing-library/react'
|
||||||
|
import { axe, toHaveNoViolations } from 'jest-axe'
|
||||||
|
import { FormField } from './FormField'
|
||||||
|
import { TextInput } from '../TextInput/TextInput'
|
||||||
|
|
||||||
|
expect.extend(toHaveNoViolations)
|
||||||
|
|
||||||
|
test('FormField has no accessibility violations', async () => {
|
||||||
|
const { container } = render(
|
||||||
|
<FormField label="Email" helpText="Enter your email">
|
||||||
|
<TextInput />
|
||||||
|
</FormField>
|
||||||
|
)
|
||||||
|
|
||||||
|
const results = await axe(container)
|
||||||
|
expect(results).toHaveNoViolations()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('FormField properly links label and input', () => {
|
||||||
|
render(
|
||||||
|
<FormField label="Email">
|
||||||
|
<TextInput />
|
||||||
|
</FormField>
|
||||||
|
)
|
||||||
|
|
||||||
|
const input = screen.getByLabelText('Email')
|
||||||
|
expect(input).toBeInTheDocument()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('FormField announces errors to screen readers', () => {
|
||||||
|
render(
|
||||||
|
<FormField label="Email" errorText="Email is required">
|
||||||
|
<TextInput />
|
||||||
|
</FormField>
|
||||||
|
)
|
||||||
|
|
||||||
|
const error = screen.getByRole('alert')
|
||||||
|
expect(error).toHaveTextContent('Email is required')
|
||||||
|
|
||||||
|
const input = screen.getByLabelText('Email')
|
||||||
|
expect(input).toHaveAttribute('aria-invalid', 'true')
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
The form architecture follows a clear hierarchy:
|
||||||
|
|
||||||
|
**Atomic → Molecular → Organism**
|
||||||
|
|
||||||
|
1. **Atomic**: Individual styled form controls (TextInput, CheckBox, Button)
|
||||||
|
2. **Molecular**: Simple compositions (CheckboxInput = CheckBox + Label)
|
||||||
|
3. **Organism**: Complex wrappers (FormField = Label + HelpText + Input + ErrorText + ARIA)
|
||||||
|
|
||||||
|
**Key Principles:**
|
||||||
|
- Compose upward, never downward
|
||||||
|
- Provide both atomic and molecular variants
|
||||||
|
- Use FormField for consistent accessibility
|
||||||
|
- Keep primitives simple and flexible
|
||||||
|
- Test accessibility thoroughly
|
||||||
|
- Integrate with form libraries as needed
|
||||||
|
|
||||||
|
This architecture ensures your forms are accessible, maintainable, and consistent across your application.
|
||||||
875
skills/panda-recipe-patterns.md
Normal file
875
skills/panda-recipe-patterns.md
Normal file
@@ -0,0 +1,875 @@
|
|||||||
|
---
|
||||||
|
name: panda-recipe-patterns
|
||||||
|
description: Create and organize recipes (regular + slot recipes), compound variants, and understand when to use recipes vs patterns vs inline CSS
|
||||||
|
---
|
||||||
|
|
||||||
|
# Panda CSS Recipe Patterns
|
||||||
|
|
||||||
|
## When to Use This Skill
|
||||||
|
|
||||||
|
Use this skill when:
|
||||||
|
- Creating reusable component styles with variants
|
||||||
|
- Building multi-part component styling (checkboxes, tooltips, menus)
|
||||||
|
- Defining compound variants (multiple conditions)
|
||||||
|
- Organizing component style libraries
|
||||||
|
- Deciding between recipes, patterns, and inline CSS
|
||||||
|
|
||||||
|
For implementing these recipes in React components, use the **panda-component-impl** skill.
|
||||||
|
|
||||||
|
## When to Use What
|
||||||
|
|
||||||
|
### Use Recipes When:
|
||||||
|
- Component has multiple style variants (e.g., button: primary, secondary, outline)
|
||||||
|
- Component has size variants (small, medium, large)
|
||||||
|
- Styles are reused across multiple instances
|
||||||
|
- Need compound variants (combining multiple variant conditions)
|
||||||
|
- Want to auto-apply styles to JSX components
|
||||||
|
- Building a design system with consistent component APIs
|
||||||
|
|
||||||
|
### Use Patterns When:
|
||||||
|
- Need computed/transformed styles (e.g., icon sizing that sets width=height)
|
||||||
|
- Creating reusable layout primitives (stack, grid, container)
|
||||||
|
- Want a props-based API for common styling tasks
|
||||||
|
- Need to enforce constraints (e.g., size must be a valid token)
|
||||||
|
|
||||||
|
### Use Inline CSS When:
|
||||||
|
- One-off styles specific to a single usage
|
||||||
|
- Dynamic values from props or state
|
||||||
|
- Component-specific overrides of recipe defaults
|
||||||
|
- Rapid prototyping before extracting to recipe
|
||||||
|
|
||||||
|
**Example Decision Tree**:
|
||||||
|
```
|
||||||
|
Button component with variants? → Recipe
|
||||||
|
Icon with size prop? → Pattern
|
||||||
|
Unique spacing on one div? → Inline CSS
|
||||||
|
Reusable card layout? → Recipe
|
||||||
|
```
|
||||||
|
|
||||||
|
## Regular Recipes (Single-Part Components)
|
||||||
|
|
||||||
|
### Basic Recipe Structure
|
||||||
|
|
||||||
|
Create: `src/recipes/button.ts`
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { defineRecipe } from '@pandacss/dev';
|
||||||
|
|
||||||
|
const buttonBase = {
|
||||||
|
position: 'relative',
|
||||||
|
appearance: 'none',
|
||||||
|
minWidth: '0',
|
||||||
|
transitionDuration: 'fast',
|
||||||
|
transitionProperty: 'background, border-color, color, box-shadow',
|
||||||
|
transitionTimingFunction: 'default',
|
||||||
|
userSelect: 'none',
|
||||||
|
verticalAlign: 'middle',
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: '4',
|
||||||
|
fontFamily: 'body',
|
||||||
|
fontSize: '16',
|
||||||
|
fontWeight: 'medium',
|
||||||
|
lineHeight: 'default',
|
||||||
|
borderWidth: '1',
|
||||||
|
borderStyle: 'solid',
|
||||||
|
borderColor: 'transparent',
|
||||||
|
borderRadius: '4',
|
||||||
|
outlineWidth: '2',
|
||||||
|
outlineStyle: 'solid',
|
||||||
|
outlineColor: 'transparent',
|
||||||
|
outlineOffset: '1',
|
||||||
|
textDecoration: 'none',
|
||||||
|
whiteSpace: 'nowrap',
|
||||||
|
cursor: 'pointer',
|
||||||
|
_disabled: {
|
||||||
|
opacity: 0.4,
|
||||||
|
cursor: 'not-allowed',
|
||||||
|
},
|
||||||
|
_focusVisible: {
|
||||||
|
outlineColor: { base: 'slate.80', _dark: 'slate.5' },
|
||||||
|
},
|
||||||
|
'& svg': {
|
||||||
|
fill: 'current',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const buttonVariants = {
|
||||||
|
variant: {
|
||||||
|
primary: {
|
||||||
|
bg: { base: 'slate.90', _dark: 'slate.5' },
|
||||||
|
color: { base: 'slate.0', _dark: 'slate.90' },
|
||||||
|
_hover: {
|
||||||
|
bg: { base: 'slate.70', _dark: 'slate.10' },
|
||||||
|
},
|
||||||
|
_active: {
|
||||||
|
bg: { base: 'slate.100', _dark: 'slate.20' },
|
||||||
|
},
|
||||||
|
_disabled: {
|
||||||
|
_hover: {
|
||||||
|
bg: { base: 'slate.90', _dark: 'slate.5' },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
_selected: {
|
||||||
|
bg: { base: 'slate.5', _dark: 'slate.90' },
|
||||||
|
color: { base: 'slate.90', _dark: 'slate.0' },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
standard: {
|
||||||
|
bg: { base: 'slate.5', _dark: 'slate.70' },
|
||||||
|
color: { base: 'slate.90', _dark: 'slate.0' },
|
||||||
|
_hover: {
|
||||||
|
bg: { base: 'slate.10', _dark: 'slate.60' },
|
||||||
|
},
|
||||||
|
_active: {
|
||||||
|
bg: { base: 'slate.20', _dark: 'slate.80' },
|
||||||
|
},
|
||||||
|
_disabled: {
|
||||||
|
_hover: {
|
||||||
|
bg: { base: 'slate.5', _dark: 'slate.70' },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
_selected: {
|
||||||
|
bg: { base: 'slate.90', _dark: 'slate.5' },
|
||||||
|
color: { base: 'slate.0', _dark: 'slate.90' },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
hollow: {
|
||||||
|
bg: 'transparent',
|
||||||
|
borderColor: { base: 'slate.30', _dark: 'slate.60' },
|
||||||
|
color: { base: 'slate.90', _dark: 'slate.0' },
|
||||||
|
_hover: {
|
||||||
|
bg: { base: 'slate.10', _dark: 'slate.60' },
|
||||||
|
borderColor: { base: 'slate.10', _dark: 'slate.60' },
|
||||||
|
},
|
||||||
|
_active: {
|
||||||
|
bg: { base: 'slate.20', _dark: 'slate.80' },
|
||||||
|
borderColor: { base: 'slate.20', _dark: 'slate.80' },
|
||||||
|
},
|
||||||
|
_disabled: {
|
||||||
|
_hover: {
|
||||||
|
bg: 'transparent',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
_selected: {
|
||||||
|
bg: { base: 'slate.90', _dark: 'slate.5' },
|
||||||
|
color: { base: 'slate.0', _dark: 'slate.90' },
|
||||||
|
borderColor: 'transparent',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ghost: {
|
||||||
|
bg: 'transparent',
|
||||||
|
color: { base: 'slate.90', _dark: 'slate.0' },
|
||||||
|
_hover: {
|
||||||
|
bg: { base: 'slate.10', _dark: 'slate.60' },
|
||||||
|
},
|
||||||
|
_active: {
|
||||||
|
bg: { base: 'slate.20', _dark: 'slate.70' },
|
||||||
|
},
|
||||||
|
_disabled: {
|
||||||
|
_hover: {
|
||||||
|
bg: 'transparent',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
_selected: {
|
||||||
|
bg: { base: 'slate.90', _dark: 'slate.5' },
|
||||||
|
color: { base: 'slate.0', _dark: 'slate.90' },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
cta: {
|
||||||
|
bg: { base: 'gold.20', _dark: 'gold.30' },
|
||||||
|
color: 'slate.90',
|
||||||
|
_hover: {
|
||||||
|
bg: { base: 'gold.10', _dark: 'gold.20' },
|
||||||
|
},
|
||||||
|
_active: {
|
||||||
|
bg: { base: 'gold.30', _dark: 'gold.40' },
|
||||||
|
},
|
||||||
|
_disabled: {
|
||||||
|
_hover: {
|
||||||
|
bg: { base: 'gold.20', _dark: 'gold.30' },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
danger: {
|
||||||
|
bg: 'red.50',
|
||||||
|
color: 'slate.0',
|
||||||
|
_hover: {
|
||||||
|
bg: 'red.40',
|
||||||
|
},
|
||||||
|
_active: {
|
||||||
|
bg: 'red.60',
|
||||||
|
},
|
||||||
|
_disabled: {
|
||||||
|
_hover: {
|
||||||
|
bg: 'red.50',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const buttonRecipe = defineRecipe({
|
||||||
|
className: 'button',
|
||||||
|
jsx: ['Button'],
|
||||||
|
base: buttonBase,
|
||||||
|
variants: {
|
||||||
|
...buttonVariants,
|
||||||
|
size: {
|
||||||
|
medium: {
|
||||||
|
fontSize: '16',
|
||||||
|
py: '3',
|
||||||
|
px: '10',
|
||||||
|
},
|
||||||
|
large: {
|
||||||
|
fontSize: '16',
|
||||||
|
py: '7',
|
||||||
|
px: '12',
|
||||||
|
},
|
||||||
|
small: {
|
||||||
|
fontSize: '14',
|
||||||
|
py: '0',
|
||||||
|
px: '8',
|
||||||
|
'& svg': {
|
||||||
|
mt: '-1',
|
||||||
|
mb: '-1',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
defaultVariants: {
|
||||||
|
variant: 'standard',
|
||||||
|
size: 'medium',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export const iconButtonRecipe = defineRecipe({
|
||||||
|
className: 'icon-button',
|
||||||
|
jsx: ['IconButton'],
|
||||||
|
base: buttonBase,
|
||||||
|
variants: {
|
||||||
|
...buttonVariants,
|
||||||
|
size: {
|
||||||
|
medium: {
|
||||||
|
fontSize: '16',
|
||||||
|
p: '3',
|
||||||
|
},
|
||||||
|
large: {
|
||||||
|
fontSize: '16',
|
||||||
|
p: '7',
|
||||||
|
},
|
||||||
|
small: {
|
||||||
|
fontSize: '14',
|
||||||
|
p: '0',
|
||||||
|
'& svg': {
|
||||||
|
mt: '-1',
|
||||||
|
mb: '-1',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
defaultVariants: {
|
||||||
|
variant: 'standard',
|
||||||
|
size: 'medium',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### Extract Base Styles for DRY Code
|
||||||
|
|
||||||
|
**Pattern**: Share base styles between related recipes:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Shared base for button and iconButton
|
||||||
|
const buttonBase = {
|
||||||
|
appearance: 'none',
|
||||||
|
display: 'inline-flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
cursor: 'pointer',
|
||||||
|
transitionDuration: 'fast',
|
||||||
|
_disabled: { opacity: 0.4, cursor: 'not-allowed' },
|
||||||
|
_focusVisible: { outlineWidth: '2', outlineColor: 'blue.50' }
|
||||||
|
}
|
||||||
|
|
||||||
|
// Shared variant styles
|
||||||
|
const buttonVariants = {
|
||||||
|
variant: {
|
||||||
|
primary: { /* ... */ },
|
||||||
|
secondary: { /* ... */ }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Button recipe
|
||||||
|
export const buttonRecipe = defineRecipe({
|
||||||
|
className: 'button',
|
||||||
|
jsx: ['Button'],
|
||||||
|
base: buttonBase,
|
||||||
|
variants: {
|
||||||
|
...buttonVariants,
|
||||||
|
size: { /* button sizes */ }
|
||||||
|
},
|
||||||
|
defaultVariants: { variant: 'primary', size: 'medium' }
|
||||||
|
})
|
||||||
|
|
||||||
|
// IconButton recipe (reuses base and variants)
|
||||||
|
export const iconButtonRecipe = defineRecipe({
|
||||||
|
className: 'iconButton',
|
||||||
|
jsx: ['IconButton'],
|
||||||
|
base: buttonBase,
|
||||||
|
variants: {
|
||||||
|
...buttonVariants,
|
||||||
|
size: {
|
||||||
|
small: { width: '32', height: '32' },
|
||||||
|
medium: { width: '40', height: '40' },
|
||||||
|
large: { width: '48', height: '48' }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
defaultVariants: { variant: 'primary', size: 'medium' }
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
**Why**: Keeps related components visually consistent, reduces duplication.
|
||||||
|
|
||||||
|
### Dynamic Variants from Tokens
|
||||||
|
|
||||||
|
**Pattern**: Generate variants from design tokens:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { tokens } from '../styles/tokens'
|
||||||
|
|
||||||
|
// Get fontSize tokens
|
||||||
|
const fontSizeTokens = tokens.fontSizes
|
||||||
|
|
||||||
|
type FontSizeKey = keyof typeof fontSizeTokens
|
||||||
|
|
||||||
|
// Generate fontSize variants dynamically
|
||||||
|
const fontSizes = (Object.keys(fontSizeTokens) as FontSizeKey[]).reduce(
|
||||||
|
(accumulator, currentKey) => {
|
||||||
|
accumulator[currentKey] = { fontSize: currentKey }
|
||||||
|
return accumulator
|
||||||
|
},
|
||||||
|
{} as Record<FontSizeKey, Record<'fontSize', string>>
|
||||||
|
)
|
||||||
|
|
||||||
|
export const textRecipe = defineRecipe({
|
||||||
|
className: 'text',
|
||||||
|
jsx: ['Text'],
|
||||||
|
variants: {
|
||||||
|
size: fontSizes // All token sizes available as variants
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
**Why**: Automatically sync recipe variants with token changes.
|
||||||
|
|
||||||
|
## Slot Recipes (Multi-Part Components)
|
||||||
|
|
||||||
|
Use slot recipes for components with multiple styled parts (checkbox + label, tooltip + arrow, menu + items).
|
||||||
|
|
||||||
|
### Basic Slot Recipe Structure
|
||||||
|
|
||||||
|
Create: `src/recipes/checkbox.ts`
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { defineSlotRecipe } from '@pandacss/dev'
|
||||||
|
|
||||||
|
export const checkBoxRecipe = defineSlotRecipe({
|
||||||
|
className: 'checkbox',
|
||||||
|
description: 'Checkbox component with label',
|
||||||
|
jsx: ['CheckBox'],
|
||||||
|
|
||||||
|
// Define named slots for component parts
|
||||||
|
slots: ['container', 'input', 'indicator', 'label'],
|
||||||
|
|
||||||
|
// Base styles for each slot
|
||||||
|
base: {
|
||||||
|
container: {
|
||||||
|
display: 'inline-flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: '8',
|
||||||
|
cursor: 'pointer',
|
||||||
|
position: 'relative'
|
||||||
|
},
|
||||||
|
|
||||||
|
input: {
|
||||||
|
// Visually hidden but accessible
|
||||||
|
position: 'absolute',
|
||||||
|
opacity: 0,
|
||||||
|
width: '1px',
|
||||||
|
height: '1px',
|
||||||
|
|
||||||
|
// Show different indicator icons based on state
|
||||||
|
_checked: {
|
||||||
|
"& ~ [data-part='indicator'][data-state='unchecked']": {
|
||||||
|
display: 'none'
|
||||||
|
},
|
||||||
|
"& ~ [data-part='indicator'][data-state='checked']": {
|
||||||
|
display: 'inline-flex'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
_indeterminate: {
|
||||||
|
"& ~ [data-part='indicator'][data-state='indeterminate']": {
|
||||||
|
display: 'inline-flex'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
_disabled: {
|
||||||
|
"& ~ [data-part='indicator']": {
|
||||||
|
opacity: 0.4,
|
||||||
|
cursor: 'not-allowed'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
indicator: {
|
||||||
|
display: 'inline-flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
flexShrink: 0,
|
||||||
|
width: '20',
|
||||||
|
height: '20',
|
||||||
|
borderWidth: '1',
|
||||||
|
borderRadius: '4',
|
||||||
|
borderColor: { base: 'slate.40', _dark: 'slate.60' },
|
||||||
|
bg: { base: 'white', _dark: 'slate.90' },
|
||||||
|
color: { base: 'blue.50', _dark: 'blue.40' }
|
||||||
|
},
|
||||||
|
|
||||||
|
label: {
|
||||||
|
fontSize: 'md',
|
||||||
|
color: { base: 'slate.90', _dark: 'slate.10' },
|
||||||
|
userSelect: 'none'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Variants apply to specific slots
|
||||||
|
variants: {
|
||||||
|
size: {
|
||||||
|
small: {
|
||||||
|
indicator: { width: '16', height: '16' },
|
||||||
|
label: { fontSize: 'sm' }
|
||||||
|
},
|
||||||
|
medium: {
|
||||||
|
indicator: { width: '20', height: '20' },
|
||||||
|
label: { fontSize: 'md' }
|
||||||
|
},
|
||||||
|
large: {
|
||||||
|
indicator: { width: '24', height: '24' },
|
||||||
|
label: { fontSize: 'lg' }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
defaultVariants: {
|
||||||
|
size: 'medium'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
### Complex State Handling
|
||||||
|
|
||||||
|
**Pattern**: Use sibling selectors for state-based styling:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
base: {
|
||||||
|
input: {
|
||||||
|
// When checked, show checked icon, hide unchecked icon
|
||||||
|
_checked: {
|
||||||
|
"& ~ [data-part='indicator']": {
|
||||||
|
bg: { base: 'blue.50', _dark: 'blue.40' },
|
||||||
|
borderColor: { base: 'blue.50', _dark: 'blue.40' },
|
||||||
|
color: 'white'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// When focused, add focus ring to indicator
|
||||||
|
_focusVisible: {
|
||||||
|
"& ~ [data-part='indicator']": {
|
||||||
|
outlineWidth: '2',
|
||||||
|
outlineOffset: '1',
|
||||||
|
outlineColor: { base: 'blue.50', _dark: 'blue.40' }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Error state
|
||||||
|
_invalid: {
|
||||||
|
"& ~ [data-part='indicator']": {
|
||||||
|
borderColor: { base: 'red.50', _dark: 'red.40' }
|
||||||
|
},
|
||||||
|
"& ~ [data-part='label']": {
|
||||||
|
color: { base: 'red.50', _dark: 'red.40' }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Advanced Slot Recipe: Tooltip
|
||||||
|
|
||||||
|
Create: `src/recipes/tooltip.ts`
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { defineSlotRecipe } from '@pandacss/dev'
|
||||||
|
|
||||||
|
export const tooltipRecipe = defineSlotRecipe({
|
||||||
|
className: 'tooltip',
|
||||||
|
jsx: ['Tooltip'],
|
||||||
|
slots: ['trigger', 'content', 'arrow'],
|
||||||
|
|
||||||
|
base: {
|
||||||
|
trigger: {
|
||||||
|
cursor: 'pointer'
|
||||||
|
},
|
||||||
|
|
||||||
|
content: {
|
||||||
|
position: 'absolute',
|
||||||
|
zIndex: 50,
|
||||||
|
px: '12',
|
||||||
|
py: '8',
|
||||||
|
fontSize: 'sm',
|
||||||
|
borderRadius: '6',
|
||||||
|
bg: { base: 'slate.90', _dark: 'slate.10' },
|
||||||
|
color: { base: 'white', _dark: 'slate.90' },
|
||||||
|
boxShadow: 'lg',
|
||||||
|
maxWidth: '320',
|
||||||
|
|
||||||
|
// Fade in/out animation
|
||||||
|
opacity: 0,
|
||||||
|
transitionProperty: 'opacity',
|
||||||
|
transitionDuration: 'fast',
|
||||||
|
_open: {
|
||||||
|
opacity: 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
arrow: {
|
||||||
|
position: 'absolute',
|
||||||
|
width: '8',
|
||||||
|
height: '8',
|
||||||
|
bg: { base: 'slate.90', _dark: 'slate.10' },
|
||||||
|
transform: 'rotate(45deg)'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
variants: {
|
||||||
|
position: {
|
||||||
|
top: {
|
||||||
|
content: { bottom: 'full', left: '50%', transform: 'translateX(-50%)' },
|
||||||
|
arrow: { bottom: '-4', left: '50%', transform: 'translateX(-50%) rotate(45deg)' }
|
||||||
|
},
|
||||||
|
'top-start': {
|
||||||
|
content: { bottom: 'full', left: '0' },
|
||||||
|
arrow: { bottom: '-4', left: '12' }
|
||||||
|
},
|
||||||
|
'top-end': {
|
||||||
|
content: { bottom: 'full', right: '0' },
|
||||||
|
arrow: { bottom: '-4', right: '12' }
|
||||||
|
},
|
||||||
|
bottom: {
|
||||||
|
content: { top: 'full', left: '50%', transform: 'translateX(-50%)' },
|
||||||
|
arrow: { top: '-4', left: '50%', transform: 'translateX(-50%) rotate(45deg)' }
|
||||||
|
},
|
||||||
|
left: {
|
||||||
|
content: { right: 'full', top: '50%', transform: 'translateY(-50%)' },
|
||||||
|
arrow: { right: '-4', top: '50%', transform: 'translateY(-50%) rotate(45deg)' }
|
||||||
|
},
|
||||||
|
right: {
|
||||||
|
content: { left: 'full', top: '50%', transform: 'translateY(-50%)' },
|
||||||
|
arrow: { left: '-4', top: '50%', transform: 'translateY(-50%) rotate(45deg)' }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Option to hide arrow
|
||||||
|
caret: {
|
||||||
|
true: {},
|
||||||
|
false: {
|
||||||
|
arrow: { display: 'none' }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Compound variants: combine multiple variant conditions
|
||||||
|
compoundVariants: [
|
||||||
|
{
|
||||||
|
position: ['top', 'top-start', 'top-end'],
|
||||||
|
caret: true,
|
||||||
|
css: {
|
||||||
|
content: { mb: '12' } // Add margin when arrow is shown on top
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
position: ['bottom', 'bottom-start', 'bottom-end'],
|
||||||
|
caret: true,
|
||||||
|
css: {
|
||||||
|
content: { mt: '12' }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
|
||||||
|
defaultVariants: {
|
||||||
|
position: 'top',
|
||||||
|
caret: true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
## Compound Variants
|
||||||
|
|
||||||
|
Use compound variants when combining multiple variant values requires unique styling.
|
||||||
|
|
||||||
|
**Pattern**: Conditional styling based on variant combinations:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
export const buttonRecipe = defineRecipe({
|
||||||
|
variants: {
|
||||||
|
variant: {
|
||||||
|
primary: { /* ... */ },
|
||||||
|
outline: { /* ... */ }
|
||||||
|
},
|
||||||
|
size: {
|
||||||
|
small: { /* ... */ },
|
||||||
|
large: { /* ... */ }
|
||||||
|
},
|
||||||
|
loading: {
|
||||||
|
true: { cursor: 'wait' }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Special styles when specific variants combine
|
||||||
|
compoundVariants: [
|
||||||
|
{
|
||||||
|
variant: 'primary',
|
||||||
|
loading: true,
|
||||||
|
css: {
|
||||||
|
bg: { base: 'blue.40', _dark: 'blue.30' }, // Lighter when loading
|
||||||
|
_hover: { bg: { base: 'blue.40', _dark: 'blue.30' } } // Disable hover
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
variant: 'outline',
|
||||||
|
size: 'small',
|
||||||
|
css: {
|
||||||
|
borderWidth: '1', // Thinner border for small outline
|
||||||
|
fontWeight: 'medium'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
## Recipe Organization
|
||||||
|
|
||||||
|
### File Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
src/
|
||||||
|
recipes/
|
||||||
|
index.ts # Export all recipes
|
||||||
|
button.ts # Regular recipe
|
||||||
|
input.ts # Regular recipe
|
||||||
|
checkbox.ts # Slot recipe
|
||||||
|
radio.ts # Slot recipe
|
||||||
|
tooltip.ts # Slot recipe
|
||||||
|
menu.ts # Slot recipe
|
||||||
|
```
|
||||||
|
|
||||||
|
### Export Pattern
|
||||||
|
|
||||||
|
`src/recipes/index.ts`:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Regular recipes
|
||||||
|
export { buttonRecipe } from './button'
|
||||||
|
export { iconButtonRecipe } from './button'
|
||||||
|
export { inputRecipe } from './input'
|
||||||
|
export { textRecipe } from './text'
|
||||||
|
|
||||||
|
// Slot recipes
|
||||||
|
export { checkBoxRecipe } from './checkbox'
|
||||||
|
export { radioRecipe } from './radio'
|
||||||
|
export { tooltipRecipe } from './tooltip'
|
||||||
|
export { menuRecipe } from './menu'
|
||||||
|
```
|
||||||
|
|
||||||
|
### Register in Config
|
||||||
|
|
||||||
|
`panda.config.ts`:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { defineConfig } from '@pandacss/dev'
|
||||||
|
import * as allRecipes from './src/recipes'
|
||||||
|
|
||||||
|
// Separate regular and slot recipes
|
||||||
|
const {
|
||||||
|
checkBoxRecipe,
|
||||||
|
radioRecipe,
|
||||||
|
tooltipRecipe,
|
||||||
|
menuRecipe,
|
||||||
|
...regularRecipes
|
||||||
|
} = allRecipes
|
||||||
|
|
||||||
|
// Transform keys: remove 'Recipe' suffix
|
||||||
|
const recipes = Object.fromEntries(
|
||||||
|
Object.entries(regularRecipes).map(([key, value]) => [
|
||||||
|
key.replace(/Recipe$/, ''), // buttonRecipe → button
|
||||||
|
value
|
||||||
|
])
|
||||||
|
)
|
||||||
|
|
||||||
|
const slotRecipes = {
|
||||||
|
checkbox: checkBoxRecipe,
|
||||||
|
radio: radioRecipe,
|
||||||
|
tooltip: tooltipRecipe,
|
||||||
|
menu: menuRecipe
|
||||||
|
}
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
theme: {
|
||||||
|
extend: {
|
||||||
|
recipes,
|
||||||
|
slotRecipes
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
**Why**: Clean separation, automatic recipe registration.
|
||||||
|
|
||||||
|
## Responsive Recipes
|
||||||
|
|
||||||
|
**Pattern**: Use object syntax for responsive variants:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
export const cardRecipe = defineRecipe({
|
||||||
|
base: {
|
||||||
|
p: { base: '16', md: '20', lg: '24' }, // Responsive padding
|
||||||
|
borderRadius: { base: '6', md: '8' },
|
||||||
|
fontSize: { base: 'sm', md: 'md' }
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
**Pattern**: Container queries for component-level responsive:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
export const menuRecipe = defineSlotRecipe({
|
||||||
|
base: {
|
||||||
|
container: {
|
||||||
|
// Change layout based on container size (not viewport)
|
||||||
|
width: { base: 'full', '@container(min-width: 768px)': '260' },
|
||||||
|
position: { base: 'fixed', '@container(min-width: 768px)': 'relative' }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
## Best Practices Checklist
|
||||||
|
|
||||||
|
Create TodoWrite items when creating recipes:
|
||||||
|
|
||||||
|
- [ ] Extract shared base styles for related components
|
||||||
|
- [ ] Use semantic tokens (not raw colors) in recipes
|
||||||
|
- [ ] Define sensible defaultVariants
|
||||||
|
- [ ] Include common state styles (_hover, _focus, _disabled, _active)
|
||||||
|
- [ ] Add _focusVisible for accessibility
|
||||||
|
- [ ] Use slot recipes for multi-part components
|
||||||
|
- [ ] Document recipe purpose in description field
|
||||||
|
- [ ] Use compound variants for complex combinations
|
||||||
|
- [ ] Test all variant combinations
|
||||||
|
- [ ] Validate recipes work in light AND dark themes
|
||||||
|
|
||||||
|
## Common Pitfalls
|
||||||
|
|
||||||
|
### Avoid: Hard-coded Values
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// BAD: Hard-coded hex colors, px values
|
||||||
|
variants: {
|
||||||
|
primary: {
|
||||||
|
bg: '#3B82F6',
|
||||||
|
padding: '12px'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GOOD: Use design tokens
|
||||||
|
variants: {
|
||||||
|
primary: {
|
||||||
|
bg: { base: 'blue.50', _dark: 'blue.40' },
|
||||||
|
padding: '12'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Avoid: Missing Default Variants
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// BAD: No defaults, components render unstyled
|
||||||
|
variants: {
|
||||||
|
size: { small: {...}, large: {...} }
|
||||||
|
}
|
||||||
|
|
||||||
|
// GOOD: Always provide defaults
|
||||||
|
variants: {
|
||||||
|
size: { small: {...}, medium: {...}, large: {...} }
|
||||||
|
},
|
||||||
|
defaultVariants: {
|
||||||
|
size: 'medium'
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Avoid: Over-nesting in Slot Recipes
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// BAD: Deep nesting, hard to maintain
|
||||||
|
base: {
|
||||||
|
container: {
|
||||||
|
'& > div > span > button': { /* ... */ }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GOOD: Use slots for structure
|
||||||
|
slots: ['container', 'wrapper', 'label', 'button'],
|
||||||
|
base: {
|
||||||
|
container: { /* ... */ },
|
||||||
|
button: { /* ... */ }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Avoid: Duplicate Logic Across Recipes
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// BAD: Copy-paste same styles
|
||||||
|
const button = { _disabled: { opacity: 0.4 } }
|
||||||
|
const input = { _disabled: { opacity: 0.4 } }
|
||||||
|
|
||||||
|
// GOOD: Extract to shared constant
|
||||||
|
const disabledStyles = { opacity: 0.4, cursor: 'not-allowed' }
|
||||||
|
|
||||||
|
const button = { _disabled: disabledStyles }
|
||||||
|
const input = { _disabled: disabledStyles }
|
||||||
|
```
|
||||||
|
|
||||||
|
## Accessing Official Panda CSS Docs
|
||||||
|
|
||||||
|
For recipe-specific documentation:
|
||||||
|
|
||||||
|
1. **Resolve library ID**: `mcp__MCP_DOCKER__resolve-library-id` with `libraryName: "panda-css"`
|
||||||
|
2. **Fetch docs**: `mcp__MCP_DOCKER__get-library-docs` with `topic: "recipes"` or `topic: "slot-recipes"`
|
||||||
|
|
||||||
|
## Next Steps
|
||||||
|
|
||||||
|
After creating recipes:
|
||||||
|
|
||||||
|
1. **Implement in components**: Use **panda-component-impl** skill for React integration
|
||||||
|
2. **Test variants**: Verify all combinations work visually
|
||||||
|
3. **Document in Storybook**: Create stories showing all variants
|
||||||
|
|
||||||
|
## Reference Files from Best Practices Repo
|
||||||
|
|
||||||
|
- Regular recipe: `src/recipes/button.ts`
|
||||||
|
- Slot recipe: `src/recipes/checkbox.ts`, `src/recipes/tooltip.ts`, `src/recipes/menu.ts`
|
||||||
|
- Shared base: `src/recipes/button.ts` (buttonBase constant)
|
||||||
|
- Dynamic variants: `src/recipes/text.ts` (fontSize generation)
|
||||||
|
- Config registration: `panda.config.ts`, `cetec-preset.ts`
|
||||||
533
skills/panda-review-component.md
Normal file
533
skills/panda-review-component.md
Normal file
@@ -0,0 +1,533 @@
|
|||||||
|
---
|
||||||
|
name: panda-review-component
|
||||||
|
description: Systematically assess an existing component against Panda CSS best practices, create prioritized recommendations, and implement approved changes
|
||||||
|
---
|
||||||
|
|
||||||
|
# Panda CSS Component Review
|
||||||
|
|
||||||
|
## When to Use This Skill
|
||||||
|
|
||||||
|
Use this skill when:
|
||||||
|
- Reviewing an existing component for Panda CSS best practices compliance
|
||||||
|
- Auditing components before production deployment
|
||||||
|
- Refactoring components to use Panda CSS patterns
|
||||||
|
- Onboarding legacy components to a Panda CSS design system
|
||||||
|
- Identifying technical debt in component styling
|
||||||
|
- Learning from existing implementations to improve other components
|
||||||
|
|
||||||
|
**Important**: This skill creates recommendations and presents them for approval BEFORE making any changes.
|
||||||
|
|
||||||
|
## Review Process Overview
|
||||||
|
|
||||||
|
The review follows a systematic four-phase approach:
|
||||||
|
|
||||||
|
1. **Discovery Phase**: Gather component code and context
|
||||||
|
2. **Assessment Phase**: Evaluate against best practices using comprehensive checklist
|
||||||
|
3. **Recommendation Phase**: Generate prioritized improvement list with explanations
|
||||||
|
4. **Implementation Phase**: Execute approved changes (only after user confirmation)
|
||||||
|
|
||||||
|
## Phase 1: Discovery - Gather Component Context
|
||||||
|
|
||||||
|
### Step 1: Identify Component Files
|
||||||
|
|
||||||
|
Create TodoWrite items for discovery:
|
||||||
|
- [ ] Locate component implementation file(s)
|
||||||
|
- [ ] Find associated recipe/pattern definitions
|
||||||
|
- [ ] Check for TypeScript type definitions
|
||||||
|
- [ ] Identify any test files or stories
|
||||||
|
- [ ] Review import statements and dependencies
|
||||||
|
|
||||||
|
Ask the user:
|
||||||
|
- Component name or file path
|
||||||
|
- Is this a standalone component or part of a component library?
|
||||||
|
- Are there related components that should be reviewed together?
|
||||||
|
|
||||||
|
### Step 2: Read Component Code
|
||||||
|
|
||||||
|
Use Read tool to examine:
|
||||||
|
- Component implementation (`.tsx`, `.jsx`)
|
||||||
|
- Recipe definitions (if separate file)
|
||||||
|
- Exported types and interfaces
|
||||||
|
- Related utilities (splitProps, theme context, etc.)
|
||||||
|
|
||||||
|
### Step 3: Access Official Panda CSS Documentation
|
||||||
|
|
||||||
|
For accurate assessment, reference latest Panda CSS best practices:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// 1. Resolve Panda CSS library ID
|
||||||
|
mcp__MCP_DOCKER__resolve-library-id({ libraryName: "panda-css" })
|
||||||
|
|
||||||
|
// 2. Fetch relevant documentation
|
||||||
|
mcp__MCP_DOCKER__get-library-docs({
|
||||||
|
context7CompatibleLibraryID: "/cschroeter/park-ui", // or resolved ID
|
||||||
|
topic: "recipes" // or "patterns", "typescript", "styling"
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
**Topics to reference**:
|
||||||
|
- `recipes` - Recipe patterns and slot recipes
|
||||||
|
- `patterns` - Built-in patterns usage
|
||||||
|
- `typescript` - Type integration
|
||||||
|
- `styling` - Style prop patterns
|
||||||
|
|
||||||
|
## Phase 2: Assessment - Comprehensive Checklist
|
||||||
|
|
||||||
|
Create TodoWrite items for each assessment category:
|
||||||
|
|
||||||
|
### Architecture & Structure Assessment
|
||||||
|
- [ ] **Component file organization**: Is the component in its own directory with proper exports?
|
||||||
|
- [ ] **Import structure**: Are imports from correct Panda CSS packages?
|
||||||
|
- [ ] **Base component usage**: Does it use Box as foundation for polymorphic behavior?
|
||||||
|
- [ ] **Recipe/pattern usage**: Are recipes/patterns properly imported and applied?
|
||||||
|
- [ ] **File separation**: Are recipes defined in proper location (separate file in `src/recipes/` or inline)?
|
||||||
|
|
||||||
|
### TypeScript Integration Assessment
|
||||||
|
- [ ] **Recipe types**: Are generated variant types imported and used (e.g., `ButtonVariantProps`)?
|
||||||
|
- [ ] **Prop type composition**: Are types properly composed (BoxProps + VariantProps)?
|
||||||
|
- [ ] **Prop conflicts**: Are conflicting props properly omitted using `Omit`?
|
||||||
|
- [ ] **Conditional types**: Are `ConditionalValue` and token types used for responsive/theme-aware props?
|
||||||
|
- [ ] **Type exports**: Are prop types exported for consuming code?
|
||||||
|
|
||||||
|
### Styling Pattern Assessment
|
||||||
|
- [ ] **Recipe application**: Is the recipe correctly applied with variant props?
|
||||||
|
- [ ] **Slot recipes**: For multi-part components, are all slots properly destructured and applied?
|
||||||
|
- [ ] **className merging**: Is `cx()` used to merge recipe + custom classes?
|
||||||
|
- [ ] **splitProps usage**: Is `splitProps` utility used to separate CSS from HTML props?
|
||||||
|
- [ ] **Style prop support**: Does component accept Panda CSS style props (bg, px, etc.)?
|
||||||
|
- [ ] **Theme awareness**: Are colors/tokens using theme-aware values with `_dark` conditions?
|
||||||
|
- [ ] **No style mixing**: Avoid mixing inline styles, external CSS, and Panda CSS?
|
||||||
|
|
||||||
|
### State & Interaction Assessment
|
||||||
|
- [ ] **Pseudo-states**: Are hover, active, focus states defined in recipe?
|
||||||
|
- [ ] **Focus visibility**: Is `_focusVisible` used for keyboard navigation?
|
||||||
|
- [ ] **Disabled state**: Is `_disabled` properly styled and handled?
|
||||||
|
- [ ] **Loading state**: If applicable, is loading state visually indicated?
|
||||||
|
- [ ] **Data attributes**: Are custom states using data attributes (e.g., `data-indeterminate`)?
|
||||||
|
- [ ] **Condition matching**: Do conditions match multiple state selectors (native + custom)?
|
||||||
|
|
||||||
|
### Accessibility Assessment
|
||||||
|
- [ ] **Semantic HTML**: Is the correct HTML element used (button, input, etc.)?
|
||||||
|
- [ ] **ARIA attributes**: Are proper ARIA attributes included (aria-label, aria-busy, etc.)?
|
||||||
|
- [ ] **Keyboard interaction**: Can the component be used with keyboard only?
|
||||||
|
- [ ] **Focus management**: Is focus properly managed in interactive components?
|
||||||
|
- [ ] **Screen reader support**: Will screen readers announce the component correctly?
|
||||||
|
- [ ] **Color contrast**: Do color combinations meet WCAG contrast requirements?
|
||||||
|
|
||||||
|
### Responsive & Adaptive Assessment
|
||||||
|
- [ ] **Responsive props**: Are responsive values using object syntax correctly?
|
||||||
|
- [ ] **Breakpoint usage**: Are breakpoints from theme tokens used consistently?
|
||||||
|
- [ ] **Container queries**: If needed, are container queries implemented properly?
|
||||||
|
- [ ] **Mobile-first**: Are base styles mobile-first with progressive enhancement?
|
||||||
|
|
||||||
|
### Token & Design System Assessment
|
||||||
|
- [ ] **Semantic tokens**: Are semantic tokens used instead of primitive values?
|
||||||
|
- [ ] **Spacing tokens**: Is spacing using token values (not arbitrary px values)?
|
||||||
|
- [ ] **Color tokens**: Are colors from token system (not hardcoded hex/rgb)?
|
||||||
|
- [ ] **Typography tokens**: Are font sizes, weights, line heights from tokens?
|
||||||
|
- [ ] **Animation tokens**: Are transitions using token durations and easings?
|
||||||
|
|
||||||
|
### Performance & Best Practices Assessment
|
||||||
|
- [ ] **Recipe optimization**: Are compound variants used efficiently?
|
||||||
|
- [ ] **Unnecessary wrapping**: Is there unnecessary Box wrapping?
|
||||||
|
- [ ] **Conditional rendering**: Is conditional rendering implemented efficiently?
|
||||||
|
- [ ] **Prop spreading**: Is prop spreading used appropriately (not spreading into recipe)?
|
||||||
|
- [ ] **Recipe size**: Is recipe definition reasonably sized (not overly complex)?
|
||||||
|
|
||||||
|
### Documentation & Developer Experience Assessment
|
||||||
|
- [ ] **Component exports**: Are component and types properly exported?
|
||||||
|
- [ ] **Prop documentation**: Are props clear and self-documenting?
|
||||||
|
- [ ] **Usage examples**: Are there examples or stories showing usage?
|
||||||
|
- [ ] **Default props**: Are sensible defaults provided?
|
||||||
|
|
||||||
|
## Phase 3: Recommendations - Generate Prioritized List
|
||||||
|
|
||||||
|
After completing the assessment, generate a structured recommendation report.
|
||||||
|
|
||||||
|
### Priority Levels
|
||||||
|
|
||||||
|
**P0 (Critical)**: Blocks functionality, security issues, major accessibility violations
|
||||||
|
- Examples: Missing keyboard support, broken TypeScript types, non-functional styles
|
||||||
|
|
||||||
|
**P1 (High)**: Significant best practice violations, maintainability issues
|
||||||
|
- Examples: Not using recipe types, missing theme awareness, no splitProps usage
|
||||||
|
|
||||||
|
**P2 (Medium)**: Optimization opportunities, minor best practice gaps
|
||||||
|
- Examples: Inefficient compound variants, missing responsive props, token misuse
|
||||||
|
|
||||||
|
**P3 (Low)**: Nice-to-haves, polish items, documentation improvements
|
||||||
|
- Examples: Component documentation, additional variants, example refinements
|
||||||
|
|
||||||
|
### Recommendation Format
|
||||||
|
|
||||||
|
For each recommendation, provide:
|
||||||
|
|
||||||
|
1. **Priority Level**: P0, P1, P2, or P3
|
||||||
|
2. **Category**: Architecture, TypeScript, Styling, Accessibility, etc.
|
||||||
|
3. **Issue Description**: What's wrong or missing
|
||||||
|
4. **Impact**: Why it matters (performance, maintainability, accessibility, etc.)
|
||||||
|
5. **Solution**: Specific implementation approach
|
||||||
|
6. **Code Example**: Show before/after if applicable
|
||||||
|
7. **Effort Estimate**: Small (< 15 min), Medium (15-45 min), Large (> 45 min)
|
||||||
|
|
||||||
|
### Example Recommendation
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
**P1 - TypeScript Integration**
|
||||||
|
|
||||||
|
**Issue**: Component not using generated recipe variant types
|
||||||
|
|
||||||
|
**Impact**:
|
||||||
|
- TypeScript types can drift out of sync with recipe definition
|
||||||
|
- No autocomplete for variant values
|
||||||
|
- Manual maintenance of prop types
|
||||||
|
|
||||||
|
**Solution**: Import and use `ButtonVariantProps` from generated recipe types
|
||||||
|
|
||||||
|
**Before**:
|
||||||
|
```typescript
|
||||||
|
type ButtonProps = {
|
||||||
|
variant?: 'primary' | 'secondary' | 'outline'
|
||||||
|
size?: 'small' | 'medium' | 'large'
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**After**:
|
||||||
|
```typescript
|
||||||
|
import { type ButtonVariantProps } from '@styled-system/recipes'
|
||||||
|
|
||||||
|
type ButtonProps = BoxProps & ButtonVariantProps & {
|
||||||
|
loading?: boolean
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Effort**: Small (5 minutes)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Phase 4: Present Recommendations for Approval
|
||||||
|
|
||||||
|
### Create Summary Report
|
||||||
|
|
||||||
|
Before showing recommendations, create a summary:
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
# Component Review: [ComponentName]
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
- **File**: path/to/Component.tsx
|
||||||
|
- **Type**: [Recipe-based | Pattern-based | Inline CSS]
|
||||||
|
- **Issues Found**: [Total count]
|
||||||
|
- **Critical (P0)**: [count]
|
||||||
|
- **High (P1)**: [count]
|
||||||
|
- **Medium (P2)**: [count]
|
||||||
|
- **Low (P3)**: [count]
|
||||||
|
|
||||||
|
## Overall Assessment
|
||||||
|
[Brief 2-3 sentence assessment of component health]
|
||||||
|
|
||||||
|
## Strengths
|
||||||
|
- [What the component does well]
|
||||||
|
- [Existing best practices being followed]
|
||||||
|
|
||||||
|
## Areas for Improvement
|
||||||
|
[High-level summary of main issues]
|
||||||
|
```
|
||||||
|
|
||||||
|
### Present Recommendations
|
||||||
|
|
||||||
|
Display recommendations grouped by priority:
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
## Recommendations
|
||||||
|
|
||||||
|
### Critical Priority (P0)
|
||||||
|
[List P0 items with full detail as shown above]
|
||||||
|
|
||||||
|
### High Priority (P1)
|
||||||
|
[List P1 items with full detail]
|
||||||
|
|
||||||
|
### Medium Priority (P2)
|
||||||
|
[List P2 items with full detail]
|
||||||
|
|
||||||
|
### Low Priority (P3)
|
||||||
|
[List P3 items with full detail]
|
||||||
|
```
|
||||||
|
|
||||||
|
### Request User Approval
|
||||||
|
|
||||||
|
After presenting recommendations, explicitly ask:
|
||||||
|
|
||||||
|
**"Which recommendations would you like me to implement?"**
|
||||||
|
|
||||||
|
Options:
|
||||||
|
- "All recommendations" - Implement everything
|
||||||
|
- "Only P0 and P1" - Focus on critical and high priority
|
||||||
|
- "Let me select specific ones" - User chooses from list
|
||||||
|
- "None, I just wanted the review" - Stop here
|
||||||
|
|
||||||
|
**DO NOT proceed with implementation until user responds.**
|
||||||
|
|
||||||
|
## Phase 5: Implementation - Execute Approved Changes
|
||||||
|
|
||||||
|
Only after user approval, proceed with changes.
|
||||||
|
|
||||||
|
### Create Implementation TodoWrite Items
|
||||||
|
|
||||||
|
For each approved recommendation, create a specific todo:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
TodoWrite({
|
||||||
|
todos: [
|
||||||
|
{ content: "Update Button types to use ButtonVariantProps", status: "pending", activeForm: "Updating Button types to use ButtonVariantProps" },
|
||||||
|
{ content: "Add splitProps utility to Button component", status: "pending", activeForm: "Adding splitProps utility to Button component" },
|
||||||
|
{ content: "Implement _focusVisible styles in button recipe", status: "pending", activeForm: "Implementing _focusVisible styles in button recipe" },
|
||||||
|
// ... more items
|
||||||
|
]
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
### Implementation Guidelines
|
||||||
|
|
||||||
|
1. **Make one change at a time**: Mark todo as in_progress, implement, mark completed
|
||||||
|
2. **Test after each change**: Verify component still works
|
||||||
|
3. **Group related changes**: When multiple changes affect same code block, batch them
|
||||||
|
4. **Preserve functionality**: Don't change behavior, only improve implementation
|
||||||
|
5. **Follow existing patterns**: Match the code style and patterns in the codebase
|
||||||
|
|
||||||
|
### Post-Implementation Verification
|
||||||
|
|
||||||
|
After implementing changes, create verification todos:
|
||||||
|
|
||||||
|
- [ ] Component renders correctly
|
||||||
|
- [ ] All variants still work
|
||||||
|
- [ ] TypeScript types are correct
|
||||||
|
- [ ] No console errors or warnings
|
||||||
|
- [ ] Accessibility features work
|
||||||
|
- [ ] Responsive behavior intact
|
||||||
|
|
||||||
|
## Using This Skill - Quick Reference
|
||||||
|
|
||||||
|
### Typical Workflow
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# User requests review
|
||||||
|
"Review the Button component for best practices"
|
||||||
|
|
||||||
|
# 1. Discovery
|
||||||
|
- Read component file(s)
|
||||||
|
- Identify recipe/pattern usage
|
||||||
|
- Note dependencies and utilities
|
||||||
|
|
||||||
|
# 2. Assessment
|
||||||
|
- Create TodoWrite items for each assessment category
|
||||||
|
- Systematically check each item
|
||||||
|
- Reference Panda CSS docs via MCP as needed
|
||||||
|
- Document findings
|
||||||
|
|
||||||
|
# 3. Generate Recommendations
|
||||||
|
- Prioritize issues (P0 > P1 > P2 > P3)
|
||||||
|
- Format each recommendation with detail
|
||||||
|
- Estimate effort for each
|
||||||
|
|
||||||
|
# 4. Present for Approval
|
||||||
|
- Show summary report
|
||||||
|
- Display recommendations by priority
|
||||||
|
- Ask user which to implement
|
||||||
|
- WAIT for response
|
||||||
|
|
||||||
|
# 5. Implement (only after approval)
|
||||||
|
- Create implementation todos
|
||||||
|
- Execute changes one by one
|
||||||
|
- Verify after each change
|
||||||
|
- Mark todos complete
|
||||||
|
```
|
||||||
|
|
||||||
|
## Accessing Official Panda CSS Documentation
|
||||||
|
|
||||||
|
Throughout the review process, reference official docs for accuracy:
|
||||||
|
|
||||||
|
### Recipe Patterns
|
||||||
|
```typescript
|
||||||
|
mcp__MCP_DOCKER__get-library-docs({
|
||||||
|
context7CompatibleLibraryID: "/cschroeter/park-ui",
|
||||||
|
topic: "recipes"
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
### TypeScript Integration
|
||||||
|
```typescript
|
||||||
|
mcp__MCP_DOCKER__get-library-docs({
|
||||||
|
context7CompatibleLibraryID: "/cschroeter/park-ui",
|
||||||
|
topic: "typescript"
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
### Styling Best Practices
|
||||||
|
```typescript
|
||||||
|
mcp__MCP_DOCKER__get-library-docs({
|
||||||
|
context7CompatibleLibraryID: "/cschroeter/park-ui",
|
||||||
|
topic: "styling"
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
## Common Review Findings
|
||||||
|
|
||||||
|
### Most Common Issues (seen in practice)
|
||||||
|
|
||||||
|
1. **Not using generated recipe types** (P1)
|
||||||
|
- Fix: Import and use `*VariantProps` types
|
||||||
|
|
||||||
|
2. **Missing splitProps utility** (P1)
|
||||||
|
- Fix: Add splitProps to separate CSS from HTML props
|
||||||
|
|
||||||
|
3. **No _focusVisible styles** (P0 for interactive components)
|
||||||
|
- Fix: Add focus styles in recipe
|
||||||
|
|
||||||
|
4. **Hardcoded colors instead of tokens** (P2)
|
||||||
|
- Fix: Replace with semantic token references
|
||||||
|
|
||||||
|
5. **Missing ARIA attributes** (P0 for complex components)
|
||||||
|
- Fix: Add appropriate aria-* attributes
|
||||||
|
|
||||||
|
6. **Not using Box as foundation** (P1)
|
||||||
|
- Fix: Refactor to use Box with `as` prop
|
||||||
|
|
||||||
|
7. **Mixing CSS approaches** (P1)
|
||||||
|
- Fix: Remove inline styles and external CSS, use only Panda
|
||||||
|
|
||||||
|
8. **No theme awareness (_dark conditions)** (P2)
|
||||||
|
- Fix: Add _dark variants for all colors
|
||||||
|
|
||||||
|
## Best Practices for Conducting Reviews
|
||||||
|
|
||||||
|
### DO:
|
||||||
|
- Be thorough but not pedantic
|
||||||
|
- Prioritize issues that impact users (accessibility, functionality)
|
||||||
|
- Provide clear, actionable recommendations
|
||||||
|
- Show code examples for complex changes
|
||||||
|
- Reference official docs for validation
|
||||||
|
- Acknowledge what's already done well
|
||||||
|
- Estimate effort realistically
|
||||||
|
- Wait for user approval before changing code
|
||||||
|
|
||||||
|
### DON'T:
|
||||||
|
- Make changes without approval
|
||||||
|
- Recommend changes without explaining why
|
||||||
|
- Suggest subjective style preferences as "best practices"
|
||||||
|
- Overwhelm with low-priority nitpicks
|
||||||
|
- Skip the assessment checklist
|
||||||
|
- Forget to create TodoWrite items for tracking
|
||||||
|
- Implement everything as one large change
|
||||||
|
- Sacrifice readability for minor optimizations
|
||||||
|
|
||||||
|
## Example Review Session
|
||||||
|
|
||||||
|
### User Request
|
||||||
|
```
|
||||||
|
"Review my Button component at src/components/Button/Button.tsx"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Your Response
|
||||||
|
```
|
||||||
|
I'll systematically review your Button component against Panda CSS best practices. Let me start by reading the component and gathering context.
|
||||||
|
|
||||||
|
[Use Read tool to examine component]
|
||||||
|
[Create discovery TodoWrite items]
|
||||||
|
[Read recipe if separate]
|
||||||
|
[Access Panda CSS docs for recipe patterns]
|
||||||
|
|
||||||
|
[Complete assessment checklist with TodoWrite items]
|
||||||
|
[Document findings in each category]
|
||||||
|
|
||||||
|
# Component Review: Button
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
- **File**: src/components/Button/Button.tsx
|
||||||
|
- **Type**: Recipe-based component
|
||||||
|
- **Issues Found**: 5
|
||||||
|
- **Critical (P0)**: 1
|
||||||
|
- **High (P1)**: 2
|
||||||
|
- **Medium (P2)**: 2
|
||||||
|
- **Low (P3)**: 0
|
||||||
|
|
||||||
|
## Overall Assessment
|
||||||
|
The Button component follows basic Panda CSS patterns but has several opportunities for improvement, particularly around TypeScript integration and prop handling.
|
||||||
|
|
||||||
|
## Strengths
|
||||||
|
- Uses recipe for variant management
|
||||||
|
- Implements hover and active states
|
||||||
|
- Has clear variant names
|
||||||
|
|
||||||
|
## Areas for Improvement
|
||||||
|
- Missing focus visible styles (accessibility concern)
|
||||||
|
- Not using generated recipe types
|
||||||
|
- No splitProps utility for prop separation
|
||||||
|
|
||||||
|
## Recommendations
|
||||||
|
|
||||||
|
### Critical Priority (P0)
|
||||||
|
|
||||||
|
**P0 - Accessibility: Missing focus visible styles**
|
||||||
|
[Full recommendation detail...]
|
||||||
|
|
||||||
|
### High Priority (P1)
|
||||||
|
|
||||||
|
**P1 - TypeScript: Not using generated recipe types**
|
||||||
|
[Full recommendation detail...]
|
||||||
|
|
||||||
|
**P1 - Architecture: Missing splitProps utility**
|
||||||
|
[Full recommendation detail...]
|
||||||
|
|
||||||
|
[... more recommendations ...]
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Which recommendations would you like me to implement?
|
||||||
|
- "All recommendations" - Implement everything
|
||||||
|
- "Only P0 and P1" - Focus on critical and high priority
|
||||||
|
- "Let me select specific ones" - Choose from list
|
||||||
|
- "None, I just wanted the review" - Stop here
|
||||||
|
```
|
||||||
|
|
||||||
|
### After User Approves
|
||||||
|
```
|
||||||
|
Great! I'll implement the P0 and P1 recommendations. Let me create a plan:
|
||||||
|
|
||||||
|
[Create implementation TodoWrite items]
|
||||||
|
|
||||||
|
Now I'll start with the critical accessibility fix...
|
||||||
|
[Implement changes one by one, marking todos complete]
|
||||||
|
```
|
||||||
|
|
||||||
|
## Integration with Other Skills
|
||||||
|
|
||||||
|
This review skill works well with:
|
||||||
|
|
||||||
|
- **panda-component-impl**: Reference for proper component patterns
|
||||||
|
- **panda-recipe-patterns**: Reference for recipe best practices
|
||||||
|
- **panda-token-architecture**: Reference for token usage
|
||||||
|
- **panda-setup-config**: Reference for configuration patterns
|
||||||
|
|
||||||
|
When recommendations involve significant refactoring, consider suggesting:
|
||||||
|
- Creating a new component following best practices
|
||||||
|
- Incremental migration approach
|
||||||
|
- Using the panda-architect agent for complex architectural changes
|
||||||
|
|
||||||
|
## Skill Usage Checklist
|
||||||
|
|
||||||
|
When using this skill, ensure you:
|
||||||
|
|
||||||
|
- [ ] Create TodoWrite items for discovery phase
|
||||||
|
- [ ] Read all relevant component files
|
||||||
|
- [ ] Reference official Panda CSS documentation via MCP
|
||||||
|
- [ ] Create TodoWrite items for assessment checklist
|
||||||
|
- [ ] Complete all assessment categories systematically
|
||||||
|
- [ ] Prioritize findings (P0, P1, P2, P3)
|
||||||
|
- [ ] Format recommendations with full detail
|
||||||
|
- [ ] Present summary report before recommendations
|
||||||
|
- [ ] Explicitly wait for user approval
|
||||||
|
- [ ] DO NOT implement anything without approval
|
||||||
|
- [ ] Create implementation TodoWrite items after approval
|
||||||
|
- [ ] Make changes incrementally
|
||||||
|
- [ ] Mark todos complete as you go
|
||||||
|
- [ ] Verify functionality after implementation
|
||||||
431
skills/panda-setup-config.md
Normal file
431
skills/panda-setup-config.md
Normal file
@@ -0,0 +1,431 @@
|
|||||||
|
---
|
||||||
|
name: panda-setup-config
|
||||||
|
description: Guide Panda CSS initial setup, configuration, preset architecture, and build integration for new projects
|
||||||
|
---
|
||||||
|
|
||||||
|
# Panda CSS Setup & Configuration
|
||||||
|
|
||||||
|
## When to Use This Skill
|
||||||
|
|
||||||
|
Use this skill when:
|
||||||
|
- Starting a new Panda CSS project from scratch
|
||||||
|
- Integrating Panda CSS into an existing React + Vite project
|
||||||
|
- Creating a reusable Panda CSS preset for multiple projects
|
||||||
|
- Configuring build tools and import aliases
|
||||||
|
- Setting up the foundation for a design system
|
||||||
|
|
||||||
|
For complex multi-project architecture or refactoring large codebases, use the **panda-architect** agent instead.
|
||||||
|
|
||||||
|
## Prerequisites Check
|
||||||
|
|
||||||
|
Before starting, verify:
|
||||||
|
- [ ] Project uses React 18+ and Vite 4+
|
||||||
|
- [ ] TypeScript is configured (recommended but not required)
|
||||||
|
- [ ] You understand the project's design token needs
|
||||||
|
|
||||||
|
## Installation Checklist
|
||||||
|
|
||||||
|
Create TodoWrite items for each step:
|
||||||
|
|
||||||
|
### 1. Install Panda CSS
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm install -D @pandacss/dev
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Initialize Panda CSS
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npx panda init
|
||||||
|
```
|
||||||
|
|
||||||
|
This creates:
|
||||||
|
- `panda.config.ts` - Main configuration file
|
||||||
|
- `styled-system/` - Generated CSS and utilities (add to .gitignore)
|
||||||
|
|
||||||
|
### 3. Configure panda.config.ts
|
||||||
|
|
||||||
|
**Critical Settings:**
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { defineConfig } from '@pandacss/dev'
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
// STRICT MODE: Prevents hard-coded values (enforce tokens-only)
|
||||||
|
strictTokens: true,
|
||||||
|
strictPropertyValues: true,
|
||||||
|
|
||||||
|
// Source files to scan for Panda CSS usage
|
||||||
|
include: [
|
||||||
|
'./src/**/*.{js,jsx,ts,tsx}',
|
||||||
|
'./pages/**/*.{js,jsx,ts,tsx}'
|
||||||
|
],
|
||||||
|
|
||||||
|
// Files to ignore
|
||||||
|
exclude: [],
|
||||||
|
|
||||||
|
// React integration
|
||||||
|
jsxFramework: 'react',
|
||||||
|
jsxStyleProps: 'all', // Enable style props on all components
|
||||||
|
|
||||||
|
// Generated code location
|
||||||
|
outdir: 'styled-system',
|
||||||
|
|
||||||
|
// Optional: Namespace your CSS classes
|
||||||
|
prefix: 'app',
|
||||||
|
|
||||||
|
// Optional: Custom import path
|
||||||
|
importMap: '@styled-system',
|
||||||
|
|
||||||
|
// Theme configuration
|
||||||
|
theme: {
|
||||||
|
extend: {
|
||||||
|
// tokens, recipes, etc. go here
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
**Key Decision Points:**
|
||||||
|
|
||||||
|
- **strictTokens**: Set to `true` to enforce design system consistency
|
||||||
|
- **prefix**: Use for avoiding CSS class conflicts (especially in design systems)
|
||||||
|
- **importMap**: Customize if you want cleaner imports (e.g., `@styled-system` instead of `../styled-system`)
|
||||||
|
|
||||||
|
### 4. Add Import Alias (Vite)
|
||||||
|
|
||||||
|
Update `vite.config.ts`:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { defineConfig } from 'vite'
|
||||||
|
import react from '@vitejs/plugin-react'
|
||||||
|
import path from 'path'
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [react()],
|
||||||
|
resolve: {
|
||||||
|
alias: {
|
||||||
|
'@styled-system': path.resolve(__dirname, './styled-system'),
|
||||||
|
'~': path.resolve(__dirname, './src') // Optional: src alias
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
Update `tsconfig.json`:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"paths": {
|
||||||
|
"@styled-system/*": ["./styled-system/*"],
|
||||||
|
"~/*": ["./src/*"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5. Add Build Scripts
|
||||||
|
|
||||||
|
Update `package.json`:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"scripts": {
|
||||||
|
"prepare": "panda codegen",
|
||||||
|
"dev": "panda --watch & vite",
|
||||||
|
"build": "panda codegen && vite build",
|
||||||
|
"lint": "eslint ."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Pattern**: Always run `panda codegen` before builds and in watch mode during dev.
|
||||||
|
|
||||||
|
### 6. Import Global Styles
|
||||||
|
|
||||||
|
In your app entry point (e.g., `src/main.tsx` or `src/App.tsx`):
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import '@styled-system/styles.css'
|
||||||
|
```
|
||||||
|
|
||||||
|
This imports the generated CSS including:
|
||||||
|
- CSS reset
|
||||||
|
- Design tokens as CSS variables
|
||||||
|
- Utility classes
|
||||||
|
- Recipe styles
|
||||||
|
|
||||||
|
### 7. Add styled-system to .gitignore
|
||||||
|
|
||||||
|
```
|
||||||
|
# Panda CSS generated files
|
||||||
|
styled-system/
|
||||||
|
```
|
||||||
|
|
||||||
|
**Why**: Generated code should not be committed (similar to node_modules).
|
||||||
|
|
||||||
|
## Preset Architecture (For Design Systems)
|
||||||
|
|
||||||
|
If you're building a **reusable design system**, create a separate preset file:
|
||||||
|
|
||||||
|
### Create a Preset File
|
||||||
|
|
||||||
|
**File**: `panda-preset.ts`
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { definePreset } from '@pandacss/dev'
|
||||||
|
import type { Preset } from '@pandacss/types'
|
||||||
|
|
||||||
|
const customPreset: Preset = definePreset({
|
||||||
|
theme: {
|
||||||
|
extend: {
|
||||||
|
tokens: {
|
||||||
|
// Your design tokens
|
||||||
|
},
|
||||||
|
semanticTokens: {
|
||||||
|
// Theme-aware tokens
|
||||||
|
},
|
||||||
|
recipes: {
|
||||||
|
// Component recipes
|
||||||
|
},
|
||||||
|
slotRecipes: {
|
||||||
|
// Multi-part component recipes
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
conditions: {
|
||||||
|
// Custom conditions (pseudo-classes, states, etc.)
|
||||||
|
},
|
||||||
|
patterns: {
|
||||||
|
// Custom patterns
|
||||||
|
},
|
||||||
|
utilities: {
|
||||||
|
// Custom utility functions
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
export default customPreset
|
||||||
|
```
|
||||||
|
|
||||||
|
### Use the Preset
|
||||||
|
|
||||||
|
In consuming projects' `panda.config.ts`:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { defineConfig } from '@pandacss/dev'
|
||||||
|
import customPreset from './panda-preset'
|
||||||
|
// Or: import customPreset from 'your-design-system/preset'
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
presets: [
|
||||||
|
'@pandacss/preset-base', // Always include base preset
|
||||||
|
customPreset
|
||||||
|
],
|
||||||
|
|
||||||
|
// Project-specific config
|
||||||
|
include: ['./src/**/*.{ts,tsx}'],
|
||||||
|
strictTokens: true,
|
||||||
|
|
||||||
|
// Optionally override preset values
|
||||||
|
theme: {
|
||||||
|
extend: {
|
||||||
|
// Project-specific tokens
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
**Pattern**: Preset provides defaults, consuming projects can extend/override.
|
||||||
|
|
||||||
|
## Configuration Best Practices
|
||||||
|
|
||||||
|
### 1. Separate Concerns
|
||||||
|
|
||||||
|
```
|
||||||
|
src/
|
||||||
|
styles/
|
||||||
|
tokens.ts # Base design tokens
|
||||||
|
semanticTokens.ts # Theme-aware tokens
|
||||||
|
conditions.ts # Custom conditions
|
||||||
|
globalStyles.ts # Global CSS
|
||||||
|
recipes/
|
||||||
|
index.ts # Export all recipes
|
||||||
|
button.ts # Individual recipe files
|
||||||
|
input.ts
|
||||||
|
patterns/
|
||||||
|
index.ts # Custom patterns
|
||||||
|
```
|
||||||
|
|
||||||
|
Import in `panda.config.ts`:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { tokens } from './src/styles/tokens'
|
||||||
|
import { semanticTokens } from './src/styles/semanticTokens'
|
||||||
|
import { conditions } from './src/styles/conditions'
|
||||||
|
import * as recipes from './src/recipes'
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
theme: {
|
||||||
|
extend: {
|
||||||
|
tokens,
|
||||||
|
semanticTokens,
|
||||||
|
recipes: {
|
||||||
|
...recipes
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
conditions
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Enable Useful Features
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
export default defineConfig({
|
||||||
|
// Generate JSDoc comments for better autocomplete
|
||||||
|
emitTokensOnly: false,
|
||||||
|
|
||||||
|
// Hash class names in production
|
||||||
|
hash: process.env.NODE_ENV === 'production',
|
||||||
|
|
||||||
|
// Minify output
|
||||||
|
minify: process.env.NODE_ENV === 'production',
|
||||||
|
|
||||||
|
// Optimize build
|
||||||
|
optimize: true,
|
||||||
|
|
||||||
|
// Enable container queries
|
||||||
|
containerQueries: true
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Configure Output Paths for Distribution
|
||||||
|
|
||||||
|
If building a **library**:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
export default defineConfig({
|
||||||
|
outdir: 'styled-system',
|
||||||
|
|
||||||
|
// Generate separate token files
|
||||||
|
outExtension: 'js',
|
||||||
|
|
||||||
|
// Export as ES modules
|
||||||
|
emitPackage: true
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
In `package.json`:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"exports": {
|
||||||
|
"./preset": "./panda-preset.js",
|
||||||
|
"./styles.css": "./styled-system/styles.css"
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"styled-system",
|
||||||
|
"panda-preset.js"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Accessing Official Panda CSS Docs
|
||||||
|
|
||||||
|
When you need up-to-date information about Panda CSS features, configuration options, or API changes:
|
||||||
|
|
||||||
|
### Use MCP Tools (Recommended)
|
||||||
|
|
||||||
|
1. **Resolve the library ID:**
|
||||||
|
```
|
||||||
|
Use mcp__MCP_DOCKER__resolve-library-id with libraryName: "panda-css"
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Fetch documentation:**
|
||||||
|
```
|
||||||
|
Use mcp__MCP_DOCKER__get-library-docs with:
|
||||||
|
- context7CompatibleLibraryID: (from step 1)
|
||||||
|
- topic: "configuration" | "recipes" | "patterns" | etc.
|
||||||
|
```
|
||||||
|
|
||||||
|
### Topics to Search
|
||||||
|
|
||||||
|
- "configuration" - Config options, presets
|
||||||
|
- "tokens" - Design tokens, semantic tokens
|
||||||
|
- "recipes" - Component recipes, variants
|
||||||
|
- "patterns" - Built-in and custom patterns
|
||||||
|
- "conditions" - Responsive, state, pseudo-classes
|
||||||
|
- "utilities" - Utility functions and utilities
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### Issue: "Token not found" errors with strictTokens
|
||||||
|
|
||||||
|
**Solution**: Either add the token to your tokens config, or disable strictTokens (not recommended).
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Add missing token
|
||||||
|
tokens: {
|
||||||
|
colors: {
|
||||||
|
brand: { value: '#FF0000' }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Issue: Styles not updating during development
|
||||||
|
|
||||||
|
**Solution**: Ensure Panda watch mode is running:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
panda --watch
|
||||||
|
```
|
||||||
|
|
||||||
|
Or update dev script:
|
||||||
|
|
||||||
|
```json
|
||||||
|
"dev": "panda --watch & vite"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Issue: Import errors for @styled-system
|
||||||
|
|
||||||
|
**Solution**:
|
||||||
|
1. Run `npm run prepare` to generate files
|
||||||
|
2. Verify `tsconfig.json` has correct path mapping
|
||||||
|
3. Check Vite alias configuration
|
||||||
|
|
||||||
|
### Issue: CSS not loading
|
||||||
|
|
||||||
|
**Solution**: Ensure you imported styles in app entry:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import '@styled-system/styles.css'
|
||||||
|
```
|
||||||
|
|
||||||
|
## Next Steps
|
||||||
|
|
||||||
|
After setup is complete:
|
||||||
|
|
||||||
|
1. **Design tokens**: Use the **panda-token-architecture** skill to organize your design system
|
||||||
|
2. **Recipes**: Use the **panda-recipe-patterns** skill to create component styles
|
||||||
|
3. **Components**: Use the **panda-component-impl** skill to build React components
|
||||||
|
|
||||||
|
For complex architectural decisions or refactoring, launch the **panda-architect** agent.
|
||||||
|
|
||||||
|
## Working Examples
|
||||||
|
|
||||||
|
Reference these files in the `examples/` directory for production-tested patterns:
|
||||||
|
|
||||||
|
**Configuration:**
|
||||||
|
- `examples/panda.config.ts` - Complete Panda config with preset integration
|
||||||
|
- `examples/preset.ts` - Full preset architecture (myPreset) with tokens, semantic tokens, conditions, patterns
|
||||||
|
|
||||||
|
**Token Architecture:**
|
||||||
|
- `examples/tokens.ts` - Base token files organized by category
|
||||||
|
- `examples/semanticTokens.ts/` - Semantic token layer referencing primitives
|
||||||
|
|
||||||
|
**Additional Patterns:**
|
||||||
|
- `examples/textStyles.ts` - Typography preset definitions
|
||||||
|
- `examples/conditions.ts` - Custom conditions and pseudo-classes
|
||||||
|
- `examples/utils/splitProps.ts` - CSS/HTML prop separation utility
|
||||||
|
- `examples/utils/ThemeContext.tsx` - Theme provider pattern
|
||||||
693
skills/panda-token-architecture.md
Normal file
693
skills/panda-token-architecture.md
Normal file
@@ -0,0 +1,693 @@
|
|||||||
|
---
|
||||||
|
name: panda-token-architecture
|
||||||
|
description: Design token systems, semantic tokens, theme structures, and responsive design tokens following best practices
|
||||||
|
---
|
||||||
|
|
||||||
|
# Panda CSS Token Architecture
|
||||||
|
|
||||||
|
## When to Use This Skill
|
||||||
|
|
||||||
|
Use this skill when:
|
||||||
|
- Designing a design system's token architecture
|
||||||
|
- Organizing color palettes, spacing, typography, and other design tokens
|
||||||
|
- Setting up theme switching (light/dark modes)
|
||||||
|
- Creating semantic token layers for intent-based naming
|
||||||
|
- Establishing responsive design token patterns
|
||||||
|
|
||||||
|
For implementing these tokens in recipes or components, use **panda-recipe-patterns** or **panda-component-impl** skills.
|
||||||
|
|
||||||
|
## Token Architecture Principles
|
||||||
|
|
||||||
|
### Two-Layer Token System
|
||||||
|
|
||||||
|
**Layer 1: Base Tokens** - Raw design values
|
||||||
|
- Color palettes with numeric scales
|
||||||
|
- Sizing and spacing scales
|
||||||
|
- Typography scales
|
||||||
|
- Static values that rarely change
|
||||||
|
|
||||||
|
**Layer 2: Semantic Tokens** - Context-aware aliases
|
||||||
|
- Theme-dependent (light/dark mode)
|
||||||
|
- Intent-based naming (success, error, brand)
|
||||||
|
- References to base tokens
|
||||||
|
- Changes based on context/theme
|
||||||
|
|
||||||
|
**Why**: Separation enables theme switching without redefining entire palettes.
|
||||||
|
|
||||||
|
## Base Tokens
|
||||||
|
|
||||||
|
Create: `src/styles/tokens.ts`
|
||||||
|
|
||||||
|
### Color Palettes
|
||||||
|
|
||||||
|
**Pattern**: Use numeric scales (0-100 or 1-90) for lightness/darkness:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { defineTokens } from '@pandacss/dev'
|
||||||
|
|
||||||
|
export const tokens = defineTokens({
|
||||||
|
colors: {
|
||||||
|
// Grayscale: 0 = lightest, 100 = darkest
|
||||||
|
slate: {
|
||||||
|
0: { value: '#FFFFFF' },
|
||||||
|
5: { value: '#F8F9FA' },
|
||||||
|
10: { value: '#F1F3F5' },
|
||||||
|
20: { value: '#E9ECEF' },
|
||||||
|
30: { value: '#DEE2E6' },
|
||||||
|
40: { value: '#CED4DA' },
|
||||||
|
50: { value: '#ADB5BD' },
|
||||||
|
60: { value: '#868E96' },
|
||||||
|
70: { value: '#495057' },
|
||||||
|
80: { value: '#343A40' },
|
||||||
|
90: { value: '#212529' },
|
||||||
|
100: { value: '#000000' }
|
||||||
|
},
|
||||||
|
|
||||||
|
// Brand colors with tints/shades
|
||||||
|
blue: {
|
||||||
|
5: { value: '#E7F5FF' },
|
||||||
|
10: { value: '#D0EBFF' },
|
||||||
|
20: { value: '#A5D8FF' },
|
||||||
|
30: { value: '#74C0FC' },
|
||||||
|
40: { value: '#4DABF7' },
|
||||||
|
50: { value: '#339AF0' }, // Base brand blue
|
||||||
|
60: { value: '#228BE6' },
|
||||||
|
70: { value: '#1C7ED6' },
|
||||||
|
80: { value: '#1971C2' },
|
||||||
|
90: { value: '#1864AB' }
|
||||||
|
},
|
||||||
|
|
||||||
|
// Semantic palette colors
|
||||||
|
green: { /* success shades */ },
|
||||||
|
red: { /* error shades */ },
|
||||||
|
yellow: { /* warning shades */ },
|
||||||
|
cyan: { /* info shades */ }
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
**Naming Convention**:
|
||||||
|
- 0-100 scale: 0 = lightest, 100 = darkest
|
||||||
|
- Or 1-90 scale: 1 = lightest, 90 = darkest
|
||||||
|
- Choose one convention and stick to it
|
||||||
|
|
||||||
|
### Spacing & Sizing
|
||||||
|
|
||||||
|
**Pattern**: Unified scale for both spacing and sizing:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const sizes = {
|
||||||
|
// Utility sizes
|
||||||
|
0: { value: '0' },
|
||||||
|
auto: { value: 'auto' },
|
||||||
|
full: { value: '100%' },
|
||||||
|
min: { value: 'min-content' },
|
||||||
|
max: { value: 'max-content' },
|
||||||
|
fit: { value: 'fit-content' },
|
||||||
|
prose: { value: '65ch' },
|
||||||
|
|
||||||
|
// Numeric scale in rem (16px base)
|
||||||
|
1: { value: '0.0625rem' }, // 1px
|
||||||
|
2: { value: '0.125rem' }, // 2px
|
||||||
|
4: { value: '0.25rem' }, // 4px
|
||||||
|
6: { value: '0.375rem' }, // 6px
|
||||||
|
8: { value: '0.5rem' }, // 8px
|
||||||
|
12: { value: '0.75rem' }, // 12px
|
||||||
|
16: { value: '1rem' }, // 16px
|
||||||
|
20: { value: '1.25rem' }, // 20px
|
||||||
|
24: { value: '1.5rem' }, // 24px
|
||||||
|
32: { value: '2rem' }, // 32px
|
||||||
|
40: { value: '2.5rem' }, // 40px
|
||||||
|
48: { value: '3rem' }, // 48px
|
||||||
|
64: { value: '4rem' }, // 64px
|
||||||
|
80: { value: '5rem' }, // 80px
|
||||||
|
96: { value: '6rem' }, // 96px
|
||||||
|
// ... extend as needed
|
||||||
|
}
|
||||||
|
|
||||||
|
export const tokens = defineTokens({
|
||||||
|
sizes,
|
||||||
|
spacing: sizes // Reuse same scale for spacing
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
**Why**: Unified scale ensures consistency between width/height and padding/margin.
|
||||||
|
|
||||||
|
### Container Sizes
|
||||||
|
|
||||||
|
**Pattern**: Named breakpoint containers:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
export const tokens = defineTokens({
|
||||||
|
sizes: {
|
||||||
|
// ... numeric sizes above ...
|
||||||
|
|
||||||
|
// Named container sizes
|
||||||
|
'2xs': { value: '16rem' }, // 256px
|
||||||
|
xs: { value: '20rem' }, // 320px
|
||||||
|
sm: { value: '24rem' }, // 384px
|
||||||
|
md: { value: '28rem' }, // 448px
|
||||||
|
lg: { value: '32rem' }, // 512px
|
||||||
|
xl: { value: '36rem' }, // 576px
|
||||||
|
'2xl': { value: '42rem' }, // 672px
|
||||||
|
'3xl': { value: '48rem' }, // 768px
|
||||||
|
'4xl': { value: '56rem' }, // 896px
|
||||||
|
'5xl': { value: '64rem' }, // 1024px
|
||||||
|
'6xl': { value: '72rem' }, // 1152px
|
||||||
|
'7xl': { value: '80rem' }, // 1280px
|
||||||
|
'8xl': { value: '90rem' } // 1440px
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
### Typography
|
||||||
|
|
||||||
|
**Pattern**: Separate font families, weights, sizes, and line heights:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
export const tokens = defineTokens({
|
||||||
|
fonts: {
|
||||||
|
body: { value: 'Inter, -apple-system, sans-serif' },
|
||||||
|
heading: { value: 'Inter, -apple-system, sans-serif' },
|
||||||
|
mono: { value: 'JetBrains Mono, Consolas, monospace' }
|
||||||
|
},
|
||||||
|
|
||||||
|
fontWeights: {
|
||||||
|
thin: { value: '100' },
|
||||||
|
light: { value: '300' },
|
||||||
|
normal: { value: '400' },
|
||||||
|
medium: { value: '500' },
|
||||||
|
semibold: { value: '600' },
|
||||||
|
bold: { value: '700' },
|
||||||
|
extrabold: { value: '800' }
|
||||||
|
},
|
||||||
|
|
||||||
|
fontSizes: {
|
||||||
|
'2xs': { value: '0.625rem' }, // 10px
|
||||||
|
xs: { value: '0.75rem' }, // 12px
|
||||||
|
sm: { value: '0.875rem' }, // 14px
|
||||||
|
md: { value: '1rem' }, // 16px
|
||||||
|
lg: { value: '1.125rem' }, // 18px
|
||||||
|
xl: { value: '1.25rem' }, // 20px
|
||||||
|
'2xl': { value: '1.5rem' }, // 24px
|
||||||
|
'3xl': { value: '1.875rem' }, // 30px
|
||||||
|
'4xl': { value: '2.25rem' }, // 36px
|
||||||
|
'5xl': { value: '3rem' }, // 48px
|
||||||
|
'6xl': { value: '3.75rem' }, // 60px
|
||||||
|
'7xl': { value: '4.5rem' } // 72px
|
||||||
|
},
|
||||||
|
|
||||||
|
lineHeights: {
|
||||||
|
none: { value: '1' },
|
||||||
|
tight: { value: '1.25' },
|
||||||
|
snug: { value: '1.375' },
|
||||||
|
normal: { value: '1.5' },
|
||||||
|
relaxed: { value: '1.625' },
|
||||||
|
loose: { value: '2' }
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
### Other Design Tokens
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
export const tokens = defineTokens({
|
||||||
|
radii: {
|
||||||
|
none: { value: '0' },
|
||||||
|
sm: { value: '0.125rem' }, // 2px
|
||||||
|
base: { value: '0.25rem' }, // 4px
|
||||||
|
md: { value: '0.375rem' }, // 6px
|
||||||
|
lg: { value: '0.5rem' }, // 8px
|
||||||
|
xl: { value: '0.75rem' }, // 12px
|
||||||
|
'2xl': { value: '1rem' }, // 16px
|
||||||
|
full: { value: '9999px' }
|
||||||
|
},
|
||||||
|
|
||||||
|
shadows: {
|
||||||
|
xs: { value: '0 1px 2px 0 rgb(0 0 0 / 0.05)' },
|
||||||
|
sm: { value: '0 1px 3px 0 rgb(0 0 0 / 0.1)' },
|
||||||
|
md: { value: '0 4px 6px -1px rgb(0 0 0 / 0.1)' },
|
||||||
|
lg: { value: '0 10px 15px -3px rgb(0 0 0 / 0.1)' },
|
||||||
|
xl: { value: '0 20px 25px -5px rgb(0 0 0 / 0.1)' },
|
||||||
|
inner: { value: 'inset 0 2px 4px 0 rgb(0 0 0 / 0.05)' }
|
||||||
|
},
|
||||||
|
|
||||||
|
easings: {
|
||||||
|
default: { value: 'cubic-bezier(0.4, 0, 0.2, 1)' },
|
||||||
|
linear: { value: 'linear' },
|
||||||
|
in: { value: 'cubic-bezier(0.4, 0, 1, 1)' },
|
||||||
|
out: { value: 'cubic-bezier(0, 0, 0.2, 1)' },
|
||||||
|
inOut: { value: 'cubic-bezier(0.4, 0, 0.2, 1)' }
|
||||||
|
},
|
||||||
|
|
||||||
|
durations: {
|
||||||
|
fast: { value: '150ms' },
|
||||||
|
normal: { value: '250ms' },
|
||||||
|
slow: { value: '350ms' },
|
||||||
|
slower: { value: '500ms' }
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
## Semantic Tokens
|
||||||
|
|
||||||
|
Create: `src/styles/semanticTokens.ts`
|
||||||
|
|
||||||
|
### Purpose
|
||||||
|
|
||||||
|
Semantic tokens provide:
|
||||||
|
- Theme-aware values (light/dark mode)
|
||||||
|
- Intent-based naming (success, error, warning)
|
||||||
|
- Contextual meaning (background, foreground, border)
|
||||||
|
- Easier token updates without touching components
|
||||||
|
|
||||||
|
### Pattern: Theme-Aware Colors
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { defineSemanticTokens } from '@pandacss/dev'
|
||||||
|
|
||||||
|
export const semanticTokens = defineSemanticTokens({
|
||||||
|
colors: {
|
||||||
|
// Background colors
|
||||||
|
bg: {
|
||||||
|
canvas: {
|
||||||
|
value: { base: '{colors.slate.0}', _dark: '{colors.slate.90}' }
|
||||||
|
},
|
||||||
|
surface: {
|
||||||
|
value: { base: '{colors.slate.5}', _dark: '{colors.slate.80}' }
|
||||||
|
},
|
||||||
|
overlay: {
|
||||||
|
value: { base: '{colors.slate.10}', _dark: '{colors.slate.70}' }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Foreground/text colors
|
||||||
|
fg: {
|
||||||
|
default: {
|
||||||
|
value: { base: '{colors.slate.90}', _dark: '{colors.slate.10}' }
|
||||||
|
},
|
||||||
|
muted: {
|
||||||
|
value: { base: '{colors.slate.60}', _dark: '{colors.slate.40}' }
|
||||||
|
},
|
||||||
|
subtle: {
|
||||||
|
value: { base: '{colors.slate.50}', _dark: '{colors.slate.50}' }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Border colors
|
||||||
|
border: {
|
||||||
|
default: {
|
||||||
|
value: { base: '{colors.slate.20}', _dark: '{colors.slate.70}' }
|
||||||
|
},
|
||||||
|
muted: {
|
||||||
|
value: { base: '{colors.slate.10}', _dark: '{colors.slate.80}' }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Intent-based colors
|
||||||
|
success: {
|
||||||
|
default: {
|
||||||
|
value: { base: '{colors.green.40}', _dark: '{colors.green.30}' }
|
||||||
|
},
|
||||||
|
emphasis: {
|
||||||
|
value: { base: '{colors.green.50}', _dark: '{colors.green.40}' }
|
||||||
|
},
|
||||||
|
muted: {
|
||||||
|
value: { base: '{colors.green.10}', _dark: '{colors.green.80}' }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
error: {
|
||||||
|
default: {
|
||||||
|
value: { base: '{colors.red.40}', _dark: '{colors.red.30}' }
|
||||||
|
},
|
||||||
|
emphasis: {
|
||||||
|
value: { base: '{colors.red.50}', _dark: '{colors.red.40}' }
|
||||||
|
},
|
||||||
|
muted: {
|
||||||
|
value: { base: '{colors.red.10}', _dark: '{colors.red.80}' }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
warning: {
|
||||||
|
default: {
|
||||||
|
value: { base: '{colors.yellow.40}', _dark: '{colors.yellow.30}' }
|
||||||
|
},
|
||||||
|
emphasis: {
|
||||||
|
value: { base: '{colors.yellow.50}', _dark: '{colors.yellow.40}' }
|
||||||
|
},
|
||||||
|
muted: {
|
||||||
|
value: { base: '{colors.yellow.10}', _dark: '{colors.yellow.80}' }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Brand colors
|
||||||
|
brand: {
|
||||||
|
default: {
|
||||||
|
value: { base: '{colors.blue.50}', _dark: '{colors.blue.40}' }
|
||||||
|
},
|
||||||
|
emphasis: {
|
||||||
|
value: { base: '{colors.blue.60}', _dark: '{colors.blue.30}' }
|
||||||
|
},
|
||||||
|
muted: {
|
||||||
|
value: { base: '{colors.blue.10}', _dark: '{colors.blue.80}' }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
### Token Reference Syntax
|
||||||
|
|
||||||
|
Reference base tokens using `{category.name.shade}`:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
value: { base: '{colors.blue.50}', _dark: '{colors.blue.40}' }
|
||||||
|
// ^-- Reference to base token
|
||||||
|
```
|
||||||
|
|
||||||
|
**Why**: References enable token changes to cascade through semantic layer.
|
||||||
|
|
||||||
|
### Nesting Semantic Tokens
|
||||||
|
|
||||||
|
Create hierarchies for better organization:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
export const semanticTokens = defineSemanticTokens({
|
||||||
|
colors: {
|
||||||
|
button: {
|
||||||
|
primary: {
|
||||||
|
bg: {
|
||||||
|
value: { base: '{colors.brand.default}', _dark: '{colors.brand.default}' }
|
||||||
|
},
|
||||||
|
fg: {
|
||||||
|
value: { base: '{colors.slate.0}', _dark: '{colors.slate.0}' }
|
||||||
|
},
|
||||||
|
border: {
|
||||||
|
value: { base: '{colors.brand.emphasis}', _dark: '{colors.brand.emphasis}' }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
secondary: {
|
||||||
|
bg: {
|
||||||
|
value: { base: '{colors.bg.surface}', _dark: '{colors.bg.surface}' }
|
||||||
|
},
|
||||||
|
fg: {
|
||||||
|
value: { base: '{colors.fg.default}', _dark: '{colors.fg.default}' }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
Usage in recipes:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
bg: 'button.primary.bg',
|
||||||
|
color: 'button.primary.fg',
|
||||||
|
borderColor: 'button.primary.border'
|
||||||
|
```
|
||||||
|
|
||||||
|
## Text Styles
|
||||||
|
|
||||||
|
**Pattern**: Define complete typography presets:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { defineTextStyles } from '@pandacss/dev'
|
||||||
|
|
||||||
|
export const textStyles = defineTextStyles({
|
||||||
|
// Display text
|
||||||
|
display: {
|
||||||
|
lg: {
|
||||||
|
value: {
|
||||||
|
fontSize: '5xl',
|
||||||
|
fontWeight: 'bold',
|
||||||
|
lineHeight: 'tight',
|
||||||
|
letterSpacing: '-0.02em'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
md: {
|
||||||
|
value: {
|
||||||
|
fontSize: '4xl',
|
||||||
|
fontWeight: 'bold',
|
||||||
|
lineHeight: 'tight'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Headings
|
||||||
|
heading: {
|
||||||
|
lg: {
|
||||||
|
value: {
|
||||||
|
fontSize: '3xl',
|
||||||
|
fontWeight: 'semibold',
|
||||||
|
lineHeight: 'tight'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
md: {
|
||||||
|
value: {
|
||||||
|
fontSize: '2xl',
|
||||||
|
fontWeight: 'semibold',
|
||||||
|
lineHeight: 'snug'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
sm: {
|
||||||
|
value: {
|
||||||
|
fontSize: 'xl',
|
||||||
|
fontWeight: 'semibold',
|
||||||
|
lineHeight: 'snug'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Body text
|
||||||
|
body: {
|
||||||
|
lg: {
|
||||||
|
value: {
|
||||||
|
fontSize: 'lg',
|
||||||
|
fontWeight: 'normal',
|
||||||
|
lineHeight: 'normal'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
md: {
|
||||||
|
value: {
|
||||||
|
fontSize: 'md',
|
||||||
|
fontWeight: 'normal',
|
||||||
|
lineHeight: 'normal'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
sm: {
|
||||||
|
value: {
|
||||||
|
fontSize: 'sm',
|
||||||
|
fontWeight: 'normal',
|
||||||
|
lineHeight: 'normal'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// In recipes or components
|
||||||
|
textStyle: 'heading.lg'
|
||||||
|
textStyle: 'body.md'
|
||||||
|
```
|
||||||
|
|
||||||
|
## Responsive Token Patterns
|
||||||
|
|
||||||
|
### Breakpoint Tokens
|
||||||
|
|
||||||
|
Define in config (not as tokens):
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// In panda.config.ts
|
||||||
|
export default defineConfig({
|
||||||
|
theme: {
|
||||||
|
extend: {
|
||||||
|
breakpoints: {
|
||||||
|
sm: '640px',
|
||||||
|
md: '768px',
|
||||||
|
lg: '1024px',
|
||||||
|
xl: '1280px',
|
||||||
|
'2xl': '1536px'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
Usage with responsive object syntax:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
fontSize: { base: 'sm', md: 'md', lg: 'lg' }
|
||||||
|
px: { base: '16', md: '20', lg: '24' }
|
||||||
|
```
|
||||||
|
|
||||||
|
### Container Query Tokens
|
||||||
|
|
||||||
|
For component-based responsive design:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// In panda.config.ts
|
||||||
|
export default defineConfig({
|
||||||
|
theme: {
|
||||||
|
extend: {
|
||||||
|
containerSizes: {
|
||||||
|
sm: '384px',
|
||||||
|
md: '448px',
|
||||||
|
lg: '512px',
|
||||||
|
xl: '576px'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
## Integration with panda.config.ts
|
||||||
|
|
||||||
|
Import and extend theme:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { defineConfig } from '@pandacss/dev'
|
||||||
|
import { tokens } from './src/styles/tokens'
|
||||||
|
import { semanticTokens } from './src/styles/semanticTokens'
|
||||||
|
import { textStyles } from './src/styles/textStyles'
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
// ... other config ...
|
||||||
|
|
||||||
|
theme: {
|
||||||
|
extend: {
|
||||||
|
tokens,
|
||||||
|
semanticTokens,
|
||||||
|
textStyles
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
## Best Practices Checklist
|
||||||
|
|
||||||
|
Create TodoWrite items when organizing tokens:
|
||||||
|
|
||||||
|
- [ ] Separate base tokens from semantic tokens (different files)
|
||||||
|
- [ ] Use consistent numeric scales (0-100 or 1-90, pick one)
|
||||||
|
- [ ] Create semantic tokens for all theme-dependent values
|
||||||
|
- [ ] Use intent-based naming (success, error, warning, not green, red, yellow)
|
||||||
|
- [ ] Unify spacing and sizing scales
|
||||||
|
- [ ] Define text styles for common typography patterns
|
||||||
|
- [ ] Reference base tokens in semantic tokens (don't duplicate values)
|
||||||
|
- [ ] Document token purpose and usage in comments
|
||||||
|
- [ ] Test tokens in both light and dark themes
|
||||||
|
- [ ] Validate strictTokens mode catches hard-coded values
|
||||||
|
|
||||||
|
## Common Pitfalls
|
||||||
|
|
||||||
|
### Avoid: Duplicating Values
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// BAD: Duplicates color value
|
||||||
|
semanticTokens: {
|
||||||
|
colors: {
|
||||||
|
success: {
|
||||||
|
value: { base: '#22C55E', _dark: '#4ADE80' }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GOOD: References base token
|
||||||
|
semanticTokens: {
|
||||||
|
colors: {
|
||||||
|
success: {
|
||||||
|
value: { base: '{colors.green.40}', _dark: '{colors.green.30}' }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Avoid: Too Many Token Layers
|
||||||
|
|
||||||
|
Keep it simple: Base → Semantic → Components (2-3 layers max)
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// BAD: Too many indirection levels
|
||||||
|
semantic → alias → component → variant → state
|
||||||
|
|
||||||
|
// GOOD: Clear, direct references
|
||||||
|
base → semantic → component
|
||||||
|
```
|
||||||
|
|
||||||
|
### Avoid: Mixing Concerns
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// BAD: Mixing spacing into colors
|
||||||
|
tokens: {
|
||||||
|
colors: {
|
||||||
|
buttonPadding: { value: '12px' } // Wrong category!
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GOOD: Correct categorization
|
||||||
|
tokens: {
|
||||||
|
spacing: {
|
||||||
|
buttonPadding: { value: '12' }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Accessing Official Panda CSS Docs
|
||||||
|
|
||||||
|
For token-specific documentation:
|
||||||
|
|
||||||
|
1. **Resolve library ID**: `mcp__MCP_DOCKER__resolve-library-id` with `libraryName: "panda-css"`
|
||||||
|
2. **Fetch docs**: `mcp__MCP_DOCKER__get-library-docs` with `topic: "tokens"` or `topic: "semantic-tokens"`
|
||||||
|
|
||||||
|
## Next Steps
|
||||||
|
|
||||||
|
After organizing tokens:
|
||||||
|
|
||||||
|
1. **Create recipes**: Use **panda-recipe-patterns** skill to build component styles
|
||||||
|
2. **Implement components**: Use **panda-component-impl** skill for React components
|
||||||
|
3. **Validate design system**: Ensure all components use tokens (strictTokens catches violations)
|
||||||
|
|
||||||
|
## Working Examples
|
||||||
|
|
||||||
|
Reference these files in the `examples/` directory for production-tested token patterns:
|
||||||
|
|
||||||
|
**Base Tokens (Primitives):**
|
||||||
|
- `examples/tokens.ts`
|
||||||
|
- - Color scales (10-100) for all hues
|
||||||
|
```typescript
|
||||||
|
lime: {
|
||||||
|
"10": { value: "#EFFFD6" },
|
||||||
|
"50": { value: "#82B536" },
|
||||||
|
"100": { value: "#28311B" }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
- - Unified spacing/sizing numeric scale
|
||||||
|
- - Font families, sizes, weights, line heights
|
||||||
|
- - Durations, easings, keyframes
|
||||||
|
- - Borders, radii, opacity, aspect ratios
|
||||||
|
|
||||||
|
**Semantic Tokens:**
|
||||||
|
- `examples/semanticTokens.ts`
|
||||||
|
- - Theme-aware color system
|
||||||
|
```typescript
|
||||||
|
background: {
|
||||||
|
accent: {
|
||||||
|
blue: {
|
||||||
|
bolder: {
|
||||||
|
value: { base: "{colors.blue.70}", _dark: "{colors.blue.40}" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
- - Shadow and depth tokens
|
||||||
|
- - Semantic utility tokens
|
||||||
|
|
||||||
|
**Preset Integration:**
|
||||||
|
- `examples/preset.ts` - Complete preset showing how to integrate primitives and semantics using `definePreset`, `defineTokens`, and `defineSemanticTokens`
|
||||||
|
- `examples/textStyles.ts` - Typography presets combining font tokens
|
||||||
Reference in New Issue
Block a user