| name | component-library |
| description | Build production-grade, accessible, and tested component libraries with Storybook, Chromatic, and design tokens |
| sasmp_version | 2.0.0 |
| bonded_agent | 03-component-architecture |
| bond_type | PRIMARY_BOND |
| input_validation | [object Object] |
| output_format | [object Object] |
| error_handling | [object Object] |
| observability | [object Object] |
Component Library Building Skill
Overview
Learn to build reusable, accessible, and well-documented component libraries for React applications using modern design patterns and tools.
Learning Objectives
- Design reusable component APIs
- Implement accessible components (WCAG 2.1)
- Create component variants and compositions
- Build documentation with Storybook
- Publish and maintain npm packages
Component Design Principles
1. Flexible and Composable
// Good: Flexible API
<Card>
<Card.Header>
<Card.Title>Title</Card.Title>
</Card.Header>
<Card.Body>Content</Card.Body>
<Card.Footer>
<Button>Action</Button>
</Card.Footer>
</Card>
// Bad: Rigid API
<Card title="Title" content="Content" action="Action" />
2. Accessible by Default
function Button({ children, onClick, disabled, ...props }) {
return (
<button
type="button"
onClick={onClick}
disabled={disabled}
aria-disabled={disabled}
{...props}
>
{children}
</button>
);
}
Essential Components
Button Component
const Button = forwardRef(({
children,
variant = 'primary',
size = 'md',
disabled = false,
loading = false,
leftIcon,
rightIcon,
...props
}, ref) => {
return (
<button
ref={ref}
className={`btn btn-${variant} btn-${size}`}
disabled={disabled || loading}
aria-busy={loading}
{...props}
>
{leftIcon && <span className="btn-icon-left">{leftIcon}</span>}
{loading ? <Spinner size="sm" /> : children}
{rightIcon && <span className="btn-icon-right">{rightIcon}</span>}
</button>
);
});
Button.displayName = 'Button';
Input Component
const Input = forwardRef(({
label,
error,
helper,
required,
...props
}, ref) => {
const id = useId();
return (
<div className="input-wrapper">
{label && (
<label htmlFor={id}>
{label}
{required && <span aria-label="required">*</span>}
</label>
)}
<input
ref={ref}
id={id}
aria-invalid={!!error}
aria-describedby={error ? `${id}-error` : helper ? `${id}-helper` : undefined}
{...props}
/>
{helper && <span id={`${id}-helper`} className="input-helper">{helper}</span>}
{error && <span id={`${id}-error`} className="input-error" role="alert">{error}</span>}
</div>
);
});
Modal Component
function Modal({ isOpen, onClose, title, children }) {
const modalRef = useRef(null);
useEffect(() => {
if (isOpen) {
const previousActiveElement = document.activeElement;
modalRef.current?.focus();
return () => {
previousActiveElement?.focus();
};
}
}, [isOpen]);
useOnClickOutside(modalRef, onClose);
if (!isOpen) return null;
return createPortal(
<div className="modal-overlay" role="dialog" aria-modal="true" aria-labelledby="modal-title">
<div ref={modalRef} className="modal" tabIndex={-1}>
<div className="modal-header">
<h2 id="modal-title">{title}</h2>
<button onClick={onClose} aria-label="Close modal">×</button>
</div>
<div className="modal-body">{children}</div>
</div>
</div>,
document.body
);
}
Dropdown Component
function Dropdown({ trigger, children }) {
const [isOpen, setIsOpen] = useState(false);
const dropdownRef = useRef(null);
useOnClickOutside(dropdownRef, () => setIsOpen(false));
return (
<div ref={dropdownRef} className="dropdown">
<div onClick={() => setIsOpen(!isOpen)} role="button" aria-expanded={isOpen}>
{trigger}
</div>
{isOpen && (
<div className="dropdown-menu" role="menu">
{children}
</div>
)}
</div>
);
}
function DropdownItem({ onClick, children }) {
return (
<div className="dropdown-item" role="menuitem" onClick={onClick} tabIndex={0}>
{children}
</div>
);
}
Dropdown.Item = DropdownItem;
Compound Components Pattern
Tabs Component
const TabsContext = createContext();
function Tabs({ children, defaultTab }) {
const [activeTab, setActiveTab] = useState(defaultTab);
return (
<TabsContext.Provider value={{ activeTab, setActiveTab }}>
<div className="tabs">{children}</div>
</TabsContext.Provider>
);
}
function TabList({ children }) {
return <div className="tab-list" role="tablist">{children}</div>;
}
function Tab({ id, children }) {
const { activeTab, setActiveTab } = useContext(TabsContext);
return (
<button
role="tab"
aria-selected={activeTab === id}
onClick={() => setActiveTab(id)}
className={activeTab === id ? 'active' : ''}
>
{children}
</button>
);
}
function TabPanels({ children }) {
return <div className="tab-panels">{children}</div>;
}
function TabPanel({ id, children }) {
const { activeTab } = useContext(TabsContext);
if (activeTab !== id) return null;
return <div role="tabpanel">{children}</div>;
}
Tabs.List = TabList;
Tabs.Tab = Tab;
Tabs.Panels = TabPanels;
Tabs.Panel = TabPanel;
Storybook Integration
Installation
npx storybook@latest init
Button Stories
// Button.stories.jsx
import { Button } from './Button';
export default {
title: 'Components/Button',
component: Button,
argTypes: {
variant: {
control: 'select',
options: ['primary', 'secondary', 'danger']
},
size: {
control: 'select',
options: ['sm', 'md', 'lg']
}
}
};
export const Primary = {
args: {
variant: 'primary',
children: 'Button'
}
};
export const WithIcons = {
args: {
leftIcon: <Icon name="star" />,
children: 'Button'
}
};
export const Loading = {
args: {
loading: true,
children: 'Loading...'
}
};
TypeScript Support
// Button.tsx
interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
variant?: 'primary' | 'secondary' | 'danger';
size?: 'sm' | 'md' | 'lg';
loading?: boolean;
leftIcon?: React.ReactNode;
rightIcon?: React.ReactNode;
}
const Button = forwardRef<HTMLButtonElement, ButtonProps>(
({ children, variant = 'primary', size = 'md', ...props }, ref) => {
return (
<button ref={ref} className={`btn btn-${variant} btn-${size}`} {...props}>
{children}
</button>
);
}
);
Publishing to npm
package.json Setup
{
"name": "@yourname/component-library",
"version": "1.0.0",
"main": "dist/index.js",
"module": "dist/index.esm.js",
"types": "dist/index.d.ts",
"files": ["dist"],
"peerDependencies": {
"react": "^18.0.0",
"react-dom": "^18.0.0"
}
}
Practice Projects
- Build a complete Button component with variants
- Create accessible Form components (Input, Select, Checkbox)
- Implement Modal with focus trap
- Build Dropdown with keyboard navigation
- Create Tabs compound component
- Implement Toast notification system
- Build Tooltip component
Resources
- Radix UI - Headless UI components
- Headless UI - Unstyled components
- Storybook - Component documentation
- React Aria - Accessibility
Difficulty: Intermediate to Advanced Estimated Time: 3-4 weeks Prerequisites: React Fundamentals, Component Architecture