Chip
A small on or off filter, often with a count.
Preview
tsx
import { state } from '@kerf-js/core'import { Chip, Row } from '@kerf-js/veneer'export function Filters() { const on = state(['Live']) const flip = (name: string) => (on.value = on.value.includes(name) ? on.value.filter((n) => n !== name) : [...on.value, name]) return ( <Row gap="tight"> {['Live', 'Building', 'Failed', 'Paused'].map( (name) => ( <Chip key={name} pressed={on.value.includes(name)} count={ PROJECTS.filter((p) => p.status === name) .length } onClick={() => flip(name)} > {name} </Chip> ), )} </Row> )}type Status = | 'Live' | 'Building' | 'Failed' | 'Paused'const PROJECTS: { id: string name: string owner: string region: string status: Status deploys: number uptime: number latency: number cost: number updated: string}[] = [ { id: 'storefront', name: 'storefront', owner: 'Priya Raman', region: 'eu-west', status: 'Live', deploys: 412, uptime: 99.98, latency: 84, cost: 1240, updated: '2026-10-31', }, { id: 'checkout', name: 'checkout', owner: 'Tomás Oliveira', region: 'eu-west', status: 'Live', deploys: 288, uptime: 99.95, latency: 112, cost: 980, updated: '2026-10-31', }, { id: 'search-api', name: 'search-api', owner: 'Lena Fischer', region: 'us-east', status: 'Building', deploys: 196, uptime: 99.91, latency: 146, cost: 1610, updated: '2026-10-30', }, { id: 'billing', name: 'billing', owner: 'Mara Lindqvist', region: 'us-east', status: 'Live', deploys: 154, uptime: 99.99, latency: 73, cost: 720, updated: '2026-10-29', }, { id: 'mobile-bff', name: 'mobile-bff', owner: 'Sam Okafor', region: 'ap-south', status: 'Failed', deploys: 233, uptime: 99.42, latency: 208, cost: 860, updated: '2026-10-29', }, { id: 'notifier', name: 'notifier', owner: 'Jun Tanaka', region: 'eu-north', status: 'Live', deploys: 97, uptime: 99.97, latency: 58, cost: 310, updated: '2026-10-27', }, { id: 'reports', name: 'reports', owner: 'Noor Haddad', region: 'us-west', status: 'Paused', deploys: 41, uptime: 99.8, latency: 321, cost: 190, updated: '2026-10-22', }, { id: 'edge-cache', name: 'edge-cache', owner: 'Jun Tanaka', region: 'global', status: 'Live', deploys: 318, uptime: 100, latency: 21, cost: 1430, updated: '2026-10-31', }, { id: 'auth', name: 'auth', owner: 'Mara Lindqvist', region: 'eu-west', status: 'Live', deploys: 176, uptime: 99.99, latency: 66, cost: 540, updated: '2026-10-28', }, { id: 'ingest', name: 'ingest', owner: 'Lena Fischer', region: 'us-east', status: 'Live', deploys: 264, uptime: 99.93, latency: 131, cost: 2120, updated: '2026-10-30', },]Installation
npm install @kerf-js/veneerTry it
Change the controls: the preview and its code update together. The address keeps what you set, so you can link to it.
Live preview
Controls
Count
Codeupdates as you change the controls
import { Chip } from '@kerf-js/veneer'<Chip pressed count={4}>Failed</Chip>Usage
tsx
import { Chip } from '@kerf-js/veneer'<Chip>Failed</Chip>Icons
Use icon to put a small icon before a chip's label.
tsx
import { state } from '@kerf-js/core'import { Chip, Row } from '@kerf-js/veneer'export function Icons() { const mine = state(true) const starred = state(false) return ( <Row gap="tight"> <Chip icon="user" pressed={mine.value} onClick={() => (mine.value = !mine.value)} > Mine </Chip> <Chip icon="star" pressed={starred.value} onClick={() => (starred.value = !starred.value)} > Starred </Chip> </Row> )}Filtering a list
Each chip's pressed turns one filter on or off, and the list shows what matches.
tsx
import { state } from '@kerf-js/core'import { Chip, Row, Stack, Text } from '@kerf-js/veneer'export function FilterList() { const on = state(['Failed', 'Building']) const flip = (name: string) => (on.value = on.value.includes(name) ? on.value.filter((n) => n !== name) : [...on.value, name]) const shown = () => { const matches = PROJECTS.filter( (p) => !on.value.length || on.value.includes(p.status), ) return matches.slice(0, 4) } return ( <Stack gap="group"> <Row gap="tight"> {['Live', 'Building', 'Failed'].map((name) => ( <Chip key={name} pressed={on.value.includes(name)} onClick={() => flip(name)} > {name} </Chip> ))} </Row> <Stack gap="tight"> {shown().map((p) => ( <Row key={p.id} justify="between" > <Text>{p.name}</Text> <Text subtle>{p.status}</Text> </Row> ))} </Stack> </Stack> )}type Status = | 'Live' | 'Building' | 'Failed' | 'Paused'const PROJECTS: { id: string name: string owner: string region: string status: Status deploys: number uptime: number latency: number cost: number updated: string}[] = [ { id: 'storefront', name: 'storefront', owner: 'Priya Raman', region: 'eu-west', status: 'Live', deploys: 412, uptime: 99.98, latency: 84, cost: 1240, updated: '2026-10-31', }, { id: 'checkout', name: 'checkout', owner: 'Tomás Oliveira', region: 'eu-west', status: 'Live', deploys: 288, uptime: 99.95, latency: 112, cost: 980, updated: '2026-10-31', }, { id: 'search-api', name: 'search-api', owner: 'Lena Fischer', region: 'us-east', status: 'Building', deploys: 196, uptime: 99.91, latency: 146, cost: 1610, updated: '2026-10-30', }, { id: 'billing', name: 'billing', owner: 'Mara Lindqvist', region: 'us-east', status: 'Live', deploys: 154, uptime: 99.99, latency: 73, cost: 720, updated: '2026-10-29', }, { id: 'mobile-bff', name: 'mobile-bff', owner: 'Sam Okafor', region: 'ap-south', status: 'Failed', deploys: 233, uptime: 99.42, latency: 208, cost: 860, updated: '2026-10-29', }, { id: 'notifier', name: 'notifier', owner: 'Jun Tanaka', region: 'eu-north', status: 'Live', deploys: 97, uptime: 99.97, latency: 58, cost: 310, updated: '2026-10-27', }, { id: 'reports', name: 'reports', owner: 'Noor Haddad', region: 'us-west', status: 'Paused', deploys: 41, uptime: 99.8, latency: 321, cost: 190, updated: '2026-10-22', }, { id: 'edge-cache', name: 'edge-cache', owner: 'Jun Tanaka', region: 'global', status: 'Live', deploys: 318, uptime: 100, latency: 21, cost: 1430, updated: '2026-10-31', }, { id: 'auth', name: 'auth', owner: 'Mara Lindqvist', region: 'eu-west', status: 'Live', deploys: 176, uptime: 99.99, latency: 66, cost: 540, updated: '2026-10-28', }, { id: 'ingest', name: 'ingest', owner: 'Lena Fischer', region: 'us-east', status: 'Live', deploys: 264, uptime: 99.93, latency: 131, cost: 2120, updated: '2026-10-30', },]When to use
Accessibility
- Enter
- Toggles
- Space
- Toggles
Theming
Restyle it app-wide from veneer() in your kerf config. Every key takes colour, spacing, size, radius, shadow, type and motion; its own keys are listed. A state nests inside: hover: { bg: 'ink' }. Text on a fill you set is contrast-checked at build. How theming works
| Key | Own keys | States |
|---|---|---|
parts.chip | radius | hover, focus, press, active, open, disabled |
ts
veneer({ colors: { brand: '#e8590c', ink: '#1b1b1f' }, parts: { chip: { radius: 6, hover: { border: 'brand' } }, },})API Reference
| Prop | Type | Default | Description |
|---|---|---|---|
pressed | boolean | false | Whether the filter is on. |
count | unknown | — | A number after the label, usually the matches. |
icon | IconRef | — | An icon name from @kerf-js/veneer-icons, or a component. |
children | unknown | — | What it contains. |
Parts
Every element carries a stable name, so you can style it from your own CSS: [data-scope="chip"] [data-part="check"]
checkcount