@frontify/fondue
Version:
Design system of Frontify
208 lines (154 loc) • 9.04 kB
Markdown
---
name: fondue
description: Queries the locally installed `@frontify/fondue/sdk` to surface accurate Fondue component metadata. ALWAYS use when picking a Fondue component (button, dialog, dropdown, tabs, …), finding a design token or utility class, or setting up the `@frontify/fondue` package.
allowed-tools: Bash(node *)
---
# Fondue
Use `@frontify/fondue/sdk` as the source of truth for every Fondue question. Never invent component names, props, token ids, or imports from memory — query the SDK.
**Runs against the project's locally installed Fondue.** Invoke `node` from the project root (or a subdirectory) so `@frontify/fondue` resolves from `node_modules` — results reflect the exact version this project depends on, not the version the skill was authored against.
## Verify the SDK is available
```bash
node -e "import('@frontify/fondue/sdk').then((m) => console.log(JSON.stringify({ components: m.components.size, tokens: m.tokens.size, guides: m.guides.size })))"
```
If that errors with `Cannot find package`, the project hasn't installed Fondue yet:
```bash
pnpm add @frontify/fondue # or npm i / yarn add
```
## How to query
The SDK is a synchronous, zero-I/O ES module that resolves from the user's project. Three singletons share the same query surface:
```ts
import { components, tokens, guides } from '@frontify/fondue/sdk';
// Same shape on each domain:
components.list();
components.get('Button'); // ComponentNode | undefined (never throws)
components.has('Button'); // boolean
components.where({ text: 'dropdown' });
components.size; // number
```
`where(filter)` clauses AND-combine; array-valued clauses OR within the clause. `get` and `where` never throw; missing ids return `undefined` / `[]`.
Nodes have **scalar fields** (read as properties) and **graph edges** (call as methods):
```ts
const button = components.get('Button');
button?.importStatement; // scalar
button?.props; // scalar (array)
button?.category(); // edge → ComponentFacetNode { name, list, where, … }
button?.related(); // edge → ComponentNode[]
```
### Running queries from Claude
For one-liners, use `node -e`:
```bash
node -e "import('@frontify/fondue/sdk').then(({components}) => console.log(JSON.stringify(components.where({text:'dropdown'}).map((c)=>({name:c.name,import:c.importStatement,canonical:c.examples.find((e)=>e.isCanonical)?.code})))))"
```
For anything multi-line, pipe a module via stdin to avoid quoting hell:
```bash
node --input-type=module <<'EOF'
import { components } from '@frontify/fondue/sdk';
const candidates = components.where({ text: 'dropdown', status: 'released' }).slice(0, 5);
console.log(JSON.stringify(candidates.map((c) => ({
name: c.name,
category: c.category().name,
tags: c.tags().map((t) => t.name),
import: c.importStatement,
canonical: c.examples.find((e) => e.isCanonical)?.code ?? c.examples[0]?.code ?? null,
})), null, 2));
EOF
```
Always emit `JSON.stringify(...)` so the response is parseable. Never `console.log(node)` directly — facet methods serialize as `[Function]`.
For the formal contract (every filter clause, node shape, facet method), see [`reference.md`](./reference.md).
---
## Workflow 1 — Setting up Fondue
Read the canonical setup prose from the SDK rather than paraphrasing — the steps change between releases (font URLs, package layout, theme provider API).
```bash
node -e "import('@frontify/fondue/sdk').then(({guides}) => console.log(guides.get('getting-started')?.content))"
```
For upgrades:
```bash
node -e "import('@frontify/fondue/sdk').then(({guides}) => console.log(guides.get('upgrading')?.content))"
```
To discover other bundled guides:
```bash
node -e "import('@frontify/fondue/sdk').then(({guides}) => console.log(JSON.stringify(guides.list().map((g)=>({id:g.id,title:g.title})))))"
```
The output is the same markdown the Storybook docs render. Use it as the source for token imports, component-style imports, font face definitions, the Tailwind preset, `ThemeProvider`.
## Workflow 2 — Finding a component
Start with intent-based search:
```bash
node --input-type=module <<'EOF'
import { components } from '@frontify/fondue/sdk';
console.log(JSON.stringify(components.where({ text: 'dropdown' }).map((c) => ({
name: c.name,
category: c.category().name,
status: c.status,
description: c.description,
})), null, 2));
EOF
```
Narrow by category or tag when the use case maps cleanly:
```ts
components.where({ category: 'overlay', status: 'released' });
components.where({ tag: 'cta' });
components.where({ category: 'input', tag: 'cta' });
```
Once you have a candidate, pull the recommendation shape:
```bash
node --input-type=module <<'EOF'
import { components } from '@frontify/fondue/sdk';
const c = components.get('Button');
console.log(JSON.stringify({
name: c.name,
description: c.description,
importStatement: c.importStatement,
canonical: c.examples.find((e) => e.isCanonical)?.code ?? c.examples[0]?.code,
requiredProps: c.props.filter((p) => p.required).map((p) => p.name),
subComponents: c.subComponents.map((sc) => sc.name),
related: c.related().map((r) => r.name),
instructions: c.instructions,
}, null, 2));
EOF
```
**Recommendation shape** — when you suggest a component, output: the name, what it's for, the import statement, and the canonical example. If the use case isn't a clean match, list 2–3 candidates with their categories and let the user pick.
**Icons** live in the components graph under `category: 'icon'`. They have empty `status`, empty `props`, empty `related()` — don't probe those fields. Find them with:
```ts
components.where({ category: 'icon', tag: 'arrow' });
components.get('IconAdobeCreativeCloud')?.importStatement;
```
When nothing matches:
- Try a related tag — `components.tags().map((t) => t.name)` lists every tag.
- Look at the closest hit's `related()`.
- If there truly is no component for the use case, say so. Don't recommend hand-rolling something Fondue already provides, but don't invent a component name either.
## Workflow 3 — Picking tokens for a custom component
Two layers exist:
- `tokens` — atomic design tokens (colors, sizes, shadows, strings). Each has a CSS variable and a Tailwind class.
- `tokens.utilities` — composed Tailwind utilities (typography classes like `tw-body-large-strong`) bundling multiple token references.
```ts
tokens.where({ text: 'primary' });
tokens.where({ category: 'colors', themeable: true });
tokens.where({ keyPathStartsWith: 'colors.text' });
tokens.type('color')?.where({ themeable: true });
const t = tokens.get('color-text-weak');
t?.value; // 'var(--color-text-weak)'
t?.cssVariable; // '--color-text-weak'
t?.tailwindClass; // 'tw-text-weak'
t?.themeable; // true
// Typography: always use the utility class, not raw font tokens
tokens.utilities.where({ keyPathStartsWith: 'utilities.text' });
```
Rules:
- Prefer `themeable: true` tokens for any user-facing surface so dark mode and theming work out of the box.
- Prefer the Tailwind class when the project uses `@frontify/fondue/tokens/tailwind`; prefer the CSS variable otherwise.
- For typography, recommend the **utility class** — not raw font-size / line-height / weight tokens.
- Never invent token ids. If `tokens.get(id)` returns `undefined`, search again.
- If the design calls for a value with no matching token, surface that explicitly — don't silently hardcode. The right answer is usually a token the user didn't know existed.
---
## Common pitfalls
| Mistake | Fix |
| ------- | --- |
| Calling `.where()` / `.get()` on the array returned by `list()` / `where()` | Arrays are arrays. Use native `.filter` / `.find`, or navigate back to a facet to query. |
| Assuming a component exists ("there must be a `Combobox`") | `components.has('Combobox')` first, or `components.where({ text: 'combobox' })` |
| Hardcoding a hex / px value in custom code | `tokens.where({ text: '<intent>' })` — most "obvious" values have a token |
| Treating icons like normal components, reading `status` / `props` | Detect with `node.category().name === 'icon'`. Icons have empty `status`, `props`, `related`. |
| Importing `Button` from the wrong path | `components.get('Button')?.importStatement` is authoritative |
| Suggesting setup steps from memory | `guides.get('getting-started')?.content` — always the live text |
| `console.log(node)` instead of `JSON.stringify` | Facet methods serialize as `[Function]`; always stringify before logging |
## Going deeper
[`reference.md`](./reference.md) is the formal contract: every export, every filter clause per domain, every node and facet type, error semantics. Load it when you need to verify a filter clause is valid (`ComponentFilter`, `TokenFilter`, `GuideFilter`) or that a method exists. Prefer it over web lookups or upstream GitHub.