npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@aircall/ds

v0.26.0

Published

Aircall Design System - Modern UI Component Library

Downloads

4,611

Readme

@aircall/ds - Aircall Design System

Modern React component library built with TailwindCSS 4, Base UI primitives, and TypeScript.

Tech Stack

  • React 19 - Latest React with compiler optimizations
  • TailwindCSS 4 - Utility-first CSS with OKLch color space
  • Base UI - Unstyled, accessible component primitives (@base-ui/react)
  • TypeScript 5.7 - Type-safe development
  • Class Variance Authority (CVA) - Variant-based component styling
  • Storybook 10 - Interactive component documentation

For AI agents / coding assistants

If you are an AI assistant generating code that uses @aircall/ds, read AGENTS.consumer.md (shipped in this package) for the rules the TypeScript types can't express — Base UI conventions (use render, not asChild), the required @aircall/ds/globals.css import, icon imports from @aircall/react-icons, and dark-mode setup. The shipped .d.ts files are the source of truth for prop names and values (variants are typed as literal unions) — do not invent props.

Migrating off @aircall/tractor? Turn on the AI migration skills

This package ships TanStack Intent migration skills in its skills/ directory (published in the npm tarball) — version-matched, agent-loadable guidance for moving an app off @aircall/tractor / @aircall/icons (and @dashboard/library@aircall/blocks) to @aircall/ds. Enable them in your repo (one-time):

# 1. install / upgrade — @aircall/react-icons MUST be >= 0.4.0 (the ds bundle imports flags from it)
pnpm add @aircall/ds@latest @aircall/blocks@latest @aircall/react-icons@latest

# 2. let your AI agent discover the skills — writes a discovery block into CLAUDE.md / AGENTS.md
npx @tanstack/intent install

# 3. allowlist the packages in package.json, then verify discovery:
#    { "intent": { "skills": ["@aircall/ds", "@aircall/blocks"] } }
npx @tanstack/intent list   # should list @aircall/ds and @aircall/blocks

Your agent then loads guidance on demand, starting with setup:

npx @tanstack/intent load "@aircall/ds#aircall-ds/setup"
# then per file: aircall-ds/migrate-tractor · aircall-ds/migrate-icons · aircall-blocks/migrate-dashboard

Each migrate skill carries an end-to-end playbook (setup → inventory → migrate → verify) and requires setup (providers incl. DsI18nProvider, the globals.css import, and the jsdom test shims). Keep @aircall/tractor installed until the last Tractor import is gone — migration is progressive (cohabitation).

Installation

For Monorepo Apps (Hydra Workspace)

If you're developing within the Hydra monorepo (e.g., apps/aw-web, apps/hubspot-cti):

# In your app's package.json
{
  "dependencies": {
    "@aircall/ds": "workspace:*"
  }
}

For External Apps

If you're using the design system in an external project or consuming the published NPM package:

pnpm add @aircall/ds

Required peer dependencies:

pnpm add react react-dom @aircall/react-icons @aircall/numbers

Optional peer dependencies (install only if you use these components):

| Component | Packages to install | |-----------|-------------------| | Calendar | react-day-picker, date-fns | | DataTable | @tanstack/react-table |

# If using Calendar
pnpm add react-day-picker date-fns

# If using DataTable
pnpm add @tanstack/react-table

The package is published with its module graph preserved (tsdown unbundle) and is marked sideEffects: ["**/*.css"], so these peers are only pulled into a consumer's bundle when Calendar / DataTable are actually imported. Importing other components (e.g. Button) tree-shakes them away — no install required.

Usage

Import Components

Components work the same way in both monorepo and external apps:

import { Button } from '@aircall/ds';

function App() {
  return (
    <Button variant="default" color="primary" size="default">
      Click me
    </Button>
  );
}

Import CSS Styles

There is one stylesheet, @aircall/ds/globals.css, but it resolves to a different artifact depending on how you consume the package. (There is no separate styles.css export.)

Monorepo Apps (workspace:*)

The package's exports point at source, so @aircall/ds/globals.css is the un-compiled Tailwind source (@import "tailwindcss", @theme, …). Your app's Tailwind v4 build compiles it, and @source scans your own files for class names:

/* your app's entry CSS — DS only */
@import '@aircall/ds/globals.css';
@source "your_own_source_file";

If you also use @aircall/blocks, import only blocks globals — its source already imports DS globals, so postcss-import deduplication means DS is compiled once:

/* your app's entry CSS — DS + blocks */
@import '@aircall/blocks/globals.css';
@source "your_own_source_file";

External Apps (published npm package)

The published tarball rewrites exports to dist, so @aircall/ds/globals.css is a precompiled Tailwind v4 bundle — Preflight, tokens, and every DS utility class already generated.

DS only, no custom Tailwind classes:

// main.tsx
import '@aircall/ds/globals.css';

DS + @aircall/blocks: import only blocks globals — it already bundles all of DS (Preflight, theme tokens, all DS utilities). Importing both duplicates the entire DS compiled output:

// main.tsx
import '@aircall/blocks/globals.css'; // already includes @aircall/ds/globals.css

Authoring your own Tailwind utility classes alongside DS or blocks — use the split import to activate Tailwind for @source scanning without adding a second Preflight:

/* style.css */
/* Required: locks layer order so @layer utilities beats @layer base.
   Without this, Preflight's border-width: 0 / display: block beat utility classes. */
@layer theme, base, components, utilities;

/* Split import — no Preflight. The .css extension is required;
   bare specifiers (tailwindcss/theme) silently produce no output in Rsbuild/webpack. */
@import 'tailwindcss/theme.css' layer(theme);
@import 'tailwindcss/utilities.css' layer(utilities);

@import '@aircall/ds/globals.css';     /* DS only */
/* OR: @import '@aircall/blocks/globals.css'; for DS + blocks (don't import both) */

@source "./src/**/*.{ts,tsx}"; /* scan YOUR code, not node_modules */

Module-federation consumer (your app is a remote inside a host that already loads DS globals — e.g. dashboard-v4): do NOT import DS or blocks globals. The host's Preflight covers the whole document; a duplicate from your remote's injected <style> tag will break DS component borders.

/* style.css */
@layer theme, base, components, utilities;
@import 'tailwindcss/theme.css' layer(theme);
@import 'tailwindcss/utilities.css' layer(utilities);
@source "./**/*.{ts,tsx}";

Dark Mode

Dark mode is controlled via the data-theme attribute:

// Light mode (default)
<html>

// Dark mode
<html data-theme="dark">

The design system uses CSS variables that automatically adjust based on this attribute.

Components

Button

Versatile button component with multiple variants and sizes, built on the Base UI button primitive (so it also accepts Base UI Button props such as disabled).

Props:

  • variant: 'default' | 'secondary' | 'outline' | 'ghost' | 'destructive' | 'link' (default: 'default')
  • size: 'default' | 'sm' | 'lg' | 'icon' | 'icon-sm' | 'icon-lg' (default: 'default')
  • block: boolean — full width (default: false)
  • render: ReactElement — render Button as a different element (e.g. <a>). Replaces the former asChild / Slot pattern.

Icons are passed as children. Import them from @aircall/react-icons (never lucide-react directly).

Examples:

import { Button } from '@aircall/ds';
import { Phone, Mail } from '@aircall/react-icons';

// Primary button (default variant)
<Button>Primary</Button>

// Secondary / outline / ghost
<Button variant="secondary">Secondary</Button>
<Button variant="outline">Outline</Button>
<Button variant="ghost">Ghost</Button>

// Icon-only button — use an `icon*` size
<Button variant="outline" size="icon-sm">
  <Phone />
</Button>

// Button with a leading icon
<Button>
  <Mail />
  Send Email
</Button>

// Full-width button
<Button block>Full Width</Button>

// Destructive button
<Button variant="destructive">Delete</Button>

// Render as a link — use the `render` prop (not `asChild`)
<Button render={<a href="/profile" />}>Go to Profile</Button>

Storybook

Interactive component documentation with live examples, controls, and accessibility testing.

Quick Start

cd packages/ds
pnpm sb:dev

View at: http://localhost:6008

Features

  • 🎨 Interactive component playground with live editing
  • 🌓 Light/dark mode testing via toolbar switcher
  • ♿ Accessibility testing with a11y addon
  • 📱 Responsive viewport testing
  • 🎭 Pseudo-state simulation (hover, focus, active)
  • 📐 Figma design integration
  • 📚 Auto-generated documentation from TypeScript types

Building Storybook

pnpm sb:build            # plain build — no recipe registry
pnpm sb:build:deploy     # full build including the recipe registry (what CI deploys)

Static site output: storybook-static/. See Recipes (shadcn Registry) below for when to use each.

Development

Adding Components

This package uses shadcn/ui patterns for component development:

# Add a new component from shadcn (wraps `pnpx shadcn@latest add`)
pnpm shadcn:add <component-name>

File Structure

src/
├── components/        # React components, one flat file per component (published to npm)
│   ├── button.tsx
│   ├── dialog.tsx
│   └── …
├── stories/          # Storybook stories (PascalCase, e.g. Button.stories.tsx)
├── styles/           # Global styles
│   ├── globals.css   # TailwindCSS + theme variables
│   └── brand.css     # Aircall brand colors
├── lib/              # Utilities
│   └── utils.ts      # cn() helper for class merging
├── hooks/            # Custom React hooks
├── recipes/          # shadcn registry recipes (copy-paste patterns, NOT published to npm)
│   ├── combobox-searchable-select.tsx
│   ├── combobox-dropdown-search.tsx
│   └── combobox-multi-select.tsx
└── fonts/            # Fellix Aircall font files

Styling System

Components use Class Variance Authority (CVA) for variant-based styling:

const buttonVariants = cva('base-classes', {
  variants: {
    variant: {
      default: 'variant-specific-classes',
      outline: 'outline-specific-classes'
    }
  },
  defaultVariants: {
    variant: 'default'
  }
});

This provides:

  • Type-safe variant props
  • Automatic TypeScript inference
  • Composable class generation
  • TailwindCSS class merging via tailwind-merge

Theme System

The design system uses CSS variables with OKLch color space for better color perception:

/* Light mode (default) */
:root {
  --primary: oklch(52.42% 0.201 192.01);
}

/* Dark mode */
[data-theme='dark'] {
  --primary: oklch(78.09% 0.128 192.01);
}

Colors automatically adapt when data-theme="dark" is set on the root element.

Recipes (shadcn Registry)

Recipes are pre-built, copy-paste component patterns built on top of @aircall/ds primitives. They are distributed via the shadcn registry — consumers run shadcn add to install a recipe into their project, and they own the code afterwards. They can customize it freely.

Note: in the shadcn registry schema these items are typed registry:block — that's a shadcn enum value, not our terminology. Internally we call them "recipes".

What is the Registry?

The registry is a set of JSON files that describe each recipe — its name, description, npm dependencies, and full source code. These JSON files are generated by shadcn build and served as static files alongside the DS Storybook.

How it works:

  1. Recipe source files live in src/recipes/ using relative imports (../components/combobox)
  2. pnpm run registry:build copies the recipes, rewrites imports to @aircall/ds, runs shadcn build, and outputs JSON files to public/r/
  3. Storybook serves public/ as static files via staticDirs config
  4. A consumer runs shadcn add <url> — the CLI fetches the JSON, extracts the source code, and writes it into their project with @aircall/ds imports

Local Development

Run pnpm run registry:build before starting Storybook to generate the JSON files. Then start Storybook:

pnpm run registry:build
pnpm run sb:dev

The recipe JSON files are served at http://localhost:6008/r/<name>.json. You can test the full consumer flow locally:

pnpm dlx shadcn@latest add http://localhost:6008/r/combobox-searchable-select.json

Production (Published Storybook)

When the DS Storybook is deployed to ds.aircall.io, the same JSON files are served at the public URL. Consumers install recipes with:

pnpm dlx shadcn@latest add https://ds.aircall.io/r/combobox-searchable-select.json

The deployment CI job runs pnpm sb:build:ds at the repo root, which maps to sb:build:deploy inside this package. That script runs registry:build first, then storybook build — Storybook's staticDirs: ['../public'] config picks up public/r/ and copies it into storybook-static/r/ so the JSON files ship alongside the deployed Storybook. sb:build on its own does NOT bundle the registry — use sb:build:deploy (or pnpm sb:build:ds at the root) whenever you need the registry included.

Available Recipes

| Recipe | Description | |---|---| | combobox-searchable-select | Single-select combobox — input acts as trigger and search filter | | combobox-dropdown-search | Button-triggered combobox with search inside dropdown | | combobox-multi-select | Multi-select with inline chips and type-ahead filtering |

Adding a New Recipe

  1. Create src/recipes/<name>.tsx — import primitives from ../components/
  2. Add an entry to registry.json at the package root
  3. Add a story in src/stories/ that imports from the recipe
  4. Run pnpm run registry:build to generate JSON in public/r/
  5. Verify at http://localhost:6008/r/<name>.json after starting Storybook

Scripts

# Development
pnpm sb:dev              # Start Storybook development server
pnpm lint                # Run ESLint

# Building for Distribution
pnpm build:package       # Build both JS and CSS for publishing
pnpm build:js            # Build JavaScript bundle only
pnpm build:css           # Build pre-compiled CSS only

# Registry
pnpm registry:build      # Generate recipe JSON files in public/r/
                         # (rewrites relative imports to @aircall/ds)

# Storybook — three distinct builds
pnpm sb:build            # Plain Storybook build. NO registry JSON.
pnpm sb:build:chromatic  # Storybook build for Chromatic (--test --stats-json). NO registry JSON.
pnpm sb:build:deploy     # Full deployment build: runs registry:build, builds Storybook,
                         # and copies public/r into storybook-static/r/.
                         # This is what CI runs (via `pnpm sb:build:ds` at the repo root).

# Testing Package Locally
pnpm pack                # Create tarball for local testing

# Component Management
pnpm shadcn:add <component>   # Add new shadcn component

# Maintenance
pnpm clean               # Remove build artifacts