Skip to content
kerfjsGitHub

Chip

A small on or off filter, often with a count.

SourcePackage @kerf-js/veneerTry it

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/veneer

Try 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
Count4
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

Use it for

  • Filtering a list by several values at once

Reach for something else

  • Tag, when it only labels something
  • Segmented, when exactly one is on

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

KeyOwn keysStates
parts.chipradiushover, focus, press, active, open, disabled
ts
veneer({  colors: { brand: '#e8590c', ink: '#1b1b1f' },  parts: {    chip: { radius: 6, hover: { border: 'brand' } },  },})

API Reference

PropTypeDefaultDescription
pressedbooleanfalseWhether the filter is on.
countunknown—A number after the label, usually the matches.
iconIconRef—An icon name from @kerf-js/veneer-icons, or a component.
childrenunknown—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

RelatedToggleTagSegmented