---
name: catnip
description: >-
  Use Signicat Catnip in product apps: catnip-* custom elements, React wrappers,
  @signicat/catnip-css, design tokens, icons, and assets. Use when writing Vue, React,
  HTML, or CSS with Catnip packages or Catnip docs.
---

# Catnip

Signicat’s **framework-agnostic** design system. Packages are separate; most apps import **components + CSS**. Public UI is **`catnip-*` custom elements**, not Vue SFCs and not a second React component set.

Resolve Catnip docs against the **origin of this file**:

- Development: `https://catnip.signicat.dev`
- Production: `https://catnip.signicat.com`

Docs map: `{origin}/llms.txt`

## Packages

| npm | Role | Docs |
| --- | --- | --- |
| `@signicat/catnip-components` | Registers all `catnip-*` tags | `{origin}/components/` |
| `@signicat/catnip-components-react` | Generated React wrappers around the same elements | `{origin}/components/react.html` |
| `@signicat/catnip-css` | Tokens + icon webfont + Inter + utilities (required for styled components) | `{origin}/css/` |
| `@signicat/catnip-design-tokens` | `--catnip-*` CSS variables and JS token objects; dark / EndUI themes | `{origin}/tokens/` |
| `@signicat/catnip-icons` | Icon webfont + SVG map | `{origin}/assets/icons/overview.html` |
| `@signicat/catnip-assets` | Inter, illustrations, flags, country metadata, raw SVGs | `{origin}/assets/` |

Install / registry / CDN: `{origin}/developer-guide/installation.html`

**npm** is Signicat’s GitLab registry (`@signicat`). **CDN** (no registry): `{base}{package}/{version|latest}/{file}` on `https://static.signicat.com/catnip/` (production) or `https://static.signicat.dev/catnip/` (development). Packages are **not** on the public npm registry.

## Install

Typical app (Vue / HTML / other):

```js
import "@signicat/catnip-components";
import "@signicat/catnip-css";
```

The components import **registers** all `catnip-*` tags. **`@signicat/catnip-css`** supplies `--catnip-*` tokens, the icon webfont, Inter, and utilities — without it, components register but will not look correct. Optional FOUC CSS: `@signicat/catnip-components/foucPrevention.min.css`.

**React:** install `@signicat/catnip-components-react` and `@signicat/catnip-css`. Import CSS + wrappers; do **not** also `import "@signicat/catnip-components"` in an ordinary React app. Child micro-frontends whose shell already owns Catnip use `@signicat/catnip-components-react/wrappers`.

**Tokens / icons / assets** are usually pulled in by the CSS package on npm. Install them on their own to import a specific CSS/JS token file, the icon webfont without the full CSS bundle, or raw illustrations / flags / fonts.

**CDN without a bundler:** load `css/latest/styles.css` and `components/latest/catnip-components.standalone.js` (IIFE; use `<script defer>`, not `type="module"`). Pin `{version}` in production.

**One instance:** `catnip-*` tags register **once per document**. The **shell** mounts components + CSS; remotes reuse the tags and `window.Catnip`. See `{origin}/developer-guide/installation.html#one-instance`.

## CSS

```js
import "@signicat/catnip-css";
```

```css
@import "@signicat/catnip-css";
```

Optional: `@signicat/catnip-css/global-reset`, `@signicat/catnip-css/grid`.

npm `styles.npm.css` `@import`s **`@signicat/catnip-design-tokens`** (default + dark) and **`@signicat/catnip-icons`**, and resolves Inter via **`@signicat/catnip-assets`**. CDN `styles.css` inlines tokens + icons and loads Inter from `./fonts/inter/…`.

**Custom CSS** uses tokens, not invented hex/px for color, type, space, radius, or shadow:

```css
.my-card {
  color: var(--catnip-color-content-neutral-strongest);
  background: var(--catnip-color-background-neutral-subtlest);
  border: 1px solid var(--catnip-color-border-neutral-subtlest);
  box-shadow: var(--catnip-shadow-elevation-1);
}
```

Utility classes (full list: `{origin}/css/functional-classes.html`): `.catnip-font-heading-m`, `.catnip-font-text-m`, `.catnip-p-m`, `.catnip-m-t-s`, `.catnip-text-center`. Spacing scale: `3xs` … `4xl`.

## Design tokens and theming

Usually you do **not** install tokens separately if you already import `@signicat/catnip-css`. Import tokens alone when you need CSS/JS files without the full CSS bundle:

```css
@import "@signicat/catnip-design-tokens/css";
@import "@signicat/catnip-design-tokens/css/dark"; /* optional */
@import "@signicat/catnip-design-tokens/css/endui"; /* optional; not in catnip-css */
```

```js
import * as tokens from "@signicat/catnip-design-tokens/js";
```

| Theme | Activate |
| --- | --- |
| Default (Signicat light) | `:root` |
| Dark | `data-theme="dark"` on `<html>` or a subtree |
| EndUI (whitelabel: blue brand, squared main buttons) | import EndUI CSS, then `data-theme="endui"` |

`data-theme` is **one value at a time** — EndUI and dark do not combine in this release.

Token groups: `--catnip-color-*`, `--catnip-font-*`, `--catnip-space-*`, `--catnip-radius-*`, `--catnip-shadow-*` (elevation-1…4, focus-default, focus-danger), component tokens such as `--catnip-button-radius` and `--catnip-graphic-primary` / `--catnip-graphic-accent`.

Override on `:root` or a wrapper; do not hardcode product colors when a token exists. Theming: `{origin}/developer-guide/theming.html`

## Icons

**In components:** `<catnip-icon name="user" size="24"></catnip-icon>` — `name` is the kebab-case SVG filename without extension. Catalogue: `{origin}/assets/icons/overview.html`

**Webfont** (already in `@signicat/catnip-css`): class **`.catnip-icon--{name}`**. Icons inherit `currentColor`. Default size with the CSS package is **24px**; otherwise set `font-size`. Do **not** use a bare `<i>` as an icon.

```html
<i class="catnip-icon--user"></i>
```

Standalone webfont: `import "@signicat/catnip-icons"` or `@import "@signicat/catnip-icons"`. SVG map: `@signicat/catnip-icons/svg`.

## Assets

Illustrations, pictograms, product marks, and Signicat logos: **`catnip-graphic`**. `name` is the path under `illustrations/` without `.svg` (kebab-case). Same SVG for light/dark — colors come from `--catnip-graphic-primary` / `--catnip-graphic-accent`.

```html
<catnip-graphic name="products/eid-hub"></catnip-graphic>
<catnip-graphic name="pictograms/folder" width="64" height="64"></catnip-graphic>
```

Raw SVG strings: `import { loadIllustration, illustrationNames } from "@signicat/catnip-assets/illustrations/svg"`. Unknown keys resolve to an empty string.

**Flags:** ISO alpha-2. `loadCountryFlag("NO")` from `@signicat/catnip-assets/countries/flags/svg`. Metadata: `import countries from "@signicat/catnip-assets/countries"` (`name`, `code`, `dial_code`, `timezones`).

**Fonts:** Inter is included when you load `@signicat/catnip-css`. Manual `@font-face` uses `@signicat/catnip-assets/fonts/inter/…`.

Assets overview: `{origin}/assets/` · Graphic: `{origin}/components/graphic/overview.html`

## Components (custom elements)

1. Tags are **kebab-case**: `catnip-button`, `catnip-input`, `catnip-table-cell`.
2. Prop tables use **camelCase**. Plain HTML uses **kebab-case** attributes (`model-value`, `test-selectors`).
3. Read **that component’s specs** before inventing props, slots, or events. A shared vocabulary does not mean every component accepts every value.

### Vue 3

Tell the compiler **`catnip-*` tags are custom elements**:

```ts
import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";

export default defineConfig({
  plugins: [
    vue({
      template: {
        compilerOptions: {
          isCustomElement: (tag) => tag.startsWith("catnip-")
        }
      }
    })
  ]
});
```

Nuxt: the same `isCustomElement` under `vue.compilerOptions` in `nuxt.config.ts`.

For **any reactive / non-literal** value, bind with Vue’s **leading-dot** shorthand in **camelCase** so the value sets a **DOM property**:

```vue
<catnip-input .modelValue="value" .size="'m'" .testSelectors="selectors" />
```

- Do: `.modelValue="x"`, `.disabled="isOff"`, `.catnipInput="{ placeholder: 'Add' }"`
- Do not: `:model-value`, `:modelValue`, `:size` (attribute path — breaks objects, booleans, numbers)
- Static literals may stay plain attributes: `size="m"`

**Two-way binding:** do **not** use `v-model` on `catnip-*` hosts. Use `.modelValue` + `@update:modelValue` and unwrap **`event.detail`** (often **`detail[0]`**). Where documented, `v-catnip-model` is the alternative.

**Slots** are Shadow DOM slots. Project **light DOM** children with the HTML **`slot`** attribute — not `<template #name>` / `v-slot`. Slot names are **camelCase** (`slot="leftIconSlot"`).

```vue
<catnip-tooltip>
  <button type="button" slot="anchor">Hover me</button>
  Tooltip body
</catnip-tooltip>
```

Vue + custom elements: `@update:modelValue` works; all-lowercase names like `@update:open` do **not** attach. Modals use `open` + `update:open` (listen with `addEventListener`). Popovers and fields use `.open` + `@open-change`.

### React

Use PascalCase wrappers and camelCase props. Wrappers assign booleans, numbers, objects, and arrays as **host properties**. Use `className` / `style`, generated `on*` event props, and **`parseCatnipEventDetail`** for payload events. Named slots still use the HTML **`slot`** attribute on children.

```tsx
<CatnipToggle
  modelValue={enabled}
  onUpdateModelValue={(event) => {
    setEnabled(parseCatnipEventDetail<boolean>(event) ?? false);
  }}
/>
```

### HTML and other frameworks

- Attributes: kebab-case. Booleans are often **presence-based** (`disabled`).
- Objects / arrays: set **properties** in JavaScript, or a JSON string on the attribute when the component documents that (for example `test-selectors`).
- Events: `addEventListener` on the host; payloads on **`event.detail`**.
- Angular: `CUSTOM_ELEMENTS_SCHEMA`; follow the same attribute vs property rules.

### Composed APIs

When a parent exposes a child Catnip API, forwarded props are a nested object named after the child. Keep the child’s prop names unchanged inside:

```vue
<catnip-input-chip .catnipInput="{ placeholder: 'Add a tag', leftIcon: 'search' }" />
```

Parent-owned behaviour stays **top-level** (`modelValue` on the chip list). Forwarded slots use `{childNamespace}.{slotName}` (`slot="catnipInput.leftIconSlot"`).

## Source of truth

| Need | Where |
| --- | --- |
| Install, CDN, micro-frontends | `{origin}/developer-guide/installation.html` |
| CSS utilities | `{origin}/css/` · `{origin}/css/functional-classes.html` |
| Tokens / theming | `{origin}/tokens/` · `{origin}/developer-guide/theming.html` |
| Icons catalogue | `{origin}/assets/icons/overview.html` |
| Assets (illustrations, flags, fonts) | `{origin}/assets/` |
| Binding rules (props, slots, events, test selectors) | `{origin}/components/component-api.html` |
| Framework tour | `{origin}/components/usage.html` |
| Per-component API | `{origin}/components/{slug}/specs.html` |
| Docs map | `{origin}/llms.txt` |
| Machine API | `{origin}/custom-elements.json` or `@signicat/catnip-components/custom-elements.json` |
| HTML editor data | `{origin}/html-custom-data.json` or `@signicat/catnip-components/html-custom-data.json` |
| Skill (offline copy) | `@signicat/catnip-components/SKILL.md` after install |

Do not invent props, slot names, events, token names, icon names, or illustration keys. Consumers own validation copy and drive semantic state through documented props such as **`intent`**.

## Anti-patterns

- Treating `catnip-*` as Vue SFCs (`CatnipButton` in Vue templates, `<template #slot>`, `v-model` on the host)
- Skipping `isCustomElement` for `catnip-*` in the Vue compiler
- Vue `:kebab-case` or `:camelCase` without the leading dot for reactive / non-literal values
- Importing components without `@signicat/catnip-css` (or CDN `styles.css`)
- Hardcoding colors / type / space when a `--catnip-*` token exists
- Using a bare `<i>` as an icon instead of `.catnip-icon--{name}` or `catnip-icon`
- Duplicate light/dark illustration files — theme via graphic tokens
- Inventing `inputPlaceholder`-style props instead of nested `catnipInput`
- Hardcoding product validation strings inside generic field usage
- Assuming every component accepts every shared size / intent / appearance value
- Importing a second Catnip runtime in a micro-frontend whose shell already mounted it
