Keyboard shortcut: Ctrl + K
Get started

Chip

A small piece of information: a status, a tag, a filter that stays chosen. It shares its variant and tone table verbatim with VButton, and adds the two states a button does not have.

Usage

vue
Design system
<script setup lang="ts">
import { VChip } from 'vectis-ui'
</script>

<template>
  <VChip>Design system</VChip>
</template>

Examples

Variants and tones

variant offers three ways of painting the chip, soft, solid and outline, and tone five meanings.

vue
neutral softneutral solidneutral outline
accent softaccent solidaccent outline
success softsuccess solidsuccess outline
warning softwarning solidwarning outline
danger softdanger soliddanger outline
<script setup lang="ts">
import { VChip } from 'vectis-ui'

const variants = ['soft', 'solid', 'outline'] as const
const tones = ['neutral', 'accent', 'success', 'warning', 'danger'] as const
</script>

<template>
  <div v-for="tone in tones" :key="tone" class="row">
    <VChip v-for="variant in variants" :key="variant" :variant="variant" :tone="tone">
      {{ tone }} {{ variant }}
    </VChip>
  </div>
</template>

<style scoped>
.row {
  display: flex;
  flex-wrap: wrap;
  gap: var(--vectis-space-2);
}
</style>

Shapes

shape chooses the silhouette: chip takes the corner radius of an interactive control, pill rounds the ends completely. The chip corners read --vectis-radius-chip, which points at --vectis-radius-interactive from :root: set it to round the chips apart from the other controls. Set on a narrower selector than :root, an override of --vectis-radius-interactive does not reach the chips, so give that selector both tokens.

vue
chip softchip solidchip outline
pill softpill solidpill outline
<script setup lang="ts">
import { VChip } from 'vectis-ui'

const variants = ['soft', 'solid', 'outline'] as const
const shapes = ['chip', 'pill'] as const
</script>

<template>
  <div v-for="shape in shapes" :key="shape" class="row">
    <VChip
      v-for="variant in variants"
      :key="variant"
      :shape="shape"
      :variant="variant"
      tone="accent"
    >
      {{ shape }} {{ variant }}
    </VChip>
  </div>
</template>

<style scoped>
.row {
  display: flex;
  flex-wrap: wrap;
  gap: var(--vectis-space-2);
}
</style>

Sizes

size sets the height to 24 or 32 pixels, and compact takes 4px off it.

vue
Extra smallSmall
Extra smallSmall
<script setup lang="ts">
import { VChip } from 'vectis-ui'
</script>

<template>
  <div class="row">
    <VChip size="xs">Extra small</VChip>
    <VChip size="sm">Small</VChip>
  </div>

  <div class="row">
    <VChip size="xs" compact>Extra small</VChip>
    <VChip size="sm" compact>Small</VChip>
  </div>
</template>

<style scoped>
.row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--vectis-space-2);
}
</style>

Custom colours

color replaces the tone, every shade the chip needs being derived from it. It takes any CSS colour.

vue
softsolidoutline
#0f766emediumvioletredoklch(0.55 0.15 150)
<script setup lang="ts">
import { ref } from 'vue'
import { VChip } from 'vectis-ui'

const selected = ref(true)
</script>

<template>
  <div class="row">
    <VChip color="#7c3aed" variant="soft">soft</VChip>
    <VChip color="#7c3aed" variant="solid">solid</VChip>
    <VChip color="#7c3aed" variant="outline">outline</VChip>
    <VChip v-model:selected="selected" color="#7c3aed" selectable check>selectable</VChip>
  </div>

  <div class="row">
    <VChip color="#0f766e">#0f766e</VChip>
    <VChip color="mediumvioletred">mediumvioletred</VChip>
    <VChip color="oklch(0.55 0.15 150)">oklch(0.55 0.15 150)</VChip>
  </div>
</template>

<style scoped>
.row {
  display: flex;
  flex-wrap: wrap;
  gap: var(--vectis-space-2);
}
</style>

With icons

iconStart and iconEnd place an icon on either side of the label, and the #start and #end slots replace them. iconFilled draws the two icons filled. A chip with no label becomes square and has to be given a name.

vue
Start iconEnd iconBoth Operational
<script setup lang="ts">
import { VChip } from 'vectis-ui'
import { arrow_right_alt as arrowRightAlt, notifications, schedule } from 'vectis-ui/icons'
</script>

<template>
  <VChip :icon-start="schedule" tone="accent">Start icon</VChip>
  <VChip :icon-end="arrowRightAlt" tone="accent">End icon</VChip>
  <VChip :icon-start="schedule" :icon-end="arrowRightAlt" tone="accent">Both</VChip>

  <!-- No label at all: the chip becomes square, and the button it already is takes the name. -->
  <VChip :icon-start="notifications" tone="danger" clickable aria-label="Notifications" />

  <VChip tone="success">
    <template #start><span class="dot" /></template>
    Operational
  </VChip>
</template>

<style scoped>
/* The #start slot takes anything, not only an icon. `currentcolor` is the chip's own text
   colour, so the dot follows the tone with nothing to declare twice. */
.dot {
  inline-size: 0.5rem;
  aspect-ratio: 1;
  border-radius: 50%;
  background: currentcolor;
}
</style>

clickable renders the chip as a button and href as a link. Given neither, it is plain text.

vue
Static, no hover vuejs.org
<script setup lang="ts">
import { ref } from 'vue'
import { VChip } from 'vectis-ui'
import { arrow_right_alt as arrowRightAlt } from 'vectis-ui/icons'

const count = ref(0)
</script>

<template>
  <VChip>Static, no hover</VChip>
  <VChip clickable tone="accent" @click="count += 1">Clicked {{ count }} times</VChip>
  <VChip :icon-end="arrowRightAlt" href="https://vuejs.org" target="_blank" rel="noreferrer">
    vuejs.org
  </VChip>
</template>

Selection

selectable turns the chip into a toggle bound to v-model:selected. check adds a tick before the label, in place of the start icon, and checkIcon changes its glyph.

vue
<script setup lang="ts">
import { ref } from 'vue'
import { VChip } from 'vectis-ui'
import { check_circle as checkCircle } from 'vectis-ui/icons'

const frameworks = ref([
  { label: 'Vue', on: true },
  { label: 'React', on: false },
  { label: 'Svelte', on: false },
])

const filters = ref([
  { label: 'Open', on: true },
  { label: 'Assigned to me', on: true },
  { label: 'Archived', on: false },
])

const labels = ref([
  { label: 'Bug', on: true },
  { label: 'Feature', on: false },
  { label: 'Docs', on: false },
])
</script>

<template>
  <div class="row" role="group" aria-label="Frameworks">
    <VChip
      v-for="framework in frameworks"
      :key="framework.label"
      v-model:selected="framework.on"
      selectable
      tone="accent"
    >
      {{ framework.label }}
    </VChip>
  </div>

  <div class="row" role="group" aria-label="Filters">
    <VChip
      v-for="filter in filters"
      :key="filter.label"
      v-model:selected="filter.on"
      selectable
      check
      tone="accent"
    >
      {{ filter.label }}
    </VChip>
  </div>

  <div class="row" role="group" aria-label="Labels">
    <VChip
      v-for="item in labels"
      :key="item.label"
      v-model:selected="item.on"
      selectable
      check
      :check-icon="checkCircle"
      tone="accent"
    >
      {{ item.label }}
    </VChip>
  </div>
</template>

<style scoped>
.row {
  display: flex;
  flex-wrap: wrap;
  gap: var(--vectis-space-2);
}
</style>

Dismissible

dismissible adds a second button that emits dismiss, taking the chip out of the list being yours to decide. dismissIcon and dismissLabel change its glyph and its words. In a row of chips, give each cross a dismissLabel that names what it removes, or every one is announced with the same word.

vue
VueTypeScriptCSSVite
<script setup lang="ts">
import { ref } from 'vue'
import { VButton, VChip } from 'vectis-ui'

const ALL = ['Vue', 'TypeScript', 'CSS', 'Vite']

/* The chip only asks to be removed. Taking it out of the list is this component's
   decision, which is what lets the same event archive, undo or confirm instead. */
const tags = ref([...ALL])

function remove(tag: string) {
  tags.value = tags.value.filter((current) => current !== tag)
}
</script>

<template>
  <div class="row">
    <VChip v-for="tag in tags" :key="tag" tone="accent" dismissible @dismiss="remove(tag)">
      {{ tag }}
    </VChip>
    <VButton
      v-if="tags.length < ALL.length"
      variant="ghost"
      tone="neutral"
      size="xs"
      @click="tags = [...ALL]"
    >
      Reset
    </VButton>
  </div>
</template>

<style scoped>
.row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--vectis-space-2);
}
</style>

States

disabled greys the chip out through the colour tokens. A disabled link has its address dropped, which leaves it neither focusable nor followable.

vue
softsolidoutline
Inert linkDismissible
<script setup lang="ts">
import { VChip } from 'vectis-ui'

const variants = ['soft', 'solid', 'outline'] as const
</script>

<template>
  <div class="row">
    <VChip v-for="variant in variants" :key="variant" :variant="variant" tone="accent" disabled>
      {{ variant }}
    </VChip>
  </div>

  <div class="row">
    <VChip clickable disabled>Clickable</VChip>
    <VChip href="https://vuejs.org" disabled>Inert link</VChip>
    <VChip selectable check :selected="true" tone="accent" disabled>Selected</VChip>
    <VChip dismissible disabled>Dismissible</VChip>
  </div>
</template>

<style scoped>
.row {
  display: flex;
  flex-wrap: wrap;
  gap: var(--vectis-space-2);
}
</style>

API

Props

PropTypeDefault
variantChipVariant'soft' | 'solid' | 'outline''soft'
How strongly the chip is painted: a tinted background, the full colour, or a border alone.
toneChipTone'neutral' | 'accent' | 'danger' | 'success' | 'warning''neutral'
What the chip means, expressed as a colour. A chip may report a state where a button may not, which is why it offers five rather than three.
colorstringnone
A colour of your own, as hex, a CSS name or oklch(), which replaces the tone. Every shade it needs is derived from that one colour, so it follows both themes with nothing to rebuild. Only the contrast of the text on a fully coloured chip is yours to check.
shapeChipShape'chip' | 'pill''chip'
The silhouette: softly rounded corners, or a full pill.
sizeChipSize'xs' | 'sm''xs'
The height of the chip.
compactbooleanfalse
Takes 4px off the height, leaving the padding, the text and the icons as they are.
clickablebooleanfalse
Makes the chip a button that reacts to clicks, without holding a state.
hrefstringnone
Where the chip leads, which makes it a link.
selectablebooleanfalse
Makes the chip something that stays chosen. It takes precedence over href and clickable.
checkbooleanfalse
Shows a tick before the label while the chip is selected. It replaces whatever start icon was given, so the two are never shown together.
checkIconIconSourcecheck
The icon of that tick, a built-in check mark by default. iconFilled does not reach it.
iconStartIconSourcenone
An icon before the label. The #start slot replaces it.
iconEndIconSourcenone
An icon after the label. The #end slot replaces it.
iconFilledbooleanfalse
Renders iconStart and iconEnd in their filled form, the font's FILL axis. It has no effect on the slots, nor on the tick or the removal cross.
dismissiblebooleanfalse
Adds a button that asks for the chip to be removed. It only emits that request: taking the chip away is your decision.
dismissIconIconSourceclose
The icon of that removal button.
dismissLabelstringnone
What the removal button does, in words. It falls back to the design system dictionary.
disabledbooleanfalse
Makes the chip unusable, greyed out through the colour tokens.
v-model:selectedbooleanfalse
Whether the chip is selected, which is also what makes it selectable at all: binding it turns the chip into a toggle button and takes precedence over href and clickable.

Events

EventType
dismiss[]
The removal button was pressed. The chip is still on screen: removing it is yours to do.

Slots

SlotType
default{}
The label. It may be left out entirely, which gives a chip made of icons alone.
start{}
Content before the label, which takes the place of iconStart.
end{}
Content after the label, which takes the place of iconEnd.

Types

The types the tables above name, written as the library declares them. The ones carrying export can be imported from vectis-ui to type your own code; the others are the shape of what a slot hands out.

export interface BuiltinIcon {
  name: string
  paths: readonly [string] | readonly [string, string]
}
export type IconRender =
  | { path: string; viewBox?: string }
  | { component: Component; props?: Record<string, unknown> }
  | { src: string }
  | { text: string; class?: string }
  | { class: string }
export type IconSource = string | BuiltinIcon | IconRender