Chip
chipuiCompact, removable tag — typically used inside `tags-input` or as a filter pill. Seven variants (`default`, `filled`, `outlined`, `elevated`, `success`, `warning`, `destructive`), three sizes, an optional close button with enter/leave micro-animation, and a ChipGroup selection container with single/multiple/mandatory/max modes.
Installation
$pnpm dlx shadcn-svelte@latest add https://uipkge.dev/r/svelte/chip.json$npx shadcn-svelte@latest add https://uipkge.dev/r/svelte/chip.json$yarn dlx shadcn-svelte@latest add https://uipkge.dev/r/svelte/chip.json$bunx shadcn-svelte@latest add https://uipkge.dev/r/svelte/chip.jsonManifest:
https://uipkge.dev/r/svelte/chip.jsonInstalls to:src/lib/components/ui/chip/Examples
Loading interactive previews…
Loading interactive previews…
Props
| Name | Type / Values | Default | Required |
|---|---|---|---|
variant | 'default''filled''outlined''outline''elevated''success''warning''destructive' | default | optional |
size | 'sm''default''lg' | default | optional |
wrap | 'true' | — | optional |
closableShow a dismiss button; `onclose` fires after the leave animation. | boolean | false | optional |
children | Snippet | — | optional |
oncloseFires when the chip requests removal (after the leave animation). | () => void | — | optional |
npm dependencies
Used by
Files installed (4)
src/lib/components/ui/chip/Chip.svelte2.6 kB
<script lang="ts" module> import type { Snippet } from 'svelte' import type { HTMLAttributes } from 'svelte/elements' import type { ChipVariants } from './chip.variants' export interface ChipProps extends HTMLAttributes<HTMLSpanElement> { variant?: ChipVariants['variant'] size?: ChipVariants['size'] /** Allow the label to wrap onto multiple lines instead of clipping. */ wrap?: boolean /** Show a dismiss button; `onclose` fires after the leave animation. */ closable?: boolean children?: Snippet /** Fires when the chip requests removal (after the leave animation). */ onclose?: () => void } </script> <script lang="ts"> import { X } from '@lucide/svelte' import { cn } from '$lib/utils' import { chipVariants } from './chip.variants' let { class: className, variant = 'default', size = 'default', wrap, closable = false, children, onclose, ...restProps }: ChipProps = $props() let leaving = $state(false) function onClose() { if (leaving) return leaving = true const reduce = typeof window !== 'undefined' && window.matchMedia('(prefers-reduced-motion: reduce)').matches window.setTimeout(() => { onclose?.() }, reduce ? 0 : 160) } </script> <span data-uipkge data-slot="chip" data-variant={variant} data-leaving={leaving || undefined} class={cn(chipVariants({ variant, size, wrap }), 'chip-enter', leaving && 'chip-leave', className)} {...restProps} > {@render children?.()} {#if closable} <button type="button" aria-label="Remove item" class="focus-visible:ring-ring hover:bg-foreground/10 ml-1 inline-flex min-h-6 min-w-6 items-center justify-center rounded-full transition-transform duration-150 focus-visible:ring-1 focus-visible:outline-none active:scale-90" onclick={(e) => { e.stopPropagation() onClose() }} > <X class="size-3" aria-hidden="true" /> </button> {/if} </span> <style> @keyframes chip-enter { from { opacity: 0; transform: scale(0.88); } to { opacity: 1; transform: scale(1); } } @keyframes chip-leave { to { opacity: 0; transform: scale(0.88); } } :global([data-slot='chip']).chip-enter { animation: chip-enter 180ms cubic-bezier(0.22, 1.2, 0.36, 1) both; } :global([data-slot='chip']).chip-leave { animation: chip-leave 160ms ease-in both; pointer-events: none; } @media (prefers-reduced-motion: reduce) { :global([data-slot='chip']).chip-enter, :global([data-slot='chip']).chip-leave { animation: none !important; } } </style>src/lib/components/ui/chip/ChipGroup.svelte2.1 kB
<script lang="ts" module> import type { Snippet } from 'svelte' import type { HTMLAttributes } from 'svelte/elements' export interface ChipGroupSlotArgs { selected: string[] multiple: boolean filter: boolean isSelected: (value: string) => boolean toggle: (value: string) => void } /** React parity alias — same shape as `ChipGroupSlotArgs`. */ export type ChipGroupRenderProps = ChipGroupSlotArgs export interface ChipGroupProps extends Omit<HTMLAttributes<HTMLDivElement>, 'children'> { selected?: string[] multiple?: boolean filter?: boolean column?: boolean mandatory?: boolean max?: number disabled?: boolean /** Fires when the selection changes. */ onSelectedChange?: (value: string[]) => void children?: Snippet<[ChipGroupSlotArgs]> } </script> <script lang="ts"> import { cn } from '$lib/utils' let { class: className, selected = $bindable([]), multiple = false, filter = false, column = false, mandatory = false, max, disabled = false, onSelectedChange, children, ...restProps }: ChipGroupProps = $props() function isSelected(value: string) { return selected.includes(value) } function toggle(value: string) { if (disabled) return let newSelected: string[] if (multiple) { if (isSelected(value)) { newSelected = selected.filter((v) => v !== value) } else { if (max && selected.length >= max) { newSelected = [...selected.slice(1), value] } else { newSelected = [...selected, value] } } } else { if (isSelected(value) && !mandatory) { newSelected = [] } else { newSelected = [value] } } selected = newSelected onSelectedChange?.(newSelected) } </script> <div role="group" data-uipkge data-slot="chip-group" data-chip-group="true" data-multiple={multiple || undefined} data-filter={filter || undefined} class={cn('flex flex-wrap gap-2', column && 'flex-col', className)} {...restProps} > {@render children?.({ selected, multiple, filter, isSelected, toggle })} </div>src/lib/components/ui/chip/chip.variants.ts2.1 kB
import type { VariantProps } from 'class-variance-authority' import { cva } from 'class-variance-authority' /** * Variant definitions live in their own file (rather than the package * `index.ts`) so consuming Vue SFCs can import without creating a circular * dependency through the index. See card.variants.ts for the canonical * example + the SSR symptom that motivated the split. */ export const chipVariants = cva( // text-overflow/ellipsis cannot apply to a flex container's anonymous text, // so truncation is opt-in via a child span (`<Chip><span class="truncate"> // …</span></Chip>`); min-w-0 lets that span shrink inside the flex root. 'inline-flex items-center justify-center gap-1 rounded-full text-xs font-medium w-fit max-w-full whitespace-nowrap shrink-0 [&>span]:min-w-0 transition-colors duration-200 overflow-hidden focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2', { variants: { variant: { default: 'bg-muted text-muted-foreground hover:bg-muted/80', filled: 'bg-primary text-primary-foreground hover:bg-primary/90', outlined: 'border border-current bg-transparent hover:bg-accent', outline: 'border border-current bg-transparent hover:bg-accent', elevated: 'bg-primary/10 text-primary shadow-sm hover:bg-primary/20', success: 'bg-success/10 text-success dark:text-success hover:bg-success/20', warning: 'bg-warning/10 text-warning dark:text-warning hover:bg-warning/20', destructive: 'bg-destructive/10 text-destructive dark:text-destructive hover:bg-destructive/20', }, size: { sm: 'h-6 px-2 text-xs', default: 'h-7 px-2.5 text-xs', lg: 'h-8 px-3 text-sm', }, // Allow multi-line labels. rounded-lg + extra leading keeps a wrapped // chip readable instead of a cramped two-line stadium pill. wrap: { true: 'whitespace-normal h-auto rounded-lg py-1 leading-snug', }, }, defaultVariants: { variant: 'default', size: 'default', }, }, ) export type ChipVariants = VariantProps<typeof chipVariants>src/lib/components/ui/chip/index.ts0.4 kB
export { default as Chip, type ChipProps } from './Chip.svelte' export { default as ChipGroup, type ChipGroupProps, type ChipGroupSlotArgs, type ChipGroupRenderProps, } from './ChipGroup.svelte' // Re-export variant API from the sibling file (kept separate to avoid the // Chip.svelte <-> index.ts circular import that broke dev SSR for Card). export { chipVariants, type ChipVariants } from './chip.variants'
Raw manifest:https://uipkge.dev/r/svelte/chip.json