# Setup & Configuration

> Generated reference (regen-owned; see [index.md](index.md)). Loads only under `primitives: nurix` — this is the wiring a project needs before any `@nurix/components` import resolves.

## Package

`@nurix/components` — published to the public npm registry (`registry.npmjs.org`) under the restricted `@nurix` scope. Ships `dist/` and `capabilities.json`. Required peers: `react ^19`, `react-dom ^19`; `@types/react` / `@types/react-dom` are optional peers for TypeScript consumers. Core dependencies install with the package: `@base-ui/react`, `class-variance-authority`, `cn`, `date-fns`, `lucide-react`, `motion`, `nanoid`, `react-day-picker`, `zod`. Source of truth: `github.com/nurixlabs/components`.

## Installing

The scope is restricted, so the consumer project needs an npm token with read access to `@nurix` in its `.npmrc`:

```ini
//registry.npmjs.org/:_authToken=${NPM_READ_TOKEN}
```

Then install with the project's package manager (detect from the lockfile): `npm install @nurix/components`.

## Optional peer families

Heavy third-party packages are **optional peers**, grouped into families — install a family only when an imported subpath reaches it. A missing family fails at the bundler with the package named (`Cannot resolve "@visx/scale"`), never silently at runtime. Copy ranges from `node_modules/@nurix/components/package.json` `peerDependencies`.

| Family | Subpaths that reach it | Install |
| --- | --- | --- |
| `charts` | `block/chart` | `@visx/curve @visx/event @visx/grid @visx/group @visx/heatmap @visx/pattern @visx/responsive @visx/scale @visx/shape d3-array` |
| `spark` | `element/spark`, `block/table` | `@mantine/charts @mantine/core recharts` (plus `@mantine/hooks`, which `@mantine/core` peers on) |
| `table` | `block/table` | `@tanstack/react-table` |
| `code` | `compound/code-block` | `shiki` |
| `editor` | `block/tiptap-editor` | `@tiptap/core @tiptap/extension-image @tiptap/extension-table @tiptap/extension-task-item @tiptap/extension-task-list @tiptap/pm @tiptap/react @tiptap/starter-kit tiptap-markdown` |
| `markdown` | `element/markdown` | `react-markdown remark-gfm` |
| `sortable` | `compound/sortable` | `@dnd-kit/core @dnd-kit/modifiers @dnd-kit/sortable @dnd-kit/utilities` |
| `carousel` | `compound/carousel` | `embla-carousel-react embla-carousel-autoplay` |
| `icon-picker` | `compound/icon-picker` | `@tanstack/react-virtual fuse.js` |
| `resizable` | `block/resizable` | `react-resizable-panels` |
| `mic-input` | `compound/mic-input` | `react-voice-visualizer` |
| `issue-reporter` | `block/issue-reporter` | `html2canvas-pro` |
| `animated-number` | `element/animated-number`, `block/chart` | `@number-flow/react` |
| `anchored-scroller` | `block/anchored-scroller` | `@shadcn/react` |
| `file-picker` | `lib/uppy-adapter` | `@uppy/core` |

## capabilities.json

`@nurix/components/capabilities.json` is the machine-readable manifest — one record per published subpath: `subpath`, `grain` (`element` · `compound` · `block` · `effect` · `hooks` · `lib`), `category`, `tier`, `contractLevel`, `taxonomy`, `symbols` (`{ name, typeOnly? }`), and `peers: [{ family, packages }]` where the subpath needs an optional family (no `peers` field = core only). Read it to resolve which families a project's imports need, or to confirm a symbol exists before importing it.

## Styles (required)

In the consumer's CSS entry, in this order:

```css
@import "./fonts.css";           /* @fontsource registrations */
@import "./design.tokens.css";   /* the product's brand layer (design skill emission) */
@import "tailwindcss";
@import "@nurix/components/styles.css";
@import "./personality.css";     /* the product's compiled personality (design skill emission) */
```

`personality.css` follows `styles.css` because its bare `:root` / `.dark` blocks only tie the library's own (0,1,0) personality defaults and win on import order.

The shipped `styles.css` is **not** precompiled — it carries the design tokens, the personality token layer at neutral defaults, the base layer, the custom variants and utilities, the library's keyframes, and an embedded `@source "./**/*.js"` directive, so the consumer's Tailwind v4 build scans the package's compiled JS and generates every utility class the components use. The project must have Tailwind v4 set up; it does **not** add its own `@source` for this package.

- **Dark mode is the `.dark` class** on an ancestor (`@custom-variant dark (&:is(.dark *))`), never a `data-theme` attribute — the library's dark values compile as `.dark { … }`, so an attribute switch inverts the product's tokens while every library surface stays light.
- **The cascade has three layers:** brand (`:root` — the shadcn colour vocabulary, `--radius`, the type ramp), personality (`--motion-*`, `--density-*`, `--radius-scale`, edge geometry, `--elevation-*`, `--texture-grain-opacity`), and mode (`.dark`, restating values only). The design skill's `design.tokens.css` armors its theme blocks as `:root:not(.dark)` / `:root.dark` (0,2,0), so the library's (0,1,0) blocks never defeat them whatever the import order.
- **Roles a product may claim:** `--success`, `--warning`, `--info` retint `--intent-success|warning|info` (the colours the `intent` prop and `data-intent` select); `--intent-danger` aliases `--destructive`. Declaring nothing ships theme-correct fallbacks. The switch is global — a subtree wanting its own status colours redeclares `--intent-*`, never `--success`.
- **Brand-layer tokens beyond shadcn's set:** `--destructive-foreground`, `--input-surface`, `--scrim` / `--scrim-foreground` (a mode-invariant pair for backdrops and captions over imagery), `--radius-xs` … `--radius-xl` (each scaled by `--radius-scale`), and the type-ramp roles `text-display`, `text-title`, `text-h1`–`text-h3`, `text-prose`, `text-body`, `text-control-lg`, `text-dense`, `text-control`, `text-meta`, `text-micro`, `text-code`, `text-ident`, plus `--text-inline` (0.875em, for text-flow pieces).
- **No font family ships.** `--font-display`, `--font-sans`, `--font-mono` are declared slots falling back to `inherit`; the product's `design.tokens.css` supplies the faces.

## Shipped utilities

Set per element at the call site; each reads the personality tokens, so a trait change moves them with no class change.

- `focus-ring` — the one focus ring (`--ring-width`, `--ring-offset`, `ring-ring/50`) on `:focus-visible`.
- `focus-ring-within` — the same ring on a host frame, lit only while its descendant carrying `data-focus-target` shows keyboard focus.
- `hover-wash` — a `currentColor` wash: 6% on hover (under `hover: hover`), `[data-highlighted]` and `[data-popup-open]`; 12% on `:active` and `[data-dragging]`; never on disabled or read-only parts, never fighting a `bg-*` fill.
- `interactive:` variant — matches a part that is not `:disabled`, `[data-disabled]`, `[aria-disabled="true"]`, `[data-readonly]` or `[aria-readonly="true"]` (`interactive:cursor-pointer`).
- `hit-area`, `hit-area-*`, `hit-area-{l,r,t,b,x,y}-*`, `hit-area-debug` — a pointer target larger than the drawn box.
- `clip`, `clip-*`, `clip-lines`, `clip-fade` — line clamps and fade-outs whose line count is set at runtime.

## Editor stylesheet

`block/tiptap-editor` needs its document stylesheet, imported once beside the component or in the CSS entry: `import "@nurix/components/tiptap-editor.css";`. It carries the `--doc-*` tokens at zero specificity (`:where(.nx-tiptap, .nx-doc)`) and the `.nx-doc` rules, deliberately unlayered so a host's layered preflight cannot strip the document's typography; override with `.nx-tiptap { --doc-accent: … }` from anywhere.

## Personality

`@nurix/components/lib/personality` compiles a **PersonalitySpec** — six orthogonal traits — into the token layer every component reads. The product declares the feel in its `design.md` (`personality:`); the design skill's validator (`validate-design-md.mjs --emit-personality --write`) runs `compileToCss` from the project's own install and writes the committed `personality.css`, imported after `styles.css` so its `:root` wins on order.

| Trait | Values | Default | Token family |
| --- | --- | --- | --- |
| `density` | `compact` · `regular` · `airy` | `regular` | `--density-control-height`, `--density-control-padding`, `--density-pad-{xs,sm,md,lg}`, `--density-gap-{sm,md,lg}` |
| `pace` | number `0`–`1` (0 = still, 0.5 = neutral, 1 = lively) | `0.5` | `--motion-duration-{fast,base,slow}`; `--motion-ease-standard` turns lively at ≥ 0.8 |
| `edge` | `sharp` · `regular` · `soft` | `regular` | `--radius-scale`; `sharp` also narrows `--ring-width`, `--edge-hairline` and `--edge-stroke`, and squares `--radius-pill` |
| `depth` | `flat` · `layered` · `elevated` | `layered` | `--elevation-1..3` |
| `texture` | `clean` · `grainy` | `grainy` | `--texture-grain-opacity` |
| `accent` | `neutral` · `expressive` | `neutral` | `--accent`, `--accent-foreground`, `--ring` (expressive re-anchors them on `--primary`) |

- **Spec shape:** `{ version: 1, ...traits }`, strict — any key outside the table is rejected. Declare only the traits that differ from neutral; omitted traits resolve to `PERSONALITY_DEFAULTS`, and the empty spec compiles to the base feel.
- **Validate at the edge — `compileToCss` does not validate input.** `validatePersonality(input)` → `{ ok: true, spec } | { ok: false, issues: [{ path, message }] }` (JSON-pointer paths). `diagnosePersonality(spec)` → `{ warnings }` for legal but self-defeating combinations (`still-expressive`, `flat-grain`, `compact-soft`). `explainPersonality(spec)` → each trait's value with its source (`spec` | `default`) plus the tokens it emits. `personalityJsonSchema` / `PersonalitySpecSchema` (zod) are the tool-definition and parse surfaces; `resolvePersonality(partial)` fills defaults without validating.
- **Emitters:** `compileToCss(spec)` → the full stylesheet (`:root`, a paired `.dark` block, the reduced-motion block last); it throws on a contrast-floor or mode-pairing invariant violation. `compileToVars(spec)` → the `:root` values as a style object (no mode pairing, no media queries). `deriveTokens(resolved)` → the raw `TokenEmission[]`.
- **`<PersonalityStyle spec={spec} />` is a runtime switch only** — it installs one shared `<style id="nurix-personality-style">` (newest mount wins, removed on last unmount) for a density toggle or a preview, never in place of the committed `personality.css`.
- **App code stays inside the trait's reach by reading tokens, not literals:** `p-(--density-pad-md)`, `gap-(--density-gap-sm)`, `duration-(--motion-duration-base)`, `shadow-(--elevation-2)`. A plain `transition-*` inherits `--motion-duration-fast`; a `duration-*` literal opts that element out. `data-mode="inline"` on a collection container zeroes the motion durations beneath it.

```tsx
import { PersonalityStyle, validatePersonality } from "@nurix/components/lib/personality";

const result = validatePersonality({ version: 1, density: "compact", pace: 0.3, edge: "sharp" });

export function DensityPreview() {
  return result.ok ? <PersonalityStyle spec={result.spec} /> : null;
}
```

## Import pattern

Use subpath imports — every published entry is one `<layer>/<name>` subpath (`element/`, `compound/`, `block/`, `effect/`, `hooks/`, `lib/`), plus the `styles.css`, `tiptap-editor.css` and `capabilities.json` assets. There is no root barrel:

```tsx
import { Button } from "@nurix/components/element/button";
import { Cell } from "@nurix/components/compound/cell";
import { defineColumn } from "@nurix/components/block/table";
```
