Agent skill
A portable skill so coding agents use the whole Catnip design system in a product app: custom elements, React wrappers, CSS, tokens, icons, and assets.
This is for using Catnip in a product codebase. If you are contributing to the Catnip repository itself, see Contributing instead.
The file follows the Agent Skills convention (name + description frontmatter and markdown). Any coding agent that loads that format can use it.
Add it to an agent
Point the agent at the published file. That URL stays current as the skill changes. Use the file from this docs environment:
| Environment | URL |
|---|---|
| Development | https://catnip.signicat.dev/skills/catnip/SKILL.md |
| Production | https://catnip.signicat.com/skills/catnip/SKILL.md |
If the agent cannot fetch URLs, download the skill file and save it as SKILL.md inside a folder named catnip in whatever skills directory it already uses, for example <agent-skills-root>/catnip/SKILL.md. Common roots include .agents/skills/ and .claude/skills/. Refresh that copy when the published file changes.
After you install the library, the same file ships as @signicat/catnip-components/SKILL.md (and @signicat/catnip-components-react/SKILL.md). Prefer the published URL so the skill stays current.
The Custom Elements Manifest for this docs environment is /custom-elements.json. HTML editor data is /html-custom-data.json.
Related docs
- llms.txt — index of Catnip docs pages for agents
- Installation — npm, CDN, Vue compiler, TypeScript, editor custom data
- CSS · Tokens · Theming · Assets
- Component API — props, slots, events, test selectors
- Usage — Vue, React, Angular, and HTML patterns
Skill file
The published file currently contains:
---
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