--- name: component-library description: | Component library design expertise covering design system tokens, component API design, compound components, polymorphic components, accessibility by default, Storybook documentation, testing, versioning, and tree-shaking. Use when the user asks about component library, component library best practices, or needs guidance on component library implementation. Do NOT use when the user needs a different specialized skill or is asking about an unrelated technology domain. license: Apache-2.0 metadata: author: foundry-skills version: "1.0.0" tags: "web-development frontend design-patterns" category: "web-development" subcategory: "frontend-frameworks" depends: "" disclaimer: "none" difficulty: "intermediate" --- # Component Library ## Purpose Design and build production-grade component libraries that serve as the foundation of a design system. This skill covers API design principles, accessibility integration, documentation, testing strategy, packaging, and long-term maintenance. ## Design System Token Architecture ### Token Layers ``` Layer 1: Global Tokens (Primitives) Raw design values with no semantic meaning. --color-blue-500: #3b82f6 --space-4: 1rem --font-size-16: 1rem Layer 2: Alias Tokens (Semantic) Intent-based mapping to primitives. --color-brand-primary: var(--color-blue-500) --color-text-primary: var(--color-gray-900) --spacing-component-gap: var(--space-4) Layer 3: Component Tokens Scoped to specific components. --button-bg: var(--color-brand-primary) --button-text: var(--color-white) --button-radius: var(--radius-md) --button-padding-x: var(--space-4) --button-padding-y: var(--space-2) ``` ### Token File Organization ``` tokens/ colors.ts # Color primitives and semantic mappings spacing.ts # Spacing scale typography.ts # Font families, sizes, weights, line-heights borders.ts # Border widths, radii shadows.ts # Elevation system motion.ts # Duration, easing curves breakpoints.ts # Responsive breakpoints z-index.ts # Z-index scale index.ts # Combined export ``` ### Token Implementation ```ts // tokens/colors.ts export const colors = { primitives: { blue: { 50: '#eff6ff', 100: '#dbeafe', 500: '#3b82f6', 700: '#1d4ed8', 900: '#1e3a5c' }, gray: { 50: '#f9fafb', 100: '#f3f4f6', 500: '#6b7280', 700: '#374151', 900: '#111827' }, red: { 50: '#fef2f2', 500: '#ef4444', 700: '#b91c1c' }, green: { 50: '#f0fdf4', 500: '#22c55e', 700: '#15803d' }, }, semantic: { brand: { primary: '{blue.500}', hover: '{blue.700}' }, text: { primary: '{gray.900}', secondary: '{gray.500}', inverse: '{gray.50}' }, bg: { primary: '#ffffff', secondary: '{gray.50}', surface: '{gray.100}' }, border: { default: '{gray.200}', focus: '{blue.500}' }, status: { success: { text: '{green.700}', bg: '{green.50}' }, error: { text: '{red.700}', bg: '{red.50}' }, }, }, } as const; ``` ## Component API Design ### Principles ``` 1. SIMPLE THINGS SHOULD BE SIMPLE -- 90% of usage ); } ); Button.displayName = 'Button'; ``` ## Compound Components ```tsx // Dialog compound component const DialogContext = createContext(null); function useDialogContext() { const ctx = useContext(DialogContext); if (!ctx) throw new Error('Dialog components must be used within '); return ctx; } function Dialog({ open, onOpenChange, children }: DialogProps) { return ( {children} ); } function DialogTrigger({ children, asChild }: DialogTriggerProps) { const { onOpenChange } = useDialogContext(); return ; } function DialogContent({ children, title, description }: DialogContentProps) { const { open, onOpenChange } = useDialogContext(); if (!open) return null; return createPortal(
onOpenChange(false)}>
e.stopPropagation()}> {title &&

{title}

} {description &&

{description}

} {children}
, document.body ); } function DialogClose({ children }: { children: React.ReactNode }) { const { onOpenChange } = useDialogContext(); return ; } Dialog.Trigger = DialogTrigger; Dialog.Content = DialogContent; Dialog.Close = DialogClose; // Usage Open Settings

Configure your preferences.

Done
``` ## Polymorphic Components ```tsx // Type-safe polymorphic `as` prop type PolymorphicRef = React.ComponentPropsWithRef['ref']; type PolymorphicProps = Props & { as?: C; } & Omit, keyof Props | 'as'>; type PolymorphicPropsWithRef = PolymorphicProps & { ref?: PolymorphicRef }; // Usage in component type TextProps = PolymorphicPropsWithRef; function Text({ as, variant = 'body', size = 'md', weight = 'regular', className, children, ...props }: TextProps) { const Component = as || 'span'; return ( {children} ); } // Usage Default span Page Title Link text Email ``` ## Accessibility by Default ### Built-in A11y Patterns ```tsx // All interactive components must include: // 1. Keyboard support // 2. ARIA attributes // 3. Focus management // 4. Screen reader announcements // Example: Switch component with built-in a11y function Switch({ checked, onCheckedChange, label, id }: SwitchProps) { const switchId = id || useId(); return (
); } // A11y testing in every component test test('Switch is accessible', async () => { const { container } = render( {}} />); expect(await axe(container)).toHaveNoViolations(); }); ``` ### A11y Checklist for Every Component ``` [ ] Has visible label or aria-label [ ] Keyboard operable (Tab, Enter, Space, Escape, Arrow keys as appropriate) [ ] Focus indicator visible [ ] Color contrast meets WCAG AA [ ] Works with screen readers (test with NVDA/VoiceOver) [ ] Supports prefers-reduced-motion [ ] Error states have aria-invalid and aria-describedby [ ] Loading states have aria-busy [ ] Dynamic content uses aria-live [ ] Touch target at least 24x24px ``` ## Storybook Documentation ### Story Structure ```tsx // Button.stories.tsx import type { Meta, StoryObj } from '@storybook/react'; import { Button } from './Button'; const meta: Meta = { title: 'Components/Button', component: Button, tags: ['autodocs'], argTypes: { variant: { control: 'select', options: ['primary', 'secondary', 'outline', 'ghost', 'danger'], description: 'Visual style of the button', table: { defaultValue: { summary: 'primary' } }, }, size: { control: 'radio', options: ['sm', 'md', 'lg'], }, loading: { control: 'boolean' }, disabled: { control: 'boolean' }, onClick: { action: 'clicked' }, }, parameters: { docs: { description: { component: 'Primary UI component for user interaction.', }, }, }, }; export default meta; type Story = StoryObj; export const Primary: Story = { args: { children: 'Button', variant: 'primary' }, }; export const AllVariants: Story = { render: () => (
), }; export const Loading: Story = { args: { children: 'Saving...', loading: true }, }; ``` ## Testing Components ```tsx // Unit test with Testing Library import { render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; describe('Button', () => { it('renders with correct text', () => { render(); expect(screen.getByRole('button', { name: 'Click me' })).toBeInTheDocument(); }); it('calls onClick when clicked', async () => { const onClick = vi.fn(); render(); await userEvent.click(screen.getByRole('button')); expect(onClick).toHaveBeenCalledOnce(); }); it('does not call onClick when disabled', async () => { const onClick = vi.fn(); render(); await userEvent.click(screen.getByRole('button')); expect(onClick).not.toHaveBeenCalled(); }); it('shows spinner when loading', () => { render(); expect(screen.getByRole('button')).toHaveAttribute('aria-busy', 'true'); }); it('passes axe accessibility checks', async () => { const { container } = render(); expect(await axe(container)).toHaveNoViolations(); }); }); // Visual regression with Chromatic or Playwright // Snapshot each story automatically ``` ## Versioning and Release Strategy ### Semantic Versioning Rules ``` MAJOR (X.0.0): Breaking changes - Removing a prop - Changing default behavior - Renaming a component - Changing TypeScript types in breaking way MINOR (0.X.0): New features (backwards compatible) - Adding a new component - Adding a new prop (with default value) - Adding a new variant PATCH (0.0.X): Bug fixes - Fixing a visual bug - Fixing a11y issue - Fixing TypeScript type accuracy ``` ### Changelog Convention ```markdown ## [2.1.0] - 2025-03-15 ### Added - `Tooltip` component with hover and focus triggers - `fullWidth` prop to `Button` component ### Fixed - `Select` dropdown now correctly positions in scroll containers - `Dialog` focus trap includes dynamically added elements ### Deprecated - `Modal` component: use `Dialog` instead (will be removed in 3.0) ``` ## Tree-Shaking and Package Build ```json // package.json { "name": "@myorg/ui", "version": "2.1.0", "sideEffects": ["*.css"], "main": "dist/cjs/index.js", "module": "dist/esm/index.js", "types": "dist/types/index.d.ts", "exports": { ".": { "import": "./dist/esm/index.js", "require": "./dist/cjs/index.js", "types": "./dist/types/index.d.ts" }, "./Button": { "import": "./dist/esm/Button.js", "require": "./dist/cjs/Button.js", "types": "./dist/types/Button.d.ts" }, "./styles.css": "./dist/styles.css" }, "files": ["dist"] } ``` ## Component Library Checklist - [ ] Token system defined (primitives -> semantic -> component tokens) - [ ] Component API follows consistent naming conventions - [ ] Every component has TypeScript types with JSDoc descriptions - [ ] Compound components used for complex composite UIs - [ ] Polymorphic `as` prop available on layout/text components - [ ] Every interactive component is keyboard accessible - [ ] Every component has Storybook stories with controls - [ ] Every component has unit tests + a11y tests - [ ] Visual regression tests configured (Chromatic or Playwright) - [ ] Package exports enable tree-shaking - [ ] sideEffects field correctly configured - [ ] Semantic versioning enforced via changesets - [ ] CHANGELOG maintained with each release - [ ] forwardRef used on all components that wrap native elements ## When to Use **Use this skill when:** - Designing or implementing component library solutions - Reviewing or improving existing component library approaches - Making architectural or implementation decisions about component library - Learning component library patterns and best practices - Troubleshooting component library-related issues **Do NOT use this skill when:** - The question is about a fundamentally different technology domain - A more specific sibling skill covers the exact topic needed - The user needs a complete hands-on tutorial rather than expert guidance ## Output Format ```markdown # Component Library Analysis ## Context Assessment [Situation summary and constraints] ## Recommended Approach [Primary recommendation with rationale] ## Implementation Steps 1. [Step with specific details] 2. [Step with specific details] 3. [Step with specific details] ## Trade-offs and Considerations - [Key trade-off 1] - [Key trade-off 2] ## Next Steps - [Immediate action item] - [Follow-up action item] ``` ## Example **Input:** "Help me implement component library for a medium-scale production application" **Output:** A structured analysis covering current state assessment, recommended component library approach with specific patterns, implementation roadmap with milestones, and risk mitigation strategies tailored to the application scale and constraints. ## Edge Cases - **Legacy system integration:** When component library must coexist with legacy approaches, provide a gradual migration path rather than a complete rewrite - **Scale mismatch:** When the solution complexity exceeds the project scale, recommend a simpler approach and note when to revisit - **Team skill gaps:** When the team lacks experience with the recommended approach, include learning resources and simpler alternatives - **Conflicting requirements:** When constraints conflict (e.g., performance vs. maintainability), explicitly state the trade-off and recommend based on stated priorities