diff --git a/CLAUDE.md b/CLAUDE.md
index f4bcdbf0..62a4eae6 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -6,7 +6,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
Faency is a React component library and design system for Traefik Labs, built with React, TypeScript, Stitches CSS-in-JS, and Radix UI Primitives. It provides accessible, themed components with light/dark mode support.
-**Migration Status**: Currently migrating from Stitches to vanilla-extract (Phase 2 complete). Most components use Stitches (`.tsx`), some have vanilla-extract versions (`.vanilla.tsx`). Prefer editing Stitches versions unless explicitly migrating. See `VANILLA_EXTRACT_MIGRATION.md` for migration status and `VANILLA_EXTRACT_DEVELOPER_GUIDE.md` for comprehensive migration instructions.
+**Migration Status**: Currently migrating from Stitches to vanilla-extract (Phase 3 in progress). Most components use Stitches (`.tsx`), some have vanilla-extract versions (`.vanilla.tsx`). Prefer editing Stitches versions unless explicitly migrating. See `VANILLA_EXTRACT_MIGRATION.md` for migration status and `VANILLA_EXTRACT_DEVELOPER_GUIDE.md` for comprehensive migration instructions.
## Development Commands
diff --git a/README.md b/README.md
index 1d675c39..7d0c25e2 100644
--- a/README.md
+++ b/README.md
@@ -2,7 +2,7 @@
This is the React component library and design system for [Traefik Labs](https://traefik.io).
-Built with React, Typescript, [Stitches](https://github.com/modulz/stitches) and [Radix UI Primitives](https://radix-ui.com/primitives/docs/overview/introduction).
+Built with React, Typescript, [Stitches](https://github.com/modulz/stitches), [vanilla-extract](https://vanilla-extract.style/) (migration in progress), and [Radix UI Primitives](https://radix-ui.com/primitives/docs/overview/introduction).
## Demo (Storybook)
@@ -46,17 +46,11 @@ const Container = styled(Flex, {
const MyComponent = () => {children};
```
-#### Using Vanilla Extract Components (New - Recommended)
+#### Using Vanilla Extract Components (Migration in Progress)
-For better performance with static CSS, use the new Vanilla Extract components:
+For better performance with static CSS, you can use the new Vanilla Extract components (many components are available, more being migrated).
-1. Import the CSS file in your app's entry point:
-
-```jsx
-import '@traefiklabs/faency/dist/style.css';
-```
-
-2. Wrap your app with the VanillaExtractThemeProvider:
+Wrap your app with the VanillaExtractThemeProvider:
```jsx
import { VanillaExtractThemeProvider } from '@traefiklabs/faency';
@@ -71,11 +65,11 @@ const App = () => (
);
```
-> **Note**: Vanilla Extract components use static CSS generated at build time, providing better performance than runtime CSS-in-JS. Components with the `Vanilla` suffix (e.g., `BadgeVanilla`, `BoxVanilla`) require the CSS import above.
+> **Note**: Vanilla Extract components use static CSS generated at build time, providing better performance than runtime CSS-in-JS. CSS is automatically included when you import components - no separate CSS import needed. Components with the `Vanilla` suffix (e.g., `BadgeVanilla`, `BoxVanilla`) are available. Not all components have Vanilla Extract versions yet - check the component documentation or use the Stitches version if a Vanilla version is not available.
## How to contribute
-- Make sure you have Node 18+, or if you prefer, you can work in a Docker container:
+- Make sure you have Node 20+, or if you prefer, you can work in a Docker container:
```sh
docker run -it -v $(pwd):/usr/local/src/ -w /usr/local/src/ -p 3000:3000 node:latest bash
diff --git a/USER_GUIDE.md b/USER_GUIDE.md
index ec196010..189cff71 100644
--- a/USER_GUIDE.md
+++ b/USER_GUIDE.md
@@ -63,13 +63,12 @@ function App() {
export default App;
```
-#### Using Vanilla Extract Components (New - Recommended)
+#### Using Vanilla Extract Components (Migration in Progress)
-For Vanilla Extract components, import the CSS file and use the `VanillaExtractThemeProvider`:
+For Vanilla Extract components, use the `VanillaExtractThemeProvider`:
```tsx
import React from 'react';
-import '@traefiklabs/faency/dist/style.css'; // Required for Vanilla Extract components
import { VanillaExtractThemeProvider } from '@traefiklabs/faency';
import { BoxVanilla, BadgeVanilla } from '@traefiklabs/faency';
@@ -86,21 +85,17 @@ function App() {
export default App;
```
+> **Note**: CSS is automatically included when you import Vanilla Extract components - no separate CSS import needed.
+
### Import Styles
#### For Stitches Components (Legacy)
Stitches components use runtime CSS-in-JS, so no separate CSS imports are needed. Styles are automatically injected when you use components.
-#### For Vanilla Extract Components (New - Recommended)
-
-Vanilla Extract components require importing the static CSS file. Add this import to your application's entry point (e.g., `App.tsx` or `index.tsx`):
-
-```tsx
-import '@traefiklabs/faency/dist/style.css';
-```
+#### For Vanilla Extract Components (Migration in Progress)
-This CSS file contains all the styles for Vanilla Extract components (components with `Vanilla` suffix like `BadgeVanilla`, `BoxVanilla`, etc.). Without this import, these components will render as unstyled elements.
+Vanilla Extract components include their CSS automatically when imported. No separate CSS import is required - styles are bundled with each component module.
---
diff --git a/VANILLA_EXTRACT_DEVELOPER_GUIDE.md b/VANILLA_EXTRACT_DEVELOPER_GUIDE.md
index cda067a1..3486440f 100644
--- a/VANILLA_EXTRACT_DEVELOPER_GUIDE.md
+++ b/VANILLA_EXTRACT_DEVELOPER_GUIDE.md
@@ -14,12 +14,11 @@ This guide explains how the new Vanilla Extract styling system works and provide
**Reference:**
-- [Code Examples](#code-examples)
- [Common Patterns](#common-patterns)
- [Troubleshooting](#troubleshooting)
- [Migration Checklist](#migration-checklist)
- [Best Practices](#best-practices)
-- [Future Migration Phases](#reference-future-migration-phases) - Phase 2 & 3 (not yet actionable)
+- [Future Migration Phases](#reference-future-migration-phases)
---
@@ -138,16 +137,14 @@ When migrating a component, follow these strict naming conventions to maintain c
#### File Naming
-- **Styles**: `ComponentName.vanilla.css.ts` (e.g., [`Text.vanilla.css.ts`](components/Text/Text.vanilla.css.ts))
-- **Component**: `ComponentName.vanilla.tsx` (e.g., [`Text.vanilla.tsx`](components/Text/Text.vanilla.tsx))
-- **Tests**: `ComponentName.vanilla.test.tsx` (e.g., [`Text.vanilla.test.tsx`](components/Text/Text.vanilla.test.tsx))
-- **Theme (if needed)**: `ComponentName.theme.ts` + `ComponentName.theme.css.ts` (see Badge example)
+- **Styles**: `ComponentName.vanilla.css.ts`
+- **Component**: `ComponentName.vanilla.tsx`
+- **Tests**: `ComponentName.vanilla.test.tsx`
+- **Theme (if needed)**: `ComponentName.theme.ts` + `ComponentName.theme.css.ts`
#### Component Naming
-**Reference Implementation:**
-
-See [`components/Text/Text.vanilla.tsx`](components/Text/Text.vanilla.tsx) for the complete naming pattern:
+The complete naming pattern (see [Text.vanilla.tsx](components/Text/Text.vanilla.tsx)):
- Internal type: `TextComponent` (for typing the implementation)
- Exported props type: `TextVanillaProps` (for consumers)
@@ -156,7 +153,7 @@ See [`components/Text/Text.vanilla.tsx`](components/Text/Text.vanilla.tsx) for t
**Key Points:**
-- Use `Omit` not `NonNullable` (see Badge component)
+- Use `NonNullable` for variant types
- Internal implementation uses `ComponentImpl` suffix
- Exported component name is `ComponentVanilla`
- Exported props type is `ComponentVanillaProps`
@@ -164,9 +161,7 @@ See [`components/Text/Text.vanilla.tsx`](components/Text/Text.vanilla.tsx) for t
#### Exports in index.ts
-**Reference:**
-
-See [`components/Text/index.ts`](components/Text/index.ts) for the export pattern:
+Export pattern to avoid naming conflicts:
```typescript
export * from './Text'; // Stitches version
@@ -174,12 +169,6 @@ export type { TextVanillaProps } from './Text.vanilla';
export { TextVanilla } from './Text.vanilla';
```
-**Why explicit exports?**
-
-- Avoids naming conflicts between Stitches and vanilla-extract versions
-- Makes it clear which version is being used
-- Prevents accidental type collisions
-
#### Recipe Naming
Use camelCase + "Recipe" suffix in `.vanilla.css.ts` files:
@@ -190,19 +179,11 @@ export const badgeRecipe = recipe({...});
export const buttonRecipe = recipe({...});
```
-See [`components/Button/Button.vanilla.css.ts`](components/Button/Button.vanilla.css.ts) for examples.
-
### Step-by-Step Guide
#### 1. Create the Vanilla Extract Styles File
-Create a new file `ComponentName.vanilla.css.ts` alongside your component.
-
-**Reference Implementation:**
-
-- Simple component: [`components/Box/Box.vanilla.css.ts`](components/Box/Box.vanilla.css.ts)
-- Component with variants: [`components/Button/Button.vanilla.css.ts`](components/Button/Button.vanilla.css.ts)
-- Component with recipes: [`components/Badge/Badge.vanilla.css.ts`](components/Badge/Badge.vanilla.css.ts)
+Create `ComponentName.vanilla.css.ts` alongside your component.
**Key patterns:**
@@ -211,18 +192,11 @@ Create a new file `ComponentName.vanilla.css.ts` alongside your component.
- Use `recipe()` for variant-based styles
- Reference theme tokens for themeable values
-#### 2. Create the React Component
-
-Create `ComponentName.vanilla.tsx`.
-
-**Reference Implementations:**
+**Examples:** [Box.vanilla.css.ts](components/Box/Box.vanilla.css.ts) (simple), [Button.vanilla.css.ts](components/Button/Button.vanilla.css.ts) (variants), [Badge.vanilla.css.ts](components/Badge/Badge.vanilla.css.ts) (recipes)
-- **Simple component**: [`components/Box/Box.vanilla.tsx`](components/Box/Box.vanilla.tsx)
-- **Button with variants**: [`components/Button/Button.vanilla.tsx`](components/Button/Button.vanilla.tsx)
-- **Polymorphic component**: [`components/Badge/Badge.vanilla.tsx`](components/Badge/Badge.vanilla.tsx)
-- **Component with theme tokens**: [`components/Text/Text.vanilla.tsx`](components/Text/Text.vanilla.tsx)
+#### 2. Create the React Component
-**Required steps:**
+Create `ComponentName.vanilla.tsx` with these required steps:
1. Import `CSSProps`, `processCSSProp` from `styles/cssProps`
2. Import `useVanillaExtractTheme` from `styles/themeContext`
@@ -232,43 +206,47 @@ Create `ComponentName.vanilla.tsx`.
6. Merge styles with `assignInlineVars(vars)`
7. Set `displayName` to match component name
-#### 3. Add Comparison Story
-
-**Required:** Every migrated component MUST have a Comparison story that shows both versions side-by-side.
+**Examples:** [Box.vanilla.tsx](components/Box/Box.vanilla.tsx) (simple), [Button.vanilla.tsx](components/Button/Button.vanilla.tsx) (variants), [Badge.vanilla.tsx](components/Badge/Badge.vanilla.tsx) (polymorphic), [Text.vanilla.tsx](components/Text/Text.vanilla.tsx) (theme tokens)
-**Reference Implementation:**
+#### 3. Add Comparison Story
-See [`components/Text/Text.stories.tsx`](components/Text/Text.stories.tsx) - Look for the `Comparison` export which shows Stitches vs Vanilla Extract side-by-side.
+**Required:** Every migrated component MUST have a Comparison story showing both versions side-by-side. Use vanilla-extract layout components (BoxVanilla, FlexVanilla, H3Vanilla) to avoid mixing systems.
**Pattern:**
```tsx
-// Import vanilla layout components
import { BoxVanilla } from '../Box/Box.vanilla';
import { FlexVanilla } from '../Flex/Flex.vanilla';
-import { H3 } from '../Heading';
+import { H3Vanilla } from '../Heading';
import { ComponentNameVanilla } from './ComponentName.vanilla';
export const Comparison: StoryFn = () => (
-
Stitches Version
- {/* Show all major variants */}
+ Stitches Version
+
+
+ Option 1
+ Option 2
+
+
-
Vanilla Extract Version
- {/* Mirror the exact same variants */}
+ Vanilla Extract Version
+
+
+ Option 1
+ Option 2
+
+
);
```
-**Why this matters:**
+**Example:** See [Text.stories.tsx](components/Text/Text.stories.tsx) for a complete implementation with all variants.
-- Visual verification that both versions render identically
-- Easy to spot visual regressions
-- Tests light/dark theme switching for both versions
-- Documents all key variants in one place
+**Benefits:** Visual verification of parity, easy regression spotting, theme switching testing, variant documentation.
#### 4. Test Theme Switching
@@ -288,52 +266,40 @@ After adding the Comparison story:
#### 5. Create Tests for Vanilla Extract Component
-**REQUIRED:** Every migrated component MUST have a corresponding `.vanilla.test.tsx` file with comprehensive tests.
-
-**Reference Implementation:**
-
-See [`components/Input/Input.vanilla.test.tsx`](components/Input/Input.vanilla.test.tsx) for the gold standard testing pattern.
-
-Other examples:
-
-- [`components/Button/Button.vanilla.test.tsx`](components/Button/Button.vanilla.test.tsx)
-- [`components/Text/Text.vanilla.test.tsx`](components/Text/Text.vanilla.test.tsx)
-- [`components/Label/Label.vanilla.test.tsx`](components/Label/Label.vanilla.test.tsx)
+**REQUIRED:** Every migrated component MUST have a `.vanilla.test.tsx` file with comprehensive tests.
**Required test coverage:**
-- Basic rendering and element types
-- Custom className support
-- All variant props (size, weight, variant, etc.)
-- Custom styling (CSS prop and style prop)
-- Style merging behavior
-- Polymorphic rendering (if applicable)
-- Ref forwarding
-- HTML attribute pass-through
-- Accessibility testing with jest-axe
-- Theme support (light/dark and different primary colors)
+- ✅ Basic rendering and element type
+- ✅ All variant props
+- ✅ Custom className
+- ✅ Style prop
+- ✅ CSS prop (with token processing)
+- ✅ Style + CSS prop merging
+- ✅ Polymorphic rendering (if applicable)
+- ✅ Ref forwarding
+- ✅ HTML attribute pass-through
+- ✅ Accessibility (axe violations with jest-axe)
+- ✅ Light/dark theme switching
**Important notes:**
- Use `unmount()` in loops to prevent "multiple elements found" errors
- Always wrap components in `VanillaExtractThemeProvider`
- For Radix-based components, use the correct role (e.g., `role="radio"` for ButtonSwitch items)
-- Test files should be named `ComponentName.vanilla.test.tsx`
-
-#### 6. Export the Component
-Update the component's exports following the [Export Strategy](#export-and-build-strategy).
+**Examples:** [Input.vanilla.test.tsx](components/Input/Input.vanilla.test.tsx) (gold standard), [Button.vanilla.test.tsx](components/Button/Button.vanilla.test.tsx), [Badge.vanilla.test.tsx](components/Badge/Badge.vanilla.test.tsx)
-**Reference:**
+#### 6. Export the Component
-See [`components/Button/index.ts`](components/Button/index.ts) for the export pattern.
+Update both the component's `index.ts` and main [index.ts](index.ts):
```tsx
export { Button } from './Button'; // Stitches (existing)
export { ButtonVanilla } from './Button.vanilla'; // Vanilla Extract (new)
```
-Also update the main [`index.ts`](index.ts).
+See [Export and Build Strategy](#export-and-build-strategy) for more details.
#### 7. Clean Up (After Full Migration)
@@ -352,143 +318,76 @@ After successful migration and verification:
## Export and Build Strategy
-### Overview
-
-We're migrating from Stitches to Vanilla Extract in three phases, maintaining backward compatibility throughout. The build system is already configured to handle both systems simultaneously.
-
-### What Developers Need to Know
-
**Current Phase: Side-by-Side Exports (Phase 1)**
-We export both versions of migrated components with clear naming conventions:
+We export both versions with clear naming conventions:
-- Original Stitches components keep their current names (e.g., `Box`)
-- New Vanilla Extract versions use a `*Vanilla` suffix (e.g., `BoxVanilla`)
-- Both are exported and available to consumers
+- Original Stitches: `Box`, `Button`, `Text`
+- Vanilla Extract: `BoxVanilla`, `ButtonVanilla`, `TextVanilla`
-This approach allows:
+**Benefits:** Zero breaking changes, gradual opt-in, easy comparison, feature parity verification.
-- Zero breaking changes for existing users
-- Gradual opt-in for early adopters
-- Easy visual comparison and testing in Storybook
-- Time to verify feature parity before switching
+**Trade-off:** Larger bundle size (~80KB) during transition.
-Trade-off: Larger bundle size (~80KB total) during the transition period.
+### Exporting a Migrated Component
----
+1. **Update component's index.ts:**
-### Developer Workflow: Exporting a Migrated Component
+ ```typescript
+ export { Button } from './Button'; // Stitches (keep)
+ export { ButtonVanilla } from './Button.vanilla'; // Vanilla Extract (add)
+ ```
-When you migrate a component, follow this export pattern:
-
-#### 1. Update Component's Local Index
-
-See [`components/Button/index.ts`](components/Button/index.ts) for example:
-
-```typescript
-export { Button } from './Button'; // Stitches (keep existing)
-export { ButtonVanilla } from './Button.vanilla'; // Vanilla Extract (add new)
-```
+2. **Update main [index.ts](index.ts)** to export both versions
-#### 2. Update Main Library Index
+3. **Add Comparison story** (see [Step 3](#3-add-comparison-story))
-Update [`index.ts`](index.ts) to export both versions.
+4. **Document in CHANGELOG.md:**
+ ```markdown
+ ### Added
-#### 3. Add Comparison Stories
+ - `ButtonVanilla`: Vanilla Extract version of Button component
+ ```
-See Step 3 in the migration process above.
-
-#### 4. Document the Addition
-
-Add an entry to `CHANGELOG.md` under "Added":
-
-```markdown
-### Added
-
-- `ButtonVanilla`: Vanilla Extract version of Button component
-```
-
-**Important**: No version bump needed during Phase 1 - this is backward compatible.
+**Note:** No version bump needed during Phase 1 - this is backward compatible.
---
## Migrating Component-Specific Theme Tokens
-Some components have their own theme tokens (e.g., `Badge.themes.ts`). Here's how to migrate them.
-
-### The New Approach
-
-In Vanilla Extract, component theme tokens are **co-located** with the component and merged into the global theme system while **preserving the original token names** to avoid breaking changes.
-
-**⚠️ IMPORTANT: Preserve Token Names**
-
-When migrating component-specific theme tokens, you **MUST preserve the exact same token names** from the Stitches version to avoid breaking changes.
+**⚠️ IMPORTANT:** Preserve the exact same token names from the Stitches version to avoid breaking changes.
-### Reference Implementations
+### Approach
-**Simple theme tokens:**
+Component theme tokens are co-located with the component and merged into the global theme system.
-See [`components/Badge/Badge.theme.css.ts`](components/Badge/Badge.theme.css.ts) - Tokens without runtime color references.
+**Simple tokens** (no runtime color references): Create `ComponentName.theme.css.ts` - see [Badge.theme.css.ts](components/Badge/Badge.theme.css.ts)
-**Complex theme tokens with runtime colors:**
+**Complex tokens** (with runtime color references): Create two files to avoid circular dependencies:
-See [`components/Text/Text.theme.ts`](components/Text/Text.theme.ts) and [`components/Text/Text.theme.css.ts`](components/Text/Text.theme.css.ts) - Pattern for tokens that reference other theme colors.
+- `ComponentName.theme.ts` (plain TypeScript) - see [Text.theme.ts](components/Text/Text.theme.ts)
+- `ComponentName.theme.css.ts` (re-export wrapper) - see [Text.theme.css.ts](components/Text/Text.theme.css.ts)
-### Step-by-Step Process
+### Steps
-#### 1. Create Component Theme File
+1. **Create component theme file(s)** (see examples above)
-**For simple tokens** (no runtime color references):
+2. **Add to [styles/tokens.css.ts](styles/tokens.css.ts)** in the `colors` object (not nested)
-Create `ComponentName.theme.css.ts` - see [`components/Badge/Badge.theme.css.ts`](components/Badge/Badge.theme.css.ts)
+3. **Merge into [styles/themes.css.ts](styles/themes.css.ts):**
-**For complex tokens** (with runtime color references):
+ ```typescript
+ import { badgeLightTheme, badgeDarkTheme } from '../components/Badge/Badge.theme.css';
-Create two files to avoid circular dependencies:
+ const lightSemanticColors = {
+ // ... other colors ...
+ ...badgeLightTheme, // Merge component theme
+ };
+ ```
-- `ComponentName.theme.ts` (plain TypeScript) - see [`components/Text/Text.theme.ts`](components/Text/Text.theme.ts)
-- `ComponentName.theme.css.ts` (re-export wrapper) - see [`components/Text/Text.theme.css.ts`](components/Text/Text.theme.css.ts)
+4. **Use in component styles:** Reference tokens from `tokens.colors` in your `.vanilla.css.ts` file
-#### 2. Add to Global Theme Contract
-
-Add the tokens to [`styles/tokens.css.ts`](styles/tokens.css.ts) in the `colors` object (not nested).
-
-#### 3. Merge into Global Theme Implementations
-
-See [`styles/themes.css.ts`](styles/themes.css.ts) for how component themes are merged:
-
-```typescript
-import { badgeLightTheme, badgeDarkTheme } from '../components/Badge/Badge.theme.css';
-
-const lightSemanticColors = {
- // ... other colors ...
- ...badgeLightTheme, // Merge component theme
-};
-```
-
-For runtime color overrides, see the Text component section in [`styles/themes.css.ts`](styles/themes.css.ts).
-
-#### 4. Use in Component Styles
-
-Reference tokens from `tokens.colors` in your component's `.vanilla.css.ts` file.
-
-See [`components/Badge/Badge.vanilla.css.ts`](components/Badge/Badge.vanilla.css.ts) for usage examples.
-
-#### 5. Clean Up
-
-After migration, delete the old `ComponentName.themes.ts` file.
-
----
-
-## Code Examples
-
-See the step-by-step migration guide above for file references. Key reference implementations:
-
-- **Simple component**: [`components/Box/Box.vanilla.tsx`](components/Box/Box.vanilla.tsx)
-- **Component with variants**: [`components/Badge/Badge.vanilla.tsx`](components/Badge/Badge.vanilla.tsx)
-- **Component with theme tokens**: [`components/Text/Text.vanilla.tsx`](components/Text/Text.vanilla.tsx)
-- **Polymorphic component**: [`components/Badge/Badge.vanilla.tsx`](components/Badge/Badge.vanilla.tsx)
-- **Component with tests**: [`components/Input/Input.vanilla.test.tsx`](components/Input/Input.vanilla.test.tsx)
+5. **Clean up:** Delete old `ComponentName.themes.ts` file
---
@@ -496,7 +395,7 @@ See the step-by-step migration guide above for file references. Key reference im
### Using Design Tokens
-Always use tokens from the theme contract for themeable values. See [`styles/tokens.css.ts`](styles/tokens.css.ts) for available tokens.
+Always use tokens from [styles/tokens.css.ts](styles/tokens.css.ts) for themeable values:
```tsx
// ✅ Good - uses theme tokens
@@ -508,16 +407,10 @@ backgroundColor: '#ffffff',
### Migrating from `asChild` to Polymorphic `as`
-Replace `asChild` pattern with polymorphic `as` prop.
-
-**Reference Implementation:**
-
-See [`components/Badge/Badge.vanilla.tsx`](components/Badge/Badge.vanilla.tsx) for the complete polymorphic pattern.
-
-**Migration steps:**
+Replace `asChild` pattern with polymorphic `as` prop (see [Badge.vanilla.tsx](components/Badge/Badge.vanilla.tsx)):
1. Remove `@radix-ui/react-slot` dependency
-2. Import polymorphic types from [`styles/polymorphic.ts`](styles/polymorphic.ts)
+2. Import polymorphic types from [styles/polymorphic.ts](styles/polymorphic.ts)
3. Replace `asChild?: boolean` with `as` prop using generic type parameter
4. Use `PolymorphicComponentProps` for props type
5. Cast implementation to `PolymorphicComponent` type
@@ -530,19 +423,13 @@ import { useVanillaExtractTheme } from '../../styles/themeContext';
const { colors, resolvedTheme } = useVanillaExtractTheme();
```
-See any vanilla component for usage examples.
-
### Using Shared Text Variants (DRY Pattern)
-For Text-based components needing identical variant behavior, import from [`styles/textVariants.css.ts`](styles/textVariants.css.ts).
-
-**Reference Implementations:**
+For Text-based components needing identical variant behavior, import from [styles/textVariants.css.ts](styles/textVariants.css.ts).
-- [`components/Text/Text.vanilla.css.ts`](components/Text/Text.vanilla.css.ts)
-- [`components/Label/Label.vanilla.css.ts`](components/Label/Label.vanilla.css.ts)
-- [`components/Blockquote/Blockquote.vanilla.css.ts`](components/Blockquote/Blockquote.vanilla.css.ts)
+**Benefits:** Single source of truth, ~70% code reduction, consistency.
-**Benefits:** Single source of truth, ~70% code reduction, consistency
+**Examples:** [Text.vanilla.css.ts](components/Text/Text.vanilla.css.ts), [Label.vanilla.css.ts](components/Label/Label.vanilla.css.ts), [Blockquote.vanilla.css.ts](components/Blockquote/Blockquote.vanilla.css.ts)
**Don't use** if component needs custom variant behavior.
@@ -552,11 +439,11 @@ For Text-based components needing identical variant behavior, import from [`styl
### Theme Not Applied
-Use theme tokens instead of hardcoded values. See [`styles/tokens.css.ts`](styles/tokens.css.ts) for available tokens.
+Use theme tokens from [styles/tokens.css.ts](styles/tokens.css.ts) instead of hardcoded values.
### CSS Prop Not Working
-Ensure you follow the pattern in any vanilla component (e.g., [`components/Button/Button.vanilla.tsx`](components/Button/Button.vanilla.tsx)):
+Follow the pattern in vanilla components (see [Button.vanilla.tsx](components/Button/Button.vanilla.tsx)):
1. Add `CSSProps` to component interface
2. Import and use `processCSSProp(css, colors)` from `useVanillaExtractTheme()`
@@ -565,37 +452,55 @@ Ensure you follow the pattern in any vanilla component (e.g., [`components/Butto
### TypeScript Errors
-#### Problem: RecipeVariants returns undefined
-
-Always wrap `RecipeVariants` with `NonNullable`. See any vanilla component for the pattern.
+#### RecipeVariants returns undefined
-#### Problem: Stitches components in vanilla components cause type errors
+Always wrap `RecipeVariants` with `NonNullable`:
-**CRITICAL RULE:** Never mix Stitches and vanilla-extract components. Always use vanilla-extract versions inside vanilla components.
+```tsx
+// ❌ Bad - may return undefined
+type MyComponentVariants = RecipeVariants;
-See [`components/TextField/TextField.vanilla.tsx`](components/TextField/TextField.vanilla.tsx) for an example of using vanilla components.
+// ✅ Good - guaranteed to have variant properties
+type MyComponentVariants = NonNullable>;
+```
-#### Problem: Props typed as `CSSProps` require `as any` in tests
+#### Invalid selector error for child elements
-**Root cause:** The prop is typed as `CSSProps` but should be `CSSProps['css']`.
+**Problem:** `Invalid selector: &:not(:empty) > *` - Vanilla-extract only allows `&` with pseudo-classes/elements in `style()`.
-**Reference:** See [`components/Textarea/Textarea.vanilla.tsx`](components/Textarea/Textarea.vanilla.tsx) for correct typing of additional CSS props.
+**Solution:** Use `globalStyle()` for child selectors:
-**Understanding CSSProps property names:**
+```tsx
+// ❌ Bad - causes error
+const skeleton = style({
+ selectors: { '&:not(:empty) > *': { visibility: 'hidden' } },
+});
-The [`styles/cssProps.ts`](styles/cssProps.ts) interface accepts both abbreviated and full property names.
+// ✅ Good - use globalStyle
+const skeleton = style({
+ selectors: { '&:not(:empty)': { maxWidth: 'fit-content' } },
+});
-```tsx
-// Both work identically:
- // Abbreviated
- // Full names
+globalStyle(`${skeleton}:not(:empty) > *`, { visibility: 'hidden' });
```
+**Rule:** Use `globalStyle()` for: `& > div`, `& h1`, `& + &` (children/siblings). Use `style()` for: `&:hover`, `&::before` (self-targeting).
+
+#### Mixing Stitches and vanilla-extract components
+
+**CRITICAL:** Never mix systems. Always use vanilla-extract versions inside vanilla components (see [TextField.vanilla.tsx](components/TextField/TextField.vanilla.tsx)).
+
+#### Props typed as `CSSProps` require `as any` in tests
+
+**Root cause:** Should be `CSSProps['css']` not `CSSProps`. See [Textarea.vanilla.tsx](components/Textarea/Textarea.vanilla.tsx) for correct typing.
+
+**Note:** `CSSProps` accepts both abbreviated (`p`, `m`) and full names (`padding`, `margin`).
+
### Build or Storybook Issues
-- **Build errors**: Check [`vite.config.ts`](vite.config.ts) has `vanillaExtractPlugin()`
-- **Theme not switching**: Verify [`.storybook/preview.jsx`](.storybook/preview.jsx) has `VanillaExtractThemeProvider`
-- **Color mismatch**: Compare tokens used in both versions, check Comparison story
+- **Build errors**: Check [vite.config.ts](vite.config.ts) has `vanillaExtractPlugin()`
+- **Theme not switching**: Verify [.storybook/preview.jsx](.storybook/preview.jsx) has `VanillaExtractThemeProvider`
+- **Color mismatch**: Compare tokens, check Comparison story
---
@@ -657,44 +562,30 @@ Use this checklist for each component migration:
- Test both light and dark themes
- Migrate one component at a time
-### Critical Pattern: Include All Base Styles in Recipes
+### Critical: Include All Base Styles in Recipes
**⚠️ IMPORTANT:** When recipes have variants, include ALL base styles (font sizes, dimensions, etc.) in the recipe's base array, not in separate style constants.
-**Why:** If styles are split and components conditionally apply recipe classes, base styles will be missing when variants are used.
-
-**Reference:**
-
-See [`components/Heading/Heading.vanilla.css.ts`](components/Heading/Heading.vanilla.css.ts) for the correct pattern - all styles are included in the recipe base.
+**Why:** Split styles with conditional recipe classes will miss base styles when variants are used.
-**Rule:** Put ALL styling in recipe base if component has variants. Test variant combinations to verify.
+**Rule:** Put ALL styling in recipe base if component has variants (see [Heading.vanilla.css.ts](components/Heading/Heading.vanilla.css.ts)).
---
## Reference: Future Migration Phases
-**Note**: These phases are not currently actionable. Continue using Phase 1 (side-by-side exports).
-
-### Phase 2: Make Vanilla Extract Default (v2.0.0)
-
-Swap exports, deprecate Stitches versions. Breaking change for consumers.
+**Note**: Not currently actionable. Continue using Phase 1 (side-by-side exports).
-### Phase 3: Remove Stitches (v3.0.0)
-
-Delete Stitches files, remove `.vanilla` suffix from filenames. ~40% smaller bundle.
+- **Phase 2 (v2.0.0):** Make Vanilla Extract default, deprecate Stitches (breaking change)
+- **Phase 3 (v3.0.0):** Remove Stitches files, remove `.vanilla` suffix (~40% smaller bundle)
---
## Reference: Build Configuration
-The build system is already configured in [`vite.config.ts`](vite.config.ts) to support both Stitches and Vanilla Extract simultaneously. No action needed.
-
-**Key Settings:**
+Build system already configured in [vite.config.ts](vite.config.ts) - no action needed.
-- `preserveModules: true` - Maintains module structure for tree-shaking
-- Both `.tsx` and `.css.ts` files are processed
-- Outputs ES modules (ESM) and CommonJS (CJS) builds
-- Static CSS files generated at build time (not runtime)
+**Key settings:** `preserveModules: true` (tree-shaking), processes `.tsx` and `.css.ts`, outputs ESM/CJS, static CSS at build time.
---
diff --git a/VANILLA_EXTRACT_MIGRATION.md b/VANILLA_EXTRACT_MIGRATION.md
index 5b1775eb..c80ca210 100644
--- a/VANILLA_EXTRACT_MIGRATION.md
+++ b/VANILLA_EXTRACT_MIGRATION.md
@@ -18,7 +18,7 @@ This document outlines the progressive migration strategy from Stitches to vanil
- ✅ Storybook theme integration
- ✅ Developer migration guide
-⏳ **Phase 3 Planned**: Component-by-Component Migration
+🚧 **Phase 3 In Progress**: Component-by-Component Migration
⏳ **Phase 4 Planned**: Remove Stitches
## Architecture Overview
@@ -88,36 +88,7 @@ styles/
## Component Migration Pattern
-### Before (Stitches)
-
-```tsx
-import { styled } from '../../stitches.config';
-
-export const Box = styled('div', {
- boxSizing: 'border-box',
-});
-
-// Usage with CSS prop
-
-```
-
-### After (Vanilla-Extract)
-
-```tsx
-import { style } from '@vanilla-extract/css';
-import { recipe } from '@vanilla-extract/recipes';
-
-export const box = style({
- boxSizing: 'border-box',
-});
-
-export const boxRecipe = recipe({
- base: box,
- variants: {
- // Add variants as needed
- },
-});
-```
+See [VANILLA_EXTRACT_DEVELOPER_GUIDE.md](./VANILLA_EXTRACT_DEVELOPER_GUIDE.md) for detailed migration steps and patterns.
## Key Considerations
@@ -152,10 +123,11 @@ export const boxRecipe = recipe({
## Next Steps
1. ✅ **Complete Phase 2**: Coexistence confirmed working ✓
-2. **Start Phase 3**: Begin migrating simple components (Text, Flex, Grid)
+2. 🚧 **Phase 3 In Progress**: Migrating components
- Follow the [Developer Guide](./VANILLA_EXTRACT_DEVELOPER_GUIDE.md) for migration steps
-3. **Validate approach**: Ensure migrated components work exactly like originals
-4. **Scale migration**: Use learnings to migrate more complex components
+ - Validate migrated components work exactly like originals
+3. **Continue migration**: Scale to more complex components
+4. **Phase 4**: Remove Stitches after all components migrated
## Benefits After Migration
diff --git a/components/AccessibleIcon/AccessibleIcon.stories.tsx b/components/AccessibleIcon/AccessibleIcon.stories.tsx
index 9ca07c0f..6b8f8548 100644
--- a/components/AccessibleIcon/AccessibleIcon.stories.tsx
+++ b/components/AccessibleIcon/AccessibleIcon.stories.tsx
@@ -3,9 +3,13 @@ import * as Icons from '@radix-ui/react-icons';
import { Meta, StoryFn } from '@storybook/react-vite';
import React from 'react';
+import { BoxVanilla } from '../Box/Box.vanilla';
import { Flex } from '../Flex';
+import { FlexVanilla } from '../Flex/Flex.vanilla';
+import { H3Vanilla } from '../Heading';
import { IconButton } from '../IconButton';
import { AccessibleIcon } from './AccessibleIcon';
+import { AccessibleIconVanilla } from './AccessibleIcon.vanilla';
const Component: Meta = {
title: 'Components/AccessibleIcon',
@@ -29,4 +33,66 @@ export const Basic: StoryFn = () => (
);
+export const Comparison: StoryFn = () => (
+
+
+ Stitches Version
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Vanilla Extract Version
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+);
+
export default Component;
diff --git a/components/AccessibleIcon/AccessibleIcon.vanilla.test.tsx b/components/AccessibleIcon/AccessibleIcon.vanilla.test.tsx
new file mode 100644
index 00000000..bab44a79
--- /dev/null
+++ b/components/AccessibleIcon/AccessibleIcon.vanilla.test.tsx
@@ -0,0 +1,127 @@
+import { render } from '@testing-library/react';
+import { axe } from 'jest-axe';
+
+import { VanillaExtractThemeProvider } from '../../styles/themeContext';
+import { AccessibleIconVanilla } from './AccessibleIcon.vanilla';
+
+describe('AccessibleIconVanilla', () => {
+ const renderWithTheme = (ui: React.ReactElement) => {
+ return render({ui});
+ };
+
+ const TestIcon = () => (
+
+ );
+
+ it('should render with label', () => {
+ const { container } = renderWithTheme(
+
+
+ ,
+ );
+
+ expect(container.firstChild).toBeInTheDocument();
+ });
+
+ it('should provide accessible label via visually hidden text', () => {
+ const { container } = renderWithTheme(
+
+
+ ,
+ );
+
+ // The label should be present in the DOM for screen readers, but visually hidden
+ expect(container.textContent).toContain('Notification Bell');
+ });
+
+ it('should render icon with accessible label', () => {
+ const { container } = renderWithTheme(
+
+
+ ,
+ );
+
+ const svg = container.querySelector('svg');
+ expect(svg).toBeInTheDocument();
+ expect(container.textContent).toContain('User Avatar');
+ });
+
+ it('should render child icon element', () => {
+ const { container } = renderWithTheme(
+
+
+ ,
+ );
+
+ const svg = container.querySelector('svg');
+ expect(svg).toBeInTheDocument();
+ });
+
+ it('should preserve child element classes', () => {
+ const { container } = renderWithTheme(
+
+