This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
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 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.
yarn install- Install dependenciesyarn patch-package- Apply necessary patches to Stitches after installationyarn storybook- Start Storybook development server at http://localhost:6006yarn build- Build the library for productionyarn lint- Lint TypeScript/React files in components directoryyarn lint:fix- Auto-fix linting issuesyarn test- Run Jest tests in watch modeyarn test:ci- Run tests in CI mode (silent, non-watch)
yarn release- Build and publish using semantic-release
- Stitches CSS-in-JS: Current primary styling system with design tokens and utilities
- vanilla-extract: New styling system (migration in progress - see
VANILLA_EXTRACT_DEVELOPER_GUIDE.mdfor how to migrate components) - Build Tool: Vite with vanilla-extract plugin
- Design Tokens: Defined in
stitches.config.ts(Stitches) andstyles/tokens.css.ts(vanilla-extract) - Theme Support: Light/dark themes with customizable primary colors
- Component Themes: Stitches components have theme files (e.g.,
Button.themes.ts)
Components follow a consistent pattern:
ComponentName.tsx- Main component implementationComponentName.stories.tsx- Storybook storiesComponentName.test.tsx- Jest tests (where applicable)ComponentName.themes.ts- Theme definitions for light/dark modesindex.ts- Exports
stitches.config.ts- Stitches configuration with design tokens, utilities, and theme setupcolors/- Color palette definitions (light/dark variants)index.ts- Main library exportscomponents/FaencyProvider.tsx- Root provider component
- Primary colors:
neon,blue,orange,red,green,deepBlue,grayBlue - Dynamic theme generation based on primary color selection
- Component themes support both light and dark variants
- Colors are defined in
/colors/with systematic naming
- Built on Radix UI Primitives for accessibility
- Uses Stitches for styling with CSS-in-JS
- React 18+ and TypeScript 5+ required
- No Unnecessary Comments: Do not add obvious or redundant comments. Code should be self-explanatory through clear naming and structure. Only add comments when explaining complex logic, non-obvious decisions, or documenting public APIs.
- No Unnecessary Emojis: OK for console warnings/errors, but avoid in code comments or commit messages.
- Create component directory in
/components/ - Implement main component with Stitches styling
- Create theme file with
getLight()andgetDark()functions - Add Storybook stories with all variants
- Write Jest tests for critical functionality
- Export from main
index.ts
- Theme objects must have
getLight(primaryColor)andgetDark(primaryColor)methods - Register new themes in
stitches.config.tsin both light and dark theme functions - Use semantic color tokens for consistency
- Jest with React Testing Library configured
- jsdom environment for DOM testing
- Focus on accessibility and component behavior
- Run tests before submitting changes
- Test coverage is currently being developed
- Pin all versions: All dependencies must use exact pinned versions (no
^or~). This applies to bothdependenciesanddevDependencies. - 3-day rule: When updating or adding dependencies, only use versions released more than 3 days ago. Check the npm release date before pinning a version.
- Patch Required: Run
yarn patch-packageafter installing dependencies to fix Stitches TypeScript 5 compatibility - Node Version: Requires Node.js 20+
- Conventional Commits: Follow conventional commit format for semantic releases
- Accessibility: All components must be accessible and support keyboard navigation
- Storybook: Every component requires comprehensive stories showing all variants
When working with vanilla-extract components during the migration:
CRITICAL: Never mix Stitches and vanilla-extract components. Always use vanilla-extract versions of components inside vanilla-extract components.
// ❌ Wrong - mixing Stitches and vanilla-extract
import { Box } from '../Box';
import { Label } from '../Label';
<Box css={css}>...</Box>;
// ✅ Correct - use vanilla versions or plain HTML
import { BoxVanilla } from '../Box';
import { LabelVanilla } from '../Label';
<BoxVanilla css={css}>...</BoxVanilla>;Why: Stitches components expect Stitches CSS type, but vanilla components use CSSProps type. Mixing them causes type errors and architectural inconsistency.
When vanilla version doesn't exist: Use plain HTML elements (<div>, <span>, etc.) with manual style processing.
Understanding the CSSProps interface is crucial for proper type safety:
Key Pattern - CSSProps['css'] vs CSSProps:
// For props that accept CSS properties directly
interface MyComponentProps {
rootCss?: CSSProps['css']; // ✅ Correct - expects inner CSS object
// NOT: rootCss?: CSSProps // ❌ Wrong - expects wrapper with css property
}
// Usage in component
const { style: rootCssStyles, vars: rootVars } = processCSSProp(rootCss, colors);When to use each:
css?: CSSProps['css']- For props that pass directly toprocessCSSProp()extends CSSProps- For components that have acssprop on the interface
Property Name Flexibility:
The CSSProps interface accepts both abbreviated and full property names:
// Both work identically
<Component css={{ p: '$4', m: '$2' }} /> // Abbreviated
<Component css={{ padding: '$4', margin: '$2' }} /> // Full namesThis works because:
CSSPropshas[key: string]: anyindex signatureprocessCSSProp()explicitly handles both forms in its switch statement
When you see as any in tests: This usually indicates a type mismatch. Check if the prop should be CSSProps['css'] instead of CSSProps.
REQUIRED: Every vanilla-extract component MUST have a corresponding .vanilla.test.tsx file with comprehensive tests.
Test File Naming:
- Vanilla-extract components:
ComponentName.vanilla.test.tsx - Stitches components:
ComponentName.test.tsx
Required Test Coverage:
- Basic rendering and element types
- Custom className support
- All variant props (size, weight, variant, gradient, transform, noWrap, etc.)
- Custom styling (CSS prop and style prop)
- Style merging behavior
- Polymorphic rendering (if component supports
asprop) - Ref forwarding
- HTML attribute pass-through
- Accessibility testing with jest-axe
- Theme support (light/dark and different primary colors)
Testing Pattern:
import { render } from '@testing-library/react';
import { axe } from 'jest-axe';
import React from 'react';
import { VanillaExtractThemeProvider } from '../../styles/themeContext';
import { ComponentVanilla } from './Component.vanilla';
describe('ComponentVanilla', () => {
const renderWithTheme = (ui: React.ReactElement) => {
return render(<VanillaExtractThemeProvider>{ui}</VanillaExtractThemeProvider>);
};
it('should render correctly', () => {
const { container } = renderWithTheme(<ComponentVanilla>Content</ComponentVanilla>);
expect(container.firstChild).toBeInTheDocument();
});
it('should have no accessibility violations', async () => {
const { container } = renderWithTheme(<ComponentVanilla>Content</ComponentVanilla>);
const results = await axe(container);
expect(results).toHaveNoViolations();
});
// Use unmount() in loops to prevent "multiple elements found" errors
it('should apply size variants', () => {
const sizes = ['small', 'medium', 'large'] as const;
sizes.forEach((size) => {
const { container, unmount } = renderWithTheme(
<ComponentVanilla size={size}>Content</ComponentVanilla>,
);
expect(container.firstChild).toBeInTheDocument();
unmount();
});
});
});Important Notes:
- Always wrap components in
VanillaExtractThemeProviderfor tests - Use
unmount()in forEach loops to prevent DOM element accumulation - For Radix-based components, use the correct role (e.g.,
role="radio"for ButtonSwitch items, notrole="button") - Reference the
Input.vanilla.test.tsxfile as the gold standard for test patterns