@fr0mpy/component-system 2.2.0 → 3.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/README.md +86 -27
  2. package/bin/cli.js +155 -62
  3. package/bin/validate-compliance.js +447 -0
  4. package/bin/validate-config.js +175 -0
  5. package/index.js +15 -0
  6. package/package.json +6 -4
  7. package/templates/agents/component-auditor.md +77 -0
  8. package/templates/agents/design-token-validator.md +72 -0
  9. package/templates/agents/harness-scaffolder.md +146 -0
  10. package/templates/agents/playwright-tester.md +437 -0
  11. package/templates/agents/style-inspector.md +81 -0
  12. package/templates/commands/component-harness.md +104 -10
  13. package/templates/commands/retheme.md +142 -0
  14. package/templates/commands/save-theme.md +50 -0
  15. package/templates/commands/setup-styling.md +386 -57
  16. package/templates/component-recipes/accordion.md +9 -9
  17. package/templates/component-recipes/alert.md +36 -23
  18. package/templates/component-recipes/avatar.md +14 -12
  19. package/templates/component-recipes/badge.md +9 -6
  20. package/templates/component-recipes/breadcrumb.md +4 -4
  21. package/templates/component-recipes/button.md +2 -2
  22. package/templates/component-recipes/checkbox.md +5 -5
  23. package/templates/component-recipes/collapsible.md +13 -13
  24. package/templates/component-recipes/combobox.md +2 -2
  25. package/templates/component-recipes/context-menu.md +11 -11
  26. package/templates/component-recipes/dialog.md +16 -16
  27. package/templates/component-recipes/drawer.md +18 -18
  28. package/templates/component-recipes/dropdown-menu.md +12 -12
  29. package/templates/component-recipes/hover-card.md +11 -11
  30. package/templates/component-recipes/label.md +4 -4
  31. package/templates/component-recipes/modal.md +9 -9
  32. package/templates/component-recipes/navigation-menu.md +19 -19
  33. package/templates/component-recipes/popover.md +10 -10
  34. package/templates/component-recipes/progress.md +10 -8
  35. package/templates/component-recipes/radio.md +6 -6
  36. package/templates/component-recipes/select.md +5 -5
  37. package/templates/component-recipes/separator.md +2 -2
  38. package/templates/component-recipes/slider.md +2 -2
  39. package/templates/component-recipes/switch.md +6 -6
  40. package/templates/component-recipes/table.md +1 -1
  41. package/templates/component-recipes/tabs.md +6 -6
  42. package/templates/component-recipes/toast.md +56 -42
  43. package/templates/component-recipes/toggle-group.md +4 -4
  44. package/templates/component-recipes/tooltip.md +5 -5
  45. package/templates/mcp/mcp.json +8 -0
  46. package/templates/playwright/playwright.config.ts +31 -0
  47. package/templates/playwright/tests/components.spec.ts +104 -0
  48. package/templates/skills/react-patterns.md +4 -0
  49. package/templates/skills/styling.md +141 -52
  50. package/templates/hooks/triggers.d/react-patterns.json +0 -18
  51. package/templates/hooks/triggers.d/styling.json +0 -23
@@ -2,25 +2,39 @@
2
2
 
3
3
  ## Structure
4
4
  - Container with icon, title, and description
5
- - Support variants: info, success, warning, error/destructive
5
+ - Support variants: default, success, warning, destructive
6
6
  - Optional dismiss button
7
7
  - Optional action buttons
8
8
 
9
+ ## IMPORTANT: Color Token Usage
10
+
11
+ **NEVER use hardcoded Tailwind colors.** Use semantic tokens only:
12
+
13
+ | Semantic Token | Purpose |
14
+ |----------------|---------|
15
+ | `bg-success`, `text-success`, `border-success` | Success states |
16
+ | `bg-warning`, `text-warning`, `border-warning` | Warning states |
17
+ | `bg-destructive`, `text-destructive`, `border-destructive` | Error/destructive states |
18
+ | `bg-accent`, `text-accent`, `border-accent` | Info/accent states |
19
+ | `bg-primary`, `text-primary`, `border-primary` | Primary actions |
20
+ | `bg-muted`, `text-muted-foreground` | Muted/secondary text |
21
+
9
22
  ## Tailwind Classes
10
23
 
11
24
  ### Container
12
25
  ```
13
- relative w-full {tokens.radius} border p-4
26
+ relative w-full rounded-[USE_CONFIG_RADIUS] border p-4
14
27
  [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:h-4 [&>svg]:w-4
15
28
  [&>svg+div]:translate-y-[-3px] [&:has(svg)]:pl-11
16
29
  ```
17
30
 
18
- ### Variants
31
+ **Note:** Replace `rounded-[USE_CONFIG_RADIUS]` with the value from `.claude/styling-config.json` → `tokens.radius` (e.g., `rounded-none`, `rounded-md`, `rounded-lg`)
32
+
33
+ ### Variants (Using Semantic Tokens)
19
34
  ```
20
35
  default: bg-background border-border text-foreground [&>svg]:text-foreground
21
- info: bg-blue-500/10 border-blue-500/20 text-blue-600 [&>svg]:text-blue-600
22
- success: bg-green-500/10 border-green-500/20 text-green-600 [&>svg]:text-green-600
23
- warning: bg-yellow-500/10 border-yellow-500/20 text-yellow-600 [&>svg]:text-yellow-600
36
+ success: bg-success/10 border-success/20 text-success [&>svg]:text-success
37
+ warning: bg-warning/10 border-warning/20 text-warning [&>svg]:text-warning
24
38
  destructive: bg-destructive/10 border-destructive/20 text-destructive [&>svg]:text-destructive
25
39
  ```
26
40
 
@@ -36,14 +50,14 @@ text-sm [&_p]:leading-relaxed
36
50
 
37
51
  ### Dismiss Button
38
52
  ```
39
- absolute right-2 top-2 {tokens.radius} p-1 opacity-70
40
- hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-primary
53
+ absolute right-2 top-2 rounded-[USE_CONFIG_RADIUS] p-1 opacity-70 cursor-pointer
54
+ hover:opacity-100 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary
41
55
  ```
42
56
 
43
57
  ## Props Interface
44
58
  ```typescript
45
59
  interface AlertProps {
46
- variant?: 'default' | 'info' | 'success' | 'warning' | 'destructive'
60
+ variant?: 'default' | 'success' | 'warning' | 'destructive'
47
61
  icon?: React.ReactNode
48
62
  title?: string
49
63
  onDismiss?: () => void
@@ -63,7 +77,6 @@ interface AlertDescriptionProps {
63
77
 
64
78
  ## Icons per Variant
65
79
  ```
66
- info: <Info /> or <AlertCircle />
67
80
  success: <CheckCircle />
68
81
  warning: <AlertTriangle />
69
82
  destructive: <XCircle /> or <AlertOctagon />
@@ -71,33 +84,32 @@ default: <Info /> or custom
71
84
  ```
72
85
 
73
86
  ## Do
74
- - Include appropriate icon for each variant
75
- - Use semantic colors from tokens where possible
87
+ - Use semantic color tokens ONLY (bg-success, bg-warning, bg-destructive)
88
+ - Read `.claude/styling-config.json` for radius value
89
+ - Include `cursor-pointer` on dismiss button
90
+ - Use `focus-visible:` not `focus:` for keyboard accessibility
76
91
  - Support composable pattern (Alert.Title, Alert.Description)
77
- - Add dismiss functionality when needed
78
92
 
79
93
  ## Don't
80
- - Hardcode colors
81
- - Make alerts too visually heavy
82
- - Forget to position icon properly
83
- - Skip the semantic meaning of variants
94
+ - Use hardcoded colors (bg-green-500, bg-yellow-500, #XXXXXX)
95
+ - Use non-semantic Tailwind colors
96
+ - Use `focus:` instead of `focus-visible:`
97
+ - Forget `cursor-pointer` on interactive elements
84
98
 
85
99
  ## Example
86
100
  ```tsx
87
101
  import { cn } from '@/lib/utils'
88
- import { AlertCircle, CheckCircle, AlertTriangle, XCircle, X, Info } from 'lucide-react'
102
+ import { CheckCircle, AlertTriangle, XCircle, X, Info } from 'lucide-react'
89
103
 
90
104
  const alertVariants = {
91
105
  default: 'bg-background border-border text-foreground',
92
- info: 'bg-blue-500/10 border-blue-500/20 text-blue-600 [&>svg]:text-blue-600',
93
- success: 'bg-green-500/10 border-green-500/20 text-green-600 [&>svg]:text-green-600',
94
- warning: 'bg-yellow-500/10 border-yellow-500/20 text-yellow-600 [&>svg]:text-yellow-600',
106
+ success: 'bg-success/10 border-success/20 text-success [&>svg]:text-success',
107
+ warning: 'bg-warning/10 border-warning/20 text-warning [&>svg]:text-warning',
95
108
  destructive: 'bg-destructive/10 border-destructive/20 text-destructive [&>svg]:text-destructive',
96
109
  }
97
110
 
98
111
  const alertIcons = {
99
112
  default: Info,
100
- info: AlertCircle,
101
113
  success: CheckCircle,
102
114
  warning: AlertTriangle,
103
115
  destructive: XCircle,
@@ -110,6 +122,7 @@ const Alert = ({ variant = 'default', title, onDismiss, className, children, ...
110
122
  <div
111
123
  role="alert"
112
124
  className={cn(
125
+ // Base styles - replace rounded-lg with your config's tokens.radius
113
126
  'relative w-full rounded-lg border p-4',
114
127
  '[&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:h-4 [&>svg]:w-4',
115
128
  '[&>svg+div]:translate-y-[-3px] [&:has(svg)]:pl-11',
@@ -126,7 +139,7 @@ const Alert = ({ variant = 'default', title, onDismiss, className, children, ...
126
139
  {onDismiss && (
127
140
  <button
128
141
  onClick={onDismiss}
129
- className="absolute right-2 top-2 rounded p-1 opacity-70 hover:opacity-100"
142
+ className="absolute right-2 top-2 rounded p-1 opacity-70 cursor-pointer hover:opacity-100 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary"
130
143
  >
131
144
  <X className="h-4 w-4" />
132
145
  </button>
@@ -35,13 +35,13 @@ flex h-full w-full items-center justify-center rounded-full
35
35
  bg-muted text-muted-foreground font-medium
36
36
  ```
37
37
 
38
- ### Status Indicator
38
+ ### Status Indicator (Using Semantic Tokens)
39
39
  ```
40
40
  absolute bottom-0 right-0 h-3 w-3 rounded-full border-2 border-background
41
- online: bg-green-500
42
- offline: bg-gray-400
43
- busy: bg-red-500
44
- away: bg-yellow-500
41
+ online: bg-success
42
+ offline: bg-muted
43
+ busy: bg-destructive
44
+ away: bg-warning
45
45
  ```
46
46
 
47
47
  ### Avatar Group
@@ -67,20 +67,21 @@ interface AvatarGroupProps {
67
67
  ```
68
68
 
69
69
  ## Do
70
- - Use Radix Avatar for image loading states
70
+ - Use Base UI Avatar for image loading states
71
71
  - Show fallback immediately while image loads
72
72
  - Support both initials and icon fallbacks
73
73
  - Use ring for avatar group overlap effect
74
74
 
75
75
  ## Don't
76
- - Hardcode colors
76
+ - Hardcode colors (use bg-success NOT bg-green-500)
77
+ - Use non-semantic Tailwind colors
77
78
  - Forget alt text for images
78
79
  - Use non-square aspect ratios
79
80
  - Skip loading state handling
80
81
 
81
82
  ## Example
82
83
  ```tsx
83
- import * as AvatarPrimitive from '@radix-ui/react-avatar'
84
+ import { Avatar as AvatarPrimitive } from '@base-ui/react/avatar'
84
85
  import { cn } from '@/lib/utils'
85
86
  import { User } from 'lucide-react'
86
87
 
@@ -93,11 +94,12 @@ const avatarSizes = {
93
94
  '2xl': 'h-24 w-24 text-xl',
94
95
  }
95
96
 
97
+ // Using semantic tokens - NO hardcoded colors!
96
98
  const statusColors = {
97
- online: 'bg-green-500',
98
- offline: 'bg-gray-400',
99
- busy: 'bg-red-500',
100
- away: 'bg-yellow-500',
99
+ online: 'bg-success',
100
+ offline: 'bg-muted',
101
+ busy: 'bg-destructive',
102
+ away: 'bg-warning',
101
103
  }
102
104
 
103
105
  const Avatar = ({ src, alt, fallback, size = 'md', status, className }) => (
@@ -14,14 +14,14 @@ inline-flex items-center rounded-full px-2.5 py-0.5 text-xs font-medium
14
14
  transition-colors
15
15
  ```
16
16
 
17
- ### Variants
17
+ ### Variants (Using Semantic Tokens ONLY)
18
18
  ```
19
19
  default: bg-primary text-primary-foreground
20
20
  secondary: bg-secondary text-secondary-foreground
21
21
  outline: border border-border text-foreground bg-transparent
22
22
  destructive: bg-destructive text-destructive-foreground
23
- success: bg-green-500/10 text-green-600 border border-green-500/20
24
- warning: bg-yellow-500/10 text-yellow-600 border border-yellow-500/20
23
+ success: bg-success/10 text-success border border-success/20
24
+ warning: bg-warning/10 text-warning border border-warning/20
25
25
  ```
26
26
 
27
27
  ### Sizes
@@ -68,21 +68,24 @@ interface BadgeProps {
68
68
  ## Don't
69
69
  - Make badges too large
70
70
  - Use for long text content
71
- - Hardcode colors
71
+ - Hardcode colors (use bg-success NOT bg-green-500)
72
+ - Use non-semantic Tailwind colors
72
73
  - Forget hover state for interactive badges
74
+ - Forget `cursor-pointer` on interactive badges
73
75
 
74
76
  ## Example
75
77
  ```tsx
76
78
  import { cn } from '@/lib/utils'
77
79
  import { X } from 'lucide-react'
78
80
 
81
+ // Using semantic tokens - NO hardcoded colors!
79
82
  const badgeVariants = {
80
83
  default: 'bg-primary text-primary-foreground',
81
84
  secondary: 'bg-secondary text-secondary-foreground',
82
85
  outline: 'border border-border text-foreground bg-transparent',
83
86
  destructive: 'bg-destructive text-destructive-foreground',
84
- success: 'bg-green-500/10 text-green-600 border border-green-500/20',
85
- warning: 'bg-yellow-500/10 text-yellow-600 border border-yellow-500/20',
87
+ success: 'bg-success/10 text-success border border-success/20',
88
+ warning: 'bg-warning/10 text-warning border border-warning/20',
86
89
  }
87
90
 
88
91
  const badgeSizes = {
@@ -72,7 +72,7 @@ interface BreadcrumbListProps {
72
72
 
73
73
  interface BreadcrumbLinkProps {
74
74
  href: string
75
- asChild?: boolean
75
+ render?: React.ReactElement
76
76
  children: React.ReactNode
77
77
  }
78
78
 
@@ -110,7 +110,7 @@ interface BreadcrumbEllipsisProps {
110
110
  ## Example
111
111
  ```tsx
112
112
  import { ChevronRight, MoreHorizontal } from 'lucide-react'
113
- import { Slot } from '@radix-ui/react-slot'
113
+ import { Slot } from '@base-ui/react/slot'
114
114
  import { cn } from '@/lib/utils'
115
115
 
116
116
  const Breadcrumb = ({ children, ...props }) => (
@@ -133,8 +133,8 @@ const BreadcrumbItem = ({ className, ...props }) => (
133
133
  <li className={cn('inline-flex items-center gap-1.5', className)} {...props} />
134
134
  )
135
135
 
136
- const BreadcrumbLink = ({ asChild, className, ...props }) => {
137
- const Comp = asChild ? Slot : 'a'
136
+ const BreadcrumbLink = ({ render, className, ...props }) => {
137
+ const Comp = render ? Slot : 'a'
138
138
  return (
139
139
  <Comp
140
140
  className={cn('transition-colors hover:text-foreground', className)}
@@ -5,7 +5,7 @@
5
5
  - Support variants: primary, secondary, outline, ghost, destructive
6
6
  - Support sizes: sm, md, lg
7
7
  - Include states: loading, disabled
8
- - Optionally support `asChild` for composition (render as link, etc.)
8
+ - Optionally support `render` for composition (render as link, etc.)
9
9
 
10
10
  ## Tailwind Classes
11
11
 
@@ -46,7 +46,7 @@ interface ButtonProps {
46
46
  size?: 'sm' | 'md' | 'lg'
47
47
  loading?: boolean
48
48
  disabled?: boolean
49
- asChild?: boolean
49
+ render?: boolean
50
50
  children: React.ReactNode
51
51
  }
52
52
  ```
@@ -14,8 +14,8 @@ peer h-4 w-4 shrink-0 {tokens.radius} border border-border
14
14
  ring-offset-background
15
15
  focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary focus-visible:ring-offset-2
16
16
  disabled:cursor-not-allowed disabled:opacity-50
17
- data-[state=checked]:bg-primary data-[state=checked]:border-primary data-[state=checked]:text-primary-foreground
18
- data-[state=indeterminate]:bg-primary data-[state=indeterminate]:border-primary
17
+ data-checked:bg-primary data-checked:border-primary data-checked:text-primary-foreground
18
+ data-indeterminate:bg-primary data-indeterminate:border-primary
19
19
  ```
20
20
 
21
21
  ### Check Icon
@@ -70,7 +70,7 @@ interface CheckboxWithLabelProps extends CheckboxProps {
70
70
 
71
71
  ## Example
72
72
  ```tsx
73
- import * as CheckboxPrimitive from '@radix-ui/react-checkbox'
73
+ import { Checkbox as CheckboxPrimitive } from '@base-ui/react/checkbox'
74
74
  import { Check, Minus } from 'lucide-react'
75
75
  import { cn } from '@/lib/utils'
76
76
 
@@ -81,8 +81,8 @@ const Checkbox = ({ className, ...props }) => (
81
81
  'ring-offset-background',
82
82
  'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary focus-visible:ring-offset-2',
83
83
  'disabled:cursor-not-allowed disabled:opacity-50',
84
- 'data-[state=checked]:bg-primary data-[state=checked]:border-primary data-[state=checked]:text-primary-foreground',
85
- 'data-[state=indeterminate]:bg-primary data-[state=indeterminate]:border-primary data-[state=indeterminate]:text-primary-foreground',
84
+ 'data-checked:bg-primary data-checked:border-primary data-checked:text-primary-foreground',
85
+ 'data-indeterminate:bg-primary data-indeterminate:border-primary data-indeterminate:text-primary-foreground',
86
86
  className
87
87
  )}
88
88
  {...props}
@@ -15,7 +15,7 @@ w-full
15
15
 
16
16
  ### Trigger
17
17
  ```
18
- flex items-center justify-between w-full [&[data-state=open]>svg]:rotate-180
18
+ flex items-center justify-between w-full [&[data-open]>svg]:rotate-180
19
19
  ```
20
20
 
21
21
  ### Trigger with default styling
@@ -32,8 +32,8 @@ h-4 w-4 shrink-0 text-muted-foreground transition-transform duration-200
32
32
  ### Content
33
33
  ```
34
34
  overflow-hidden
35
- data-[state=closed]:animate-collapsible-up
36
- data-[state=open]:animate-collapsible-down
35
+ data-closed:animate-collapsible-up
36
+ data-open:animate-collapsible-down
37
37
  ```
38
38
 
39
39
  ### Content Inner (for padding)
@@ -46,10 +46,10 @@ pt-2 pb-4
46
46
  keyframes: {
47
47
  'collapsible-down': {
48
48
  from: { height: '0' },
49
- to: { height: 'var(--radix-collapsible-content-height)' },
49
+ to: { height: 'var(--collapsible-panel-height)' },
50
50
  },
51
51
  'collapsible-up': {
52
- from: { height: 'var(--radix-collapsible-content-height)' },
52
+ from: { height: 'var(--collapsible-panel-height)' },
53
53
  to: { height: '0' },
54
54
  },
55
55
  },
@@ -70,7 +70,7 @@ interface CollapsibleProps {
70
70
  }
71
71
 
72
72
  interface CollapsibleTriggerProps {
73
- asChild?: boolean
73
+ render?: React.ReactElement
74
74
  children: React.ReactNode
75
75
  }
76
76
 
@@ -89,7 +89,7 @@ interface CollapsibleContentProps {
89
89
  - Code snippets with preview
90
90
 
91
91
  ## Do
92
- - Use Radix Collapsible for accessibility
92
+ - Use Base UI Collapsible for accessibility
93
93
  - Animate height smoothly
94
94
  - Include visual indicator of state (chevron)
95
95
  - Support keyboard toggle (Enter/Space)
@@ -102,7 +102,7 @@ interface CollapsibleContentProps {
102
102
 
103
103
  ## Example
104
104
  ```tsx
105
- import * as CollapsiblePrimitive from '@radix-ui/react-collapsible'
105
+ import { Collapsible as CollapsiblePrimitive } from '@base-ui/react/collapsible'
106
106
  import { ChevronDown } from 'lucide-react'
107
107
  import { cn } from '@/lib/utils'
108
108
 
@@ -114,8 +114,8 @@ const CollapsibleContent = ({ className, children, ...props }) => (
114
114
  <CollapsiblePrimitive.Content
115
115
  className={cn(
116
116
  'overflow-hidden',
117
- 'data-[state=closed]:animate-collapsible-up',
118
- 'data-[state=open]:animate-collapsible-down',
117
+ 'data-closed:animate-collapsible-up',
118
+ 'data-open:animate-collapsible-down',
119
119
  className
120
120
  )}
121
121
  {...props}
@@ -127,7 +127,7 @@ const CollapsibleContent = ({ className, children, ...props }) => (
127
127
  // Simple usage
128
128
  const SimpleCollapsible = ({ title, children }) => (
129
129
  <Collapsible>
130
- <CollapsibleTrigger className="flex items-center justify-between w-full py-2 font-medium hover:underline [&[data-state=open]>svg]:rotate-180">
130
+ <CollapsibleTrigger className="flex items-center justify-between w-full py-2 font-medium hover:underline [&[data-open]>svg]:rotate-180">
131
131
  {title}
132
132
  <ChevronDown className="h-4 w-4 shrink-0 transition-transform duration-200" />
133
133
  </CollapsibleTrigger>
@@ -140,7 +140,7 @@ const SimpleCollapsible = ({ title, children }) => (
140
140
  // With border styling
141
141
  const CollapsibleCard = ({ title, defaultOpen = false, children }) => (
142
142
  <Collapsible defaultOpen={defaultOpen} className="rounded-lg border border-border">
143
- <CollapsibleTrigger className="flex items-center justify-between w-full p-4 font-medium [&[data-state=open]>svg]:rotate-180">
143
+ <CollapsibleTrigger className="flex items-center justify-between w-full p-4 font-medium [&[data-open]>svg]:rotate-180">
144
144
  {title}
145
145
  <ChevronDown className="h-4 w-4 shrink-0 text-muted-foreground transition-transform duration-200" />
146
146
  </CollapsibleTrigger>
@@ -155,7 +155,7 @@ const CollapsibleCard = ({ title, defaultOpen = false, children }) => (
155
155
  // Sidebar navigation example
156
156
  const SidebarGroup = ({ label, items, defaultOpen = true }) => (
157
157
  <Collapsible defaultOpen={defaultOpen} className="space-y-2">
158
- <CollapsibleTrigger className="flex items-center justify-between w-full px-2 py-1.5 text-sm font-semibold text-muted-foreground hover:text-foreground [&[data-state=open]>svg]:rotate-180">
158
+ <CollapsibleTrigger className="flex items-center justify-between w-full px-2 py-1.5 text-sm font-semibold text-muted-foreground hover:text-foreground [&[data-open]>svg]:rotate-180">
159
159
  {label}
160
160
  <ChevronDown className="h-4 w-4 shrink-0 transition-transform duration-200" />
161
161
  </CollapsibleTrigger>
@@ -88,7 +88,7 @@ interface ComboboxProps {
88
88
  ```
89
89
 
90
90
  ## Do
91
- - Use Radix Combobox or cmdk for accessibility
91
+ - Use Base UI Combobox or cmdk for accessibility
92
92
  - Support type-ahead filtering
93
93
  - Clear search on selection (single) or keep (multi)
94
94
  - Show loading state while filtering
@@ -123,7 +123,7 @@ const Combobox = ({
123
123
 
124
124
  return (
125
125
  <Popover open={open} onOpenChange={setOpen}>
126
- <PopoverTrigger asChild>
126
+ <PopoverTrigger render={<Button variant="outline" role="combobox" aria-expanded={open} className="w-full justify-between" />}>
127
127
  <Button
128
128
  variant="outline"
129
129
  role="combobox"
@@ -19,9 +19,9 @@
19
19
  z-50 min-w-[8rem] overflow-hidden {tokens.radius} border border-border
20
20
  bg-background p-1 text-foreground {tokens.shadow}
21
21
  animate-in fade-in-80
22
- data-[state=open]:animate-in data-[state=closed]:animate-out
23
- data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0
24
- data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95
22
+ data-open:animate-in data-closed:animate-out
23
+ data-closed:fade-out-0 data-open:fade-in-0
24
+ data-closed:zoom-out-95 data-open:zoom-in-95
25
25
  data-[side=bottom]:slide-in-from-top-2
26
26
  data-[side=left]:slide-in-from-right-2
27
27
  data-[side=right]:slide-in-from-left-2
@@ -82,14 +82,14 @@ ml-auto text-xs tracking-widest text-muted-foreground
82
82
  flex cursor-pointer select-none items-center {tokens.radius} px-2 py-1.5
83
83
  text-sm outline-none
84
84
  focus:bg-muted
85
- data-[state=open]:bg-muted
85
+ data-open:bg-muted
86
86
  ```
87
87
 
88
88
  ### Sub Content
89
89
  ```
90
90
  z-50 min-w-[8rem] overflow-hidden {tokens.radius} border border-border
91
91
  bg-background p-1 {tokens.shadow}
92
- data-[state=open]:animate-in data-[state=closed]:animate-out
92
+ data-open:animate-in data-closed:animate-out
93
93
  ```
94
94
 
95
95
  ## Props Interface
@@ -102,7 +102,7 @@ interface ContextMenuProps {
102
102
  interface ContextMenuTriggerProps {
103
103
  children: React.ReactNode
104
104
  disabled?: boolean
105
- asChild?: boolean
105
+ render?: React.ReactElement
106
106
  }
107
107
 
108
108
  interface ContextMenuContentProps {
@@ -122,7 +122,7 @@ interface ContextMenuItemProps {
122
122
  ```
123
123
 
124
124
  ## Do
125
- - Use Radix ContextMenu for accessibility
125
+ - Use Base UI Menu for accessibility
126
126
  - Support all item types (checkbox, radio, sub-menus)
127
127
  - Show keyboard shortcuts
128
128
  - Handle touch devices (long-press)
@@ -135,7 +135,7 @@ interface ContextMenuItemProps {
135
135
 
136
136
  ## Example
137
137
  ```tsx
138
- import * as ContextMenuPrimitive from '@radix-ui/react-context-menu'
138
+ import { Menu as ContextMenuPrimitive } from '@base-ui/react/menu'
139
139
  import { Check, ChevronRight, Circle } from 'lucide-react'
140
140
  import { cn } from '@/lib/utils'
141
141
 
@@ -152,9 +152,9 @@ const ContextMenuContent = ({ className, ...props }) => (
152
152
  className={cn(
153
153
  'z-50 min-w-[8rem] overflow-hidden rounded-md border border-border bg-background p-1 shadow-md',
154
154
  'animate-in fade-in-80',
155
- 'data-[state=open]:animate-in data-[state=closed]:animate-out',
156
- 'data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0',
157
- 'data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95',
155
+ 'data-open:animate-in data-closed:animate-out',
156
+ 'data-closed:fade-out-0 data-open:fade-in-0',
157
+ 'data-closed:zoom-out-95 data-open:zoom-in-95',
158
158
  'data-[side=bottom]:slide-in-from-top-2',
159
159
  className
160
160
  )}
@@ -11,8 +11,8 @@
11
11
  ### Overlay
12
12
  ```
13
13
  fixed inset-0 z-50 bg-black/80
14
- data-[state=open]:animate-in data-[state=closed]:animate-out
15
- data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0
14
+ data-open:animate-in data-closed:animate-out
15
+ data-closed:fade-out-0 data-open:fade-in-0
16
16
  ```
17
17
 
18
18
  ### Content
@@ -20,11 +20,11 @@ data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0
20
20
  fixed left-1/2 top-1/2 z-50 grid w-full max-w-lg -translate-x-1/2 -translate-y-1/2
21
21
  gap-4 border border-border bg-background p-6 {tokens.shadow} {tokens.radius}
22
22
  duration-200
23
- data-[state=open]:animate-in data-[state=closed]:animate-out
24
- data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0
25
- data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95
26
- data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%]
27
- data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%]
23
+ data-open:animate-in data-closed:animate-out
24
+ data-closed:fade-out-0 data-open:fade-in-0
25
+ data-closed:zoom-out-95 data-open:zoom-in-95
26
+ data-closed:slide-out-to-left-1/2 data-closed:slide-out-to-top-[48%]
27
+ data-open:slide-in-from-left-1/2 data-open:slide-in-from-top-[48%]
28
28
  ```
29
29
 
30
30
  ### Header
@@ -53,7 +53,7 @@ absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background
53
53
  transition-opacity hover:opacity-100
54
54
  focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2
55
55
  disabled:pointer-events-none
56
- data-[state=open]:bg-accent data-[state=open]:text-muted-foreground
56
+ data-open:bg-accent data-open:text-muted-foreground
57
57
  ```
58
58
 
59
59
  ## Alert Dialog Variant (for destructive confirmations)
@@ -112,7 +112,7 @@ interface DialogDescriptionProps {
112
112
 
113
113
  ## Example
114
114
  ```tsx
115
- import * as DialogPrimitive from '@radix-ui/react-dialog'
115
+ import { Dialog as DialogPrimitive } from '@base-ui/react/dialog'
116
116
  import { X } from 'lucide-react'
117
117
  import { cn } from '@/lib/utils'
118
118
 
@@ -125,8 +125,8 @@ const DialogOverlay = ({ className, ...props }) => (
125
125
  <DialogPrimitive.Overlay
126
126
  className={cn(
127
127
  'fixed inset-0 z-50 bg-black/80',
128
- 'data-[state=open]:animate-in data-[state=closed]:animate-out',
129
- 'data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0',
128
+ 'data-open:animate-in data-closed:animate-out',
129
+ 'data-closed:fade-out-0 data-open:fade-in-0',
130
130
  className
131
131
  )}
132
132
  {...props}
@@ -140,9 +140,9 @@ const DialogContent = ({ className, children, ...props }) => (
140
140
  className={cn(
141
141
  'fixed left-1/2 top-1/2 z-50 grid w-full max-w-lg -translate-x-1/2 -translate-y-1/2',
142
142
  'gap-4 border border-border bg-background p-6 shadow-lg rounded-lg duration-200',
143
- 'data-[state=open]:animate-in data-[state=closed]:animate-out',
144
- 'data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0',
145
- 'data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95',
143
+ 'data-open:animate-in data-closed:animate-out',
144
+ 'data-closed:fade-out-0 data-open:fade-in-0',
145
+ 'data-closed:zoom-out-95 data-open:zoom-in-95',
146
146
  className
147
147
  )}
148
148
  {...props}
@@ -174,8 +174,8 @@ const DialogDescription = ({ className, ...props }) => (
174
174
 
175
175
  // Usage
176
176
  <Dialog>
177
- <DialogTrigger asChild>
178
- <Button variant="outline">Edit Profile</Button>
177
+ <DialogTrigger render={<Button variant="outline" />}>
178
+ Edit Profile
179
179
  </DialogTrigger>
180
180
  <DialogContent>
181
181
  <DialogHeader>