@aircall/ds

中文文档
0.48.0发布于 2026/10/28ISCTypeScriptES Module近 30 天下载 3761

Aircall Design System - Modern UI Component Library

原始使用文档
文档目录28 节
  1. Tech Stack
  2. For AI agents / coding assistants
  3. Migrating off @aircall/tractor ? Turn on the AI migration s…
  4. Installation
  5. For Monorepo Apps (Hydra Workspace)
  6. For External Apps
  7. Usage
  8. Import Components
  9. Import CSS Styles
  10. Dark Mode
  11. Components
  12. Button
  13. Storybook
  14. Quick Start
  15. Features
  16. Building Storybook
  17. Development
  18. Adding Components
  19. File Structure
  20. Styling System
  21. Theme System
  22. Recipes (shadcn Registry)
  23. What is the Registry?
  24. Local Development
  25. Production (Published Storybook)
  26. Available Recipes
  27. Adding a New Recipe
  28. Scripts

@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):

ComponentPackages to install
Calendarreact-day-picker, date-fns
# If using Calendar
pnpm add react-day-picker date-fns

The package is published with its module graph preserved (tsdown unbundle) and is marked sideEffects: ["**/*.css"], so Calendar's peers are only pulled into a consumer's bundle when Calendar is actually imported. Importing other components (e.g. Button) tree-shakes them away — no install required. DataTable's @tanstack/react-table runtime is a regular @aircall/ds dependency instead — it installs automatically, nothing to add yourself.

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

RecipeDescription
combobox-searchable-selectSingle-select combobox — input acts as trigger and search filter
combobox-dropdown-searchButton-triggered combobox with search inside dropdown
combobox-multi-selectMulti-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

版本与发布记录

@aircall/ds 共发布 62 个版本,首次发布于 2026/01/12;过去 12 个月发布了 62 个版本。

版本发布日期距今
0.48.0latest2026/10/2810 天前
0.47.12026/10/1226 天前
0.47.02026/10/0929 天前
0.46.22026/09/251 个月前
0.46.02026/09/161 个月前
0.45.12026/09/111 个月前

关于 @aircall/ds 的常见问题

@aircall/ds 是什么?

Aircall Design System - Modern UI Component Library

如何安装 @aircall/ds?

在项目目录运行 npm install @aircall/ds,或使用 pnpm add / yarn add / bun add。它还需要宿主项目提供 peer 依赖:react(^18.0.0 || ^19.0.0)、i18next(>=20.3.0 <24)、react-dom(^18.0.0 || ^19.0.0)、@aircall/hooks(*)、@aircall/numbers(*)、@aircall/react-icons(*)。npm 7 及以上与 pnpm 8 及以上默认会自动安装必需的 peer 依赖,Yarn 需要手动安装。

@aircall/ds 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@aircall/ds 支持 ESM 还是 CommonJS?

以 ES Module 形式发布,推荐使用 import 引入。在 CommonJS 项目中,Node.js 20.19+ / 22.12+ 可以直接 require() 不含顶层 await 的 ES Module,更早的版本需要改用动态 import()。

@aircall/ds 可以商用吗?

@aircall/ds 使用 ISC 许可。宽松许可:允许商业使用、修改和再分发,通常只需在分发时保留原版权与许可声明。以上为概括,不构成法律意见,请以许可原文为准。

@aircall/ds 的下载量有多少?

根据 npm 官方下载统计,@aircall/ds 在 2026/10/05 – 2026/11/03 这 30 天内被下载 3,761 次,日均约 125 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@aircall/ds 还在维护吗?

最新版本 0.48.0 发布于 2026/10/28(10 天前);至今共发布 62 个版本,其中过去 12 个月发布了 62 个;首次发布于 2026/01/12。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。