---
name: aircall-blocks/setup
description: >
  Set up @aircall/blocks in an app already (or simultaneously) adopting @aircall/ds.
  Load when wiring blocks compositions (DashboardPage, DashboardPageHeader,
  DashboardSidebar, empty states, form fields) into a project: installing the
  package, importing its precompiled globals.css after the DS bundle, and the DS
  providers it relies on. Covers both standalone apps and module-federation
  consumers where the host already loads DS globals.
type: core
library: aircall-blocks
requires:
  - aircall-ds/setup
sources:
  - "aircall/hydra:docs/migration-guides/tractor-to-ds/00-setup.md"
  - "aircall/hydra:packages/blocks/package.json"
---

# Setting up @aircall/blocks

This skill builds on aircall-ds/setup. Read it first: blocks compose `@aircall/ds`
primitives and depend on DS being installed and its `globals.css` imported.

## Setup

Install blocks alongside DS:

```bash
pnpm add @aircall/blocks @aircall/ds @aircall/react-icons
```

How you import the CSS depends on whether your app is standalone or a
**module-federation consumer** whose host already loads DS globals.

### Standalone app (owns its own Preflight)

`@aircall/blocks/globals.css` is a **delta build** — it contains only blocks-specific tokens
and utility classes; it does NOT bundle DS. Import DS globals first (Preflight + DS tokens +
DS utilities), then blocks globals (blocks-specific delta). Always import in this order.

```css
/* style.css */
@layer theme, base, components, utilities;
@import 'tailwindcss/theme.css' layer(theme);
@import 'tailwindcss/utilities.css' layer(utilities);
@import '@aircall/ds/globals.css';
@import '@aircall/blocks/globals.css';

@source './src/**/*.{ts,tsx}';
```

Or from a JS/TS entry (no custom Tailwind classes of your own):

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

#### Single-compilation via `theme.css` (recommended for DS + blocks)

The two precompiled bundles above each re-emit the same Tailwind utilities into `@layer
utilities`; at equal specificity the last-loaded bundle can clobber the other's responsive
variants (e.g. blocks' plain `.text-base` beating DS's `md:text-sm` on `Textarea`), and **no
import order fixes it**. To avoid this, import the directive-preserving `theme.css` artifacts
(tokens + dark variant + reset + keyframes only — **no** Preflight, **no** utilities) and let a
single Tailwind pass generate the utilities by `@source`-scanning the library dist:

```css
/* style.css */
@import 'tailwindcss';                      /* consumer owns the single Preflight + engine */
@import '@aircall/ds/theme.css';
@import '@aircall/blocks/theme.css';
@source '../node_modules/@aircall/ds/dist/index.js';
@source '../node_modules/@aircall/blocks/dist/index.js';
```

Here the full `@import 'tailwindcss'` is **correct** (unlike the `globals.css` case) because
`theme.css` ships no Preflight/utilities. `globals.css` still ships unchanged for precompiled-
bundle consumers. Full rationale: `packages/ds/docs/single-compilation-theme-css.md`.

### Module-federation consumer (host already owns the Preflight)

When your app is a remote loaded inside a host that already imports DS globals
(e.g. `dashboard-v4`), **do not re-import DS or blocks globals** in your own
CSS. The host's single Preflight applies to the whole document — re-importing
it duplicates the base reset and causes cascade conflicts (see Common Mistakes
below).

Import only the Tailwind layers you need for your own authored utility classes:

```css
/* style.css */
@layer theme, base, components, utilities;
@import 'tailwindcss/theme.css' layer(theme);
@import 'tailwindcss/utilities.css' layer(utilities);

@source './**/*.{ts,tsx}';
```

> **Note on the `.css` extension**: `@import "tailwindcss/theme"` (no `.css`)
> silently fails in webpack/Rsbuild PostCSS pipelines — the extension is required.

Blocks render under the same DS providers — there is no blocks-specific provider; mount the
DS root providers from `aircall-ds/setup` as needed (`ThemeProvider` / `TooltipProvider` /
`Toaster`). Two DS providers matter specifically once you use blocks:

- **`DsI18nProvider`** (from `@aircall/ds`) — importing `@aircall/blocks` runs
  `import './i18n/register'` at module load (`packages/blocks/src/index.ts`), which registers
  a `blocks` namespace on DS's shared i18next instance. Block strings render localized off the
  self-initialized DS singleton even without a provider; to make DS + blocks strings follow the
  **user's** language (and track switches), mount `DsI18nProvider` **as a descendant of your
  react-i18next `I18nextProvider`** and pass the active language (`language={i18n.language}`).
  Use **either** `DsI18nProvider` **or** `syncDsLanguage(i18n)`, never both. See
  `aircall-ds/setup` for the full provider tree and nesting order.
- **`NotificationQueueProvider` + `NotificationSlot`** (from `@aircall/ds`) — needed only if
  your block compositions surface notifications/banners: wrap `NotificationQueueProvider`
  (props `{ children }`) above any notification caller, and render a `NotificationSlot
  slot="page"` (props `{ slot: string; className? }`) inside it.

Source: `packages/blocks/src/index.ts`; `@aircall/ds` `DsI18nProvider` / `NotificationQueueProvider`.

## Core Patterns

### Use a block composition

```tsx
import {
  DashboardPageHeader,
  DashboardPageHeaderTitle,
} from '@aircall/blocks';

function CampaignsHeader() {
  return (
    <DashboardPageHeader>
      <DashboardPageHeaderTitle size="lg">Campaigns</DashboardPageHeaderTitle>
    </DashboardPageHeader>
  );
}
```

## Common Mistakes

### HIGH — Re-importing DS/blocks globals in a module-federation consumer

Wrong — in a consumer whose host already loads DS globals:

```css
@import 'tailwindcss';
@import '@aircall/ds/globals.css';
@import '@aircall/blocks/globals.css';
```

Correct — consumer owns only its own utilities, no Preflight:

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

In a module-federation setup, CSS from each remote lands in the **same document**
as the host. DS globals ships a Preflight (`@layer base { * { border-color: var(--color-border); } }`)
that sets the token-based border color globally. If the consumer also imports DS globals
(or the full `@import 'tailwindcss'`), it emits a second `@layer base` reset. Because
webpack/Rsbuild emits deep dependencies first and the consumer's own CSS last, the
consumer's Preflight (`border-color: currentColor`) ends up later in the output — and
later wins. DS component borders (Card, etc.) lose their token color and become invisible.

### HIGH — Importing blocks globals before DS globals (or omitting DS globals)

Wrong — wrong order, or omitting DS globals:

```css
@import '@aircall/blocks/globals.css';
@import '@aircall/ds/globals.css';
```

```css
/* missing DS globals entirely */
@import '@aircall/blocks/globals.css';
```

Correct — DS globals first, then blocks globals:

```css
@import '@aircall/ds/globals.css';
@import '@aircall/blocks/globals.css';
```

`@aircall/blocks/globals.css` is a delta build — it contains only blocks-specific tokens
and utility classes. It depends on DS globals being loaded first to provide Preflight and
DS token definitions. Loading blocks before DS means DS's base reset (`border-color:
var(--color-border)`) lands after blocks and within the same `@layer base`, and component
tokens like `--background` and `--border` may not be defined when blocks tries to use them.

Source: aircall/hydra:docs/migration-guides/tractor-to-ds/00-setup.md (§3)

### MEDIUM — Using bare specifiers without `.css` in Rsbuild/webpack

Wrong:

```css
@import 'tailwindcss/theme';
@import 'tailwindcss/utilities';
```

Correct:

```css
@import 'tailwindcss/theme.css';
@import 'tailwindcss/utilities.css';
```

The bare form (no `.css`) silently produces no output in webpack/Rsbuild PostCSS
pipelines — classes like `flex`, `gap-4`, `text-sm` are never generated. Always
use the `.css` extension when splitting Tailwind imports.

### MEDIUM — Installing @aircall/blocks without @aircall/ds

Wrong:

```bash
pnpm add @aircall/blocks
```

Correct:

```bash
pnpm add @aircall/blocks @aircall/ds
```

Blocks import from `@aircall/ds` at runtime; without it the blocks barrel fails to resolve its peer and the build breaks.

Source: aircall/hydra:packages/blocks/package.json (peerDependencies)

### MEDIUM — Expecting a separate BlocksProvider

Wrong: searching for a `BlocksProvider` to mount.

Correct: blocks render under the DS providers from aircall-ds/setup; mount those, not a blocks-specific provider.

There is no blocks-specific provider; assuming one wastes time and produces dead wiring.

Source: aircall/hydra:docs/migration-guides/tractor-to-ds/00-setup.md (§4)
