This document tracks breaking changes that will be introduced when migrating from Stitches to Vanilla Extract as the default styling system.
These changes will occur in v13.0.0 when we make Vanilla Extract the default export and deprecate Stitches versions. Users will need to update their code when upgrading.
Current Version: v12.x (Stitches-based, side-by-side with Vanilla Extract) Next Major Version: v13.0.0 (Vanilla Extract only - complete migration with breaking changes) Current Status: In development - migrating components to Vanilla Extract
Change: asChild prop replaced with polymorphic as prop across all applicable components
Before (v12.x with Stitches):
import { Badge } from 'faency';
// Using asChild with a child element
<Badge asChild variant="success">
<a href="/profile">Link Badge</a>
</Badge>;After (v13.x with Vanilla Extract):
import { Badge } from 'faency';
// Using as prop directly
<Badge as="a" href="/profile" variant="success">
Link Badge
</Badge>;Migration Guide:
- Replace
asChildwithas="elementType" - Move the child element's props to the component itself
- Remove the wrapper element and move children to the component's children
Rationale:
- More ergonomic API (no wrapper element needed)
- Better TypeScript support (element-specific props are typed correctly)
- Consistent polymorphic pattern across all components
- Simpler implementation without Radix UI Slot dependency
Impact: Medium - Common pattern for components rendered as links or buttons
- ✅ Box - Full polymorphic support (default:
div) - ✅ Container - Full polymorphic support (default:
div) - ✅ Flex - Full polymorphic support (default:
div) - ✅ Grid - Full polymorphic support (default:
div) - ✅ Panel - Full polymorphic support (default:
div) - ✅ Elevation - Full polymorphic support (default:
div) - ✅ Card - Full polymorphic support with smart defaults (default:
buttonwheninteractive, otherwisediv)
- ✅ Text - Full polymorphic support (default:
span) - ✅ Heading - Full polymorphic support (default:
h1) - ✅ Paragraph - Full polymorphic support (default:
p) - ✅ Blockquote - Full polymorphic support (default:
blockquote) - ✅ Label - Full polymorphic support (default:
label)
- ✅ Badge - Full polymorphic support (default:
span) - ✅ Button - Full polymorphic support (default:
button) - ✅ IconButton - Full polymorphic support (default:
button) - ✅ Bubble - Full polymorphic support (default:
div) - ✅ ButtonSwitch (Container & Item) - Full polymorphic support with conditional rendering
- Container default: uses Radix
ToggleGroup.Rootwhen noasprop, otherwise renders as custom element withrole="group" - Item default: uses Radix
ToggleGroup.Itemwhen noasprop, otherwise renders as custom element withrole="radio"
- Container default: uses Radix
These components do not support the as prop because the actual form elements must remain fixed for proper functionality and browser compatibility:
- ❌ Input - Wrapper is fixed
div, input element cannot be changed - ❌ TextField - Uses
BoxVanillawrapper, input element cannot be changed - ❌ Textarea - Wrapper is fixed
div, textarea element cannot be changed
- ⏳ AriaTable - Not yet migrated to Vanilla Extract
- Additional components may be added as migration continues
ButtonSwitch uses a special conditional rendering pattern:
- Without
asprop: Uses Radix UI primitives (ToggleGroup.RootandToggleGroup.Item) for full Radix functionality - With
asprop: Renders as custom elements with appropriate ARIA roles- Container:
role="group" - Item:
role="radio"
- Container:
Important: When using as prop on ButtonSwitch container, you must also use as prop on items to avoid Radix context errors.
// ✅ Correct - both use `as` prop
<ButtonSwitchContainerVanilla as="nav">
<ButtonSwitchItemVanilla as="a" value="1" href="/home">Home</ButtonSwitchItemVanilla>
<ButtonSwitchItemVanilla as="a" value="2" href="/about">About</ButtonSwitchItemVanilla>
</ButtonSwitchContainerVanilla>
// ❌ Wrong - mixing will cause Radix context errors
<ButtonSwitchContainerVanilla as="nav">
<ButtonSwitchItemVanilla value="1">Home</ButtonSwitchItemVanilla> {/* Missing `as` */}
</ButtonSwitchContainerVanilla>Card component has intelligent default element selection:
interactive={true}→ defaults to<button>interactive={false}or not set → defaults to<div>asprop always overrides the default
// Renders as <button>
<CardVanilla interactive>Click me</CardVanilla>
// Renders as <div>
<CardVanilla>Static content</CardVanilla>
// Renders as <section> (overrides interactive default)
<CardVanilla as="section" interactive>Custom element</CardVanilla>Change: VanillaExtractThemeProvider required at app root
Before (v12.x with Stitches):
import { FaencyProvider } from 'faency';
<FaencyProvider>
<App />
</FaencyProvider>;After (v13.x with Vanilla Extract):
import { VanillaExtractThemeProvider } from 'faency';
<VanillaExtractThemeProvider defaultTheme="light">
<App />
</VanillaExtractThemeProvider>;Migration Guide:
- Replace
FaencyProviderwithVanillaExtractThemeProvider - Add
defaultThemeprop (either"light"or"dark") - Optional: Add
forcedThemeprop to override user preference - Optional: Add
primaryColorprop to set the primary color theme
Rationale:
- Vanilla Extract requires theme class application at root
- More explicit theme control
- Better integration with system preferences
- Support for dynamic primary color theming
Impact: High - Required for all users
Change: Internal implementation change (no user-facing API change)
Status: No breaking change - CSS prop API remains identical
// Both versions support the same API
<Badge css={{ px: '$4', py: '$6', bc: '$blue6' }} />
// Also supports full property names
<Badge css={{ padding: '$4', backgroundColor: '$blue6' }} />Note: This is maintained for backward compatibility. The css prop continues to support token-based styling with both abbreviated and full property names.
- Both Stitches and Vanilla Extract versions exported
- No breaking changes
- Users can opt-in to
*Vanillacomponents - This allows users to test Vanilla Extract versions before migration
- Components have
.vanilla.tsxsuffix in source - Currently in this phase - actively migrating components
- Complete switch to Vanilla Extract
- Remove all Stitches code and dependencies
- Remove
.vanillasuffix from filenames and exports - All components use Vanilla Extract only
- Breaking changes documented above take effect
- Expected bundle size reduction
Migration Steps (when upgrading to v13.0.0):
- Update imports:
import { Badge } from 'faency'(removeVanillasuffix) - Replace
FaencyProviderwithVanillaExtractThemeProvider - Update
asChildusage toasprop pattern - Test all components in your application
- Update TypeScript types if using component type references
## [13.0.0] - YYYY-MM-DD
### BREAKING CHANGES
#### Polymorphic Components: `asChild` → `as` Prop
Layout, typography, and interactive components now use polymorphic `as` prop
- **Changed:** Replaced `asChild` prop with polymorphic `as` prop for better TypeScript support and ergonomics
- Before: `<Badge asChild><a href="#">Link</a></Badge>`
- After: `<Badge as="a" href="#">Link</Badge>`
- Migration: Replace `asChild` with `as="elementType"`, move child element props to component, remove wrapper element
- See [Migration Guide](./BREAKING_CHANGES.md#polymorphic-components-aschild--as-prop)
#### Theme Provider
- **Changed:** Requires `VanillaExtractThemeProvider` instead of `FaencyProvider`
- Before: `<FaencyProvider><App /></FaencyProvider>`
- After: `<VanillaExtractThemeProvider defaultTheme="light"><App /></VanillaExtractThemeProvider>`
- See [Migration Guide](./BREAKING_CHANGES.md#provider-changes)
#### Component Exports
- **Changed:** Removed `.vanilla` suffix from all component exports
- Before: `import { BadgeVanilla } from 'faency'`
- After: `import { Badge } from 'faency'`
- All components now use Vanilla Extract by default
### Added
- Polymorphic `as` prop for layout, typography, and interactive components with full TypeScript support
- Better performance with build-time CSS generation via Vanilla Extract
- Improved TypeScript inference for element-specific props
- Smart default element selection (e.g., Card defaults to button when interactive)
- Conditional rendering pattern for Radix-based components (ButtonSwitch)
### Removed
- **Complete removal of Stitches styling system**
- Removed `@stitches/react` dependency
- Removed all `*.themes.ts` files
- Removed `stitches.config.ts`
- Removed `@radix-ui/react-slot` dependency (replaced with polymorphic pattern)
- Removed `.vanilla` suffix from component exports
- Removed `FaencyProvider` (replaced with `VanillaExtractThemeProvider`)
### Performance
- Build-time CSS generation for faster runtime performance
- Reduced JavaScript payload with static CSS extraction-
Documentation:
- Update README with migration guide
- Create detailed migration examples for common patterns
- Document all breaking changes with before/after code samples
- Add migration codemods (if feasible)
- Create visual guide showing polymorphic component usage
-
Release Notes:
- Comprehensive changelog with all breaking changes
- Step-by-step migration guide
- Common pitfalls and solutions
-
Testing:
- Comprehensive test coverage for all polymorphic components
- Integration tests for migration paths
- Performance benchmarks
-
Support Period:
- Maintain v12.x branch for critical bug fixes
- Community support in GitHub issues
- Dedicated migration support channel
-
Feedback Collection:
- Monitor migration issues and pain points
- Update guide based on common problems
- Create FAQ for frequently asked questions
For changes like asChild → as and FaencyProvider → VanillaExtractThemeProvider, a codemod could automate migration:
npx @faency/migratePotential transformations:
- Replace
asChildpattern withasprop - Update provider imports and usage
- Remove
Vanillasuffix from component imports - Update component type references
Decision: TBD - To be evaluated based on migration complexity
A temporary compatibility shim could ease migration:
// Compatibility mode (deprecated, to be removed in v14.0.0)
import { Badge } from 'faency/compat';
<Badge asChild>
<a href="#">Link</a>
</Badge>;Decision: TBD - Evaluate based on community feedback
This document should be updated as:
- New components are migrated
- Breaking changes are identified
- Migration patterns are refined
- User feedback is received
- Edge cases are discovered
Last Updated: 2026-01-02 Status: Work in Progress - Component migration ongoing