ComponentsKit is a modern, headless React component library designed for accessibility and TypeScript safety. It's a monorepo with:
- 23 accessible components with zero production dependencies
- Headless/unstyled design — all styling via
data-*attributes for CSS - Figma design system sync — CSS updates without code redeployment
- Polymorphic components — render as any HTML element via
asprop
| Location | Purpose |
|---|---|
libs/react/ |
Main component library (@components-kit/react) |
libs/cli/ |
CLI for generating variant types (@components-kit/cli) |
example/next-app-router/ |
Next.js 15 SSR example |
example/tanstack-router/ |
Vite + TanStack Router CSR example |
| Component | Lines | Complexity | Key Features |
|---|---|---|---|
| Table | ~830 | High | Sorting, pagination (data slicing), filtering, row selection (TanStack Table) |
| Select | ~520 | High | Dropdown with groups, type-ahead, keyboard nav, icon slot, placement, form integration, read-only, error state, live region (Downshift + Floating UI) |
| Combobox | ~775 | High | Searchable select with text filtering, clearable, placement, form integration, read-only, error state, live region with result count, async/loading support, autoFocus, onBlur/onFocus, keyboard nav (Downshift + Floating UI) |
| MultiSelect | ~1015 | High | Multi-value select with tags, filtering, tag keyboard nav, clearable, fixed tags, token separators, form integration, read-only, error state, live region (Downshift + Floating UI) |
| Tabs | ~300 | Medium | Roving tabindex, keyboard nav, controlled/uncontrolled (useTabs hook) |
| Slot | 267 | Medium | Enables asChild pattern, smart prop merging |
| Icon | ~110 | Low | Icon wrapper, size variants (sm/md/lg), aria-hidden by default |
| Button | 168 | Medium | Polymorphic, asChild, loading states |
| Toast | ~150 | Medium | Sonner integration, semantic markup, Button component for action |
| Textarea | ~150 | Low | Auto-resize support |
| Progress | ~150 | Low | Label, determinate/indeterminate, CSS custom property |
| Input | ~120 | Low | Text input variants |
| Pagination | ~200 | Medium | Offset/cursor modes, ellipsis, controlled/uncontrolled (usePagination hook) |
| Checkbox | ~120 | Low | Indeterminate state |
| Switch | ~100 | Low | Toggle control |
| RadioGroup | ~100 | Low | Radio group pattern |
| Alert | ~80 | Low | Heading, description, action (icon via CSS variant) |
| Badge | ~60 | Low | Status indicator, asChild |
| Heading | ~50 | Low | Polymorphic h1-h6 |
| Text | ~50 | Low | Polymorphic text |
| Separator | ~40 | Low | Visual divider |
| Skeleton | ~40 | Low | Loading placeholder |
| Slider | ~240 | Medium | Keyboard nav, pointer drag, orientation, onValueCommit, controlled/uncontrolled (useSlider hook) |
- Polymorphic — Some components render as different elements:
<Heading as="h2"> - AsChild — Merge props with children via Slot:
<Button asChild><Link/></Button> - Data Attributes — Styling hooks:
data-variant,data-size,data-loading - Accessibility — ARIA compliant, keyboard navigation, semantic HTML
- Type-Safe Variants — Register pattern for
variantNameautocomplete (see below)
The variantName prop on every component uses VariantFor<T> from types/register.ts:
┌──────────────────────────────────────────────────────────┐
│ @components-kit/react (library) │
│ │
│ interface ComponentsKitVariants {} ← empty by default │
│ type VariantFor<T> = T extends keyof ComponentsKitVariants │
│ ? ComponentsKitVariants[T] │
│ : string ← fallback │
└──────────────────────────────────────────────────────────┘
│
│ declare module "@components-kit/react"
│ (TypeScript declaration merging)
│
┌──────────────────────────────────────────────────────────┐
│ @components-kit/cli (ck generate) │
│ │
│ Fetches variants from API → generates .d.ts that │
│ augments ComponentsKitVariants with actual unions: │
│ button: "primary" | "secondary" | "destructive" | ... │
└──────────────────────────────────────────────────────────┘
Without the CLI, VariantFor<"button"> resolves to string. With the generated .d.ts, it narrows to the specific union — enabling autocomplete and build-time errors.
┌─────────────────────────────────────────────────────────────────────────┐
│ COMPONENTSKIT MONOREPO │
│ /packages │
└─────────────────────────────────────────────────────────────────────────┘
│
┌─────────────────────────┼─────────────────────────┐
│ │ │
▼ ▼ ▼
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ libs/react │ │ example/next- │ │ example/tanstack│
│ @components-kit │ │ app-router │ │ -router │
│ /react │ │ (Next.js 15) │ │ (Vite + CSR) │
└────────┬────────┘ └─────────────────┘ └─────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────────┐
│ src/components/ │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ Button │ │ Select │ │ Table │ │ Slot │ │
│ │ polymorphic │ │ downshift │ │ tanstack │ │ asChild │ │
│ │ asChild │ │ float. UI │ │ sorting │ │ prop merge │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘ │
│ │
│ ┌─────────────┐ ┌─────────────┐ │
│ │ Combobox │ │ MultiSelect │ │
│ │ downshift │ │ downshift │ │
│ │ filter/async│ │ tags/keys │ │
│ └─────────────┘ └─────────────┘ │
│ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ Input │ │ Textarea │ │ Checkbox │ │ Switch │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘ │
│ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ RadioGroup │ │ Alert │ │ Badge │ │ Icon │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘ │
│ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ Heading │ │ Text │ │ Separator │ │ Skeleton │ │
│ │ polymorphic │ │ polymorphic │ │ │ │ │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘ │
│ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ Toast │ │ Tabs │ │ Progress │ │ Slider │ │
│ │ sonner │ │ roving tab │ │ label/a11y │ │ drag/keys │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘ │
│ │
│ ┌─────────────┐ │
│ │ Pagination │ │
│ │ offset/curs │ │
│ └─────────────┘ │
│ (usePagination, useSlider, useTabs hooks) │
│ │
└─────────────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────────┐
│ src/hooks/, src/utils/ & types/ │
├─────────────────────────────────────────────────────────────────────────┤
│ hooks/use-floating-select.ts ─── Floating UI dropdown positioning │
│ hooks/use-exit-transition.ts ─── delayed unmount for CSS exit anims │
│ hooks/use-debounced-callback.ts ── debounced async callbacks │
│ hooks/use-floating-viewport-sync.ts ── mobile viewport sync for │
│ dropdown repositioning │
│ utils/select.ts ─── processOptions, filterRenderItems, areValuesEqual, │
│ serializeValue, findItemByValue, findItemsByValue │
│ utils/render-dropdown-items.tsx ── shared dropdown item renderer │
│ (Select, MultiSelect, Combobox) │
│ utils/merge-refs.ts ─── ref merging utility │
│ forward-ref.ts ─── polymorphicForwardRef(), createPolymorphicComponent │
│ types/index.ts ─── PolymorphicComponentProps, PolymorphicRef │
│ types/register.ts ── ComponentsKitVariants, VariantFor<T> │
│ types/select.ts ── NormalizedItem, SelectOption, RenderItem │
└─────────────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────────┐
│ OUTPUT (data-*) │
├─────────────────────────────────────────────────────────────────────────┤
│ <button data-variant="primary" data-size="lg" data-loading="true"> │
│ ↓ │
│ CSS from Figma Design System (loaded at runtime or build-time) │
└─────────────────────────────────────────────────────────────────────────┘
┌────────────────────────────────────────────────────────────────┐
│ PEER DEPENDENCIES │
├────────────────────────────────────────────────────────────────┤
│ │
│ Required: Optional: │
│ ┌─────────────┐ ┌─────────────┐ ┌──────────────┐ ┌──────────────┐ ┌────────┐│
│ │ React 18+ │ │ downshift │ │ @floating-ui │ │ @tanstack/ │ │ sonner ││
│ │ or 19 │ │ (9.0+) │ │ /react │ │ react-table │ │ (2.0+) ││
│ └──────┬──────┘ └──────┬──────┘ └──────┬───────┘ └──────┬───────┘ └───┬────┘│
│ │ │ │ │ │ │
│ ▼ ▼ ▼ ▼ ▼ │
│ All Components Select family Select family Table only Toast only │
│ (Select, Combobox, MultiSelect) │
│ │
└────────────────────────────────────────────────────────────────┘
packages/
├── libs/cli/ # CLI tool (@components-kit/cli)
│ ├── src/
│ │ ├── index.ts # Commander entry (ck binary)
│ │ ├── config.ts # Config loading/saving
│ │ ├── generate.ts # Generation orchestration
│ │ ├── codegen.ts # TypeScript code generation
│ │ └── fetch-variants.ts # API fetching
│ ├── package.json
│ └── tsup.config.ts
├── libs/react/ # Main library
│ ├── src/
│ │ ├── components/ # 23 component directories
│ │ │ ├── alert/
│ │ │ ├── badge/
│ │ │ ├── button/
│ │ │ ├── checkbox/
│ │ │ ├── combobox/
│ │ │ ├── heading/
│ │ │ ├── icon/
│ │ │ ├── input/
│ │ │ ├── multi-select/
│ │ │ ├── pagination/
│ │ │ ├── progress/
│ │ │ ├── radio-group/
│ │ │ ├── select/
│ │ │ ├── separator/
│ │ │ ├── skeleton/
│ │ │ ├── slider/
│ │ │ ├── slot/
│ │ │ ├── switch/
│ │ │ ├── table/
│ │ │ ├── tabs/
│ │ │ ├── text/
│ │ │ ├── textarea/
│ │ │ └── toast/
│ │ ├── hooks/ # Shared hooks
│ │ │ ├── use-debounced-callback.ts
│ │ │ ├── use-floating-select.ts
│ │ │ ├── use-floating.ts
│ │ │ ├── use-floating-viewport-sync.ts
│ │ │ └── index.ts
│ │ ├── types/ # TypeScript type definitions
│ │ │ ├── register.ts # ComponentsKitVariants + VariantFor<T>
│ │ ├── utils/ # Utility functions (incl. select.ts, merge-refs.ts)
│ │ └── index.tsx # Main export barrel
│ ├── package.json
│ └── tsup.config.ts # Build configuration
├── example/
│ ├── next-app-router/ # Next.js 15 SSR example
│ └── tanstack-router/ # Vite CSR example
├── package.json # Root workspace config
├── pnpm-workspace.yaml
├── tsconfig.json
├── vitest.config.ts
└── eslint.config.mjs
- Headless/Unstyled First — No CSS dependencies, enabling clean separation of concerns and Figma sync
- Zero Dependencies — Only peer dependencies, minimal bundle footprint
- TypeScript-First — Full type safety with exported interfaces and generics
- Polymorphic by Design — Most components support the
asprop for flexibility - Composition Pattern — Slot/asChild pattern enables powerful component composition
- Data Attributes for Styling — Clean, CSS-friendly attributes (
data-variant,data-size, etc.) - Semantic HTML — Proper elements and roles for accessibility
- Accessibility Default — ARIA attributes, keyboard navigation built-in by default
pnpm build # Build @components-kit/react
pnpm dev # Watch mode development
pnpm lint # Run ESLint
pnpm test:run # Run tests once
pnpm test:ui # Run tests with UI dashboard