Skip to content

Bi-directional Figma design system sync. CSS ships instantly—no code, no redeploy, no maintenance.

License

Notifications You must be signed in to change notification settings

components-kit/packages

Repository files navigation

ComponentsKit

npm version License: MIT TypeScript

Headless, accessible React components with data attributes for CSS-based styling

Bi-directional Figma design system sync. CSS ships instantly—no code, no redeploy, no maintenance.

Highlights

  • Headless/Unstyled - Zero built-in styles, full control via data-* attributes
  • Accessible - WAI-ARIA compliant with keyboard navigation support
  • Zero Dependencies - Only peer dependencies (React, optional table/select libs)
  • TypeScript First - Full type safety with exported interfaces
  • Polymorphic - Render components as different HTML elements
  • Composable - asChild pattern for flexible component composition

Installation

npm install @components-kit/react

Peer Dependencies

# Required
npm install react react-dom

# Optional - only if using Table component
npm install @tanstack/react-table

# Optional - only if using Select, Combobox, or MultiSelect
npm install downshift @floating-ui/react

# Optional - only if using Toast component
npm install sonner

Quick Start

import { Button, Input, Heading } from "@components-kit/react";

function App() {
  return (
    <div>
      <Heading as="h1" variantName="title">
        Welcome
      </Heading>
      <Input
        type="email"
        placeholder="Enter your email"
        variantName="default"
      />
      <Button variantName="primary" size="md">
        Get Started
      </Button>
    </div>
  );
}

Styling Setup

ComponentsKit components are headless (unstyled). To apply styles, load the CSS bundle from the ComponentsKit API.

1. Get Your API Key

Sign up at componentskit.com to get your API key.

2. Environment Variables

Create a .env file in your project root:

# For Next.js
NEXT_PUBLIC_COMPONENTS_KIT_URL=https://api.componentskit.com
NEXT_PUBLIC_COMPONENTS_KIT_KEY=your_api_key_here

# For Vite
VITE_COMPONENTS_KIT_URL=https://api.componentskit.com
VITE_COMPONENTS_KIT_KEY=your_api_key_here

3. Load Styles

Next.js App Router

// app/layout.tsx
const BASE_URL = process.env.NEXT_PUBLIC_COMPONENTS_KIT_URL;
const API_KEY = process.env.NEXT_PUBLIC_COMPONENTS_KIT_KEY;
const BUNDLE_URL = `${BASE_URL}/v1/public/bundle.min.css?key=${API_KEY}`;
const FONTS_URL = `${BASE_URL}/v1/public/fonts.css?key=${API_KEY}`;

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="en">
      <head>
        <link href="https://fonts.googleapis.com" rel="preconnect" />
        <link
          crossOrigin="anonymous"
          href="https://fonts.gstatic.com"
          rel="preconnect"
        />
        <link as="style" href={BUNDLE_URL} rel="preload" />
        <link href={BUNDLE_URL} rel="stylesheet" />
        <link href={FONTS_URL} rel="stylesheet" />
      </head>
      <body>{children}</body>
    </html>
  );
}

Vite / React SPA

Use Vite's transformIndexHtml hook to inject CSS at build time, preventing flash of unstyled content (FOUC):

// vite.config.ts
import { defineConfig, loadEnv } from "vite";
import react from "@vitejs/plugin-react";

export default defineConfig(({ mode }) => {
  const env = loadEnv(mode, ".", "VITE_");
  const BASE_URL = env.VITE_COMPONENTS_KIT_URL ?? "";
  const API_KEY = env.VITE_COMPONENTS_KIT_KEY ?? "";

  return {
    plugins: [
      react(),
      {
        name: "inject-components-kit-assets",
        transformIndexHtml(html) {
          return html
            .replace(
              /__BUNDLE_URL__/g,
              `${BASE_URL}/v1/public/bundle.min.css?key=${API_KEY}`,
            )
            .replace(
              /__FONTS_URL__/g,
              `${BASE_URL}/v1/public/fonts.css?key=${API_KEY}`,
            );
        },
      },
    ],
  };
});
<!-- index.html -->
<!doctype html>
<html lang="en">
  <head>
    <!-- Preconnect for fonts -->
    <link rel="preconnect" href="https://fonts.googleapis.com" />
    <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />

    <!-- Preload and load CSS bundle (render-blocking) -->
    <link rel="preload" href="__BUNDLE_URL__" as="style" />
    <link rel="stylesheet" href="__BUNDLE_URL__" />

    <!-- Load fonts -->
    <link rel="stylesheet" href="__FONTS_URL__" />
  </head>
  <body>
    <div id="root"></div>
    <script type="module" src="/src/main.tsx"></script>
  </body>
</html>

Type-Safe Variants

Use @components-kit/cli to generate TypeScript types for variantName props. This gives you autocomplete and build-time validation.

npm install -D @components-kit/cli
npx ck init
npx ck generate

Add the generated types to your tsconfig.json:

{
  "include": ["src", "types"]
}

Now variantName props autocomplete with your project's actual variants:

// Autocomplete suggests "primary", "secondary", "destructive", etc.
<Button variantName="primary">Submit</Button>

// TypeScript error: Type '"invalid"' is not assignable
<Button variantName="invalid">Submit</Button>

See the @components-kit/cli README for full documentation.

Core Concepts

Headless Components with Data Attributes

All components are unstyled and expose data-* attributes for CSS-based styling:

<Button variantName="primary" size="lg" isLoading>
  Submit
</Button>

Renders with these attributes for styling:

<button data-variant="primary" data-size="lg" data-loading="true">
  Submit
</button>

Polymorphic Components

Some components support the as prop to render as different HTML elements:

// Renders as <h2>
<Heading as="h2" variantName="section-title">Section</Heading>

// Renders as <span>
<Text as="span" variantName="caption">Inline text</Text>

Polymorphic components: Heading, Text

Composition with asChild

Components supporting asChild merge their props onto their child element:

import { Button } from "@components-kit/react";
import Link from "next/link";

// Button behavior with Link rendering
<Button asChild variantName="primary">
  <Link href="/dashboard">Dashboard</Link>
</Button>;

Components with asChild: Button, Badge

Components

Component Description Optional Deps
Alert Contextual feedback messages with heading, description, and action -
Badge Small status indicator for labels and counts -
Button Button with loading, icons, and composition support -
Checkbox Boolean selection with indeterminate state support -
Heading Polymorphic heading (h1-h6) with semantic hierarchy -
Icon Icon wrapper with size variants (sm/md/lg) for icon library components -
Input Text input with type variants -
Pagination Accessible pagination with offset (numeric) and cursor-based modes -
Progress Linear progress bar with label, determinate/indeterminate modes -
RadioGroup Radio button group with RadioGroupItem -
Select Dropdown select with keyboard navigation, type-ahead, placement, form integration, read-only, and error states downshift, @floating-ui/react
Combobox Searchable select with text input filtering, clearable, placement, form integration, read-only, error states, and async support downshift, @floating-ui/react
MultiSelect Multi-value select with tags, filtering, keyboard navigation, clearable, fixed tags, token separators, form integration, and error states downshift, @floating-ui/react
Separator Visual divider (horizontal/vertical) -
Skeleton Loading placeholder with customizable dimensions -
Slider Range input with keyboard navigation and pointer drag support -
Slot Utility for prop merging and asChild pattern -
Switch Binary toggle control -
Table Data table with sorting, pagination (data slicing), selection, and expansion. Compose with Pagination for UI @tanstack/react-table
Text Polymorphic text element (p, span, strong, em, etc.) -
Textarea Multi-line text input with auto-resize -
Toast Toast notification function with semantic markup sonner
Tabs Accessible tabs for organizing content into panels -

Toast Component Setup

The Toast component requires special setup since it uses Sonner for toast management.

Installation

npm install sonner

Setup

Import <Toaster /> from sonner (not from @components-kit/react) and add it to your app root:

// Next.js App Router - app/layout.tsx
import { Toaster } from "sonner";

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="en">
      <body>
        <Toaster />
        {children}
      </body>
    </html>
  );
}
// Vite / React SPA - App.tsx
import { Toaster } from "sonner";

function App() {
  return (
    <>
      <Toaster />
      {/* your app */}
    </>
  );
}

Usage

Import the toast function from @components-kit/react:

import { toast } from "@components-kit/react";

function MyComponent() {
  return (
    <button
      onClick={() =>
        toast({
          title: "Success",
          description: "Your changes have been saved.",
          variantName: "success",
        })
      }
    >
      Save Changes
    </button>
  );
}

With action button:

toast({
  title: "Item deleted",
  description: "The item has been removed from your list.",
  button: {
    label: "Undo",
    onClick: () => console.log("Undo clicked"),
  },
  variantName: "info",
});

Accessibility

All components follow WAI-ARIA guidelines:

  • Semantic HTML - Proper elements and roles
  • Keyboard Navigation - Full keyboard support (Tab, Enter, Space, Arrow keys)
  • ARIA Attributes - Correct aria-* attributes for screen readers
  • Focus Management - Visible focus indicators and logical focus order

Example accessibility features:

Component Accessibility Features
Alert role="alert", aria-live="polite"
Button aria-disabled, aria-busy for loading
Checkbox Label association, aria-invalid
Progress role="progressbar", aria-valuenow/min/max, aria-labelledby for label
Select WAI-ARIA Listbox pattern, type-ahead search, aria-haspopup="listbox", aria-orientation="vertical", grouped options with role="group" + aria-labelledby, live region for selection changes
Combobox WAI-ARIA Combobox pattern, aria-expanded, aria-controls, aria-required, aria-orientation="vertical", aria-labelledby on menu, grouped options with role="group" + aria-labelledby, aria-busy during loading, role="alert" on error, live region for selection and result count changes
MultiSelect WAI-ARIA Combobox pattern, aria-multiselectable, aria-orientation="vertical", aria-required, aria-labelledby on menu, grouped options with role="group" + aria-labelledby, tag keyboard navigation, live region for selection changes
Slider role="slider", aria-valuenow/min/max, keyboard navigation
Table aria-sort, aria-selected, keyboard navigation
Pagination <nav> landmark, aria-current="page", aria-disabled, keyboard navigation
Toast role="status", aria-live="polite", Button component with aria-disabled, aria-busy, keyboard support
Tabs WAI-ARIA Tabs pattern, roving tabindex, aria-orientation

TypeScript

All components export their prop types:

import { Button, ButtonProps } from "@components-kit/react";
import type { ColumnDef } from "@components-kit/react"; // Re-exported from TanStack

// Extend props
interface MyButtonProps extends ButtonProps {
  analyticsId: string;
}

// Generic components
import { Table } from "@components-kit/react";

interface User {
  id: string;
  name: string;
  email: string;
}

<Table<User> data={users} columns={columns} />;

Variant Type Registration

The library exports ComponentsKitVariants and VariantFor<T> for type-safe variant names:

import type { ComponentsKitVariants, VariantFor } from "@components-kit/react";

// VariantFor<"button"> resolves to the registered union (e.g., "primary" | "secondary" | ...)
// or falls back to `string` if no augmentation is configured
type ButtonVariant = VariantFor<"button">;

Run npx ck generate from @components-kit/cli to augment ComponentsKitVariants with your project's variants. See Type-Safe Variants above.

Examples

Next.js App Router (SSR)

See the Next.js example for Server-Side Rendering with React Server Components.

TanStack Router + Vite (CSR)

See the TanStack Router example for Client-Side Rendering with Vite.

Browser Support

  • Chrome (latest)
  • Firefox (latest)
  • Safari (latest)
  • Edge (latest)

License

MIT License - see LICENSE for details.

About

Bi-directional Figma design system sync. CSS ships instantly—no code, no redeploy, no maintenance.

Resources

License

Contributing

Stars

Watchers

Forks

Packages

 
 
 

Contributors