UIPackage

Chip

Vue data-display
Edit on GitHub

Compact, removable tag — typically used inside `tags-input` or as a filter pill. Seven variants (`default`, `filled`, `outlined`, `elevated`, `success`, `warning`, `destructive`), three sizes, and an optional close button.

Also available for React ->

Installation

$ npx shadcn-vue@latest add https://uipkge.dev/r/vue/chip.json

Or with the named registry: npx shadcn-vue@latest add @uipkge/chip

Examples

Props

Name Type / Values Default Required
variant
'default''filled''outlined''elevated''success''warning''destructive'
optional
size
'sm''default''lg'
optional
class HTMLAttributes['class'] optional
closable boolean optional

Dependencies

Files (4)

  • app/components/ui/chip/Chip.vue 1 kB
    <script setup lang="ts">
    import type { HTMLAttributes } from 'vue'
    import { X } from 'lucide-vue-next'
    import { cn } from '@/lib/utils'
    import { chipVariants } from './chip.variants'
    
    // Inlined unions: SFC compiler can't extract runtime props from
    // `ChipVariants['variant'] | ['size']`.
    const props = defineProps<{
      variant?: 'default' | 'filled' | 'outlined' | 'elevated' | 'success' | 'warning' | 'destructive'
      size?: 'sm' | 'default' | 'lg'
      class?: HTMLAttributes['class']
      closable?: boolean
    }>()
    
    const emit = defineEmits<{
      close: []
    }>()
    </script>
    
    <template>
      <span :class="cn(chipVariants({ variant, size }), props.class)">
        <slot />
        <button
          v-if="closable"
          type="button"
          aria-label="Remove item"
          class="focus-visible:ring-ring hover:bg-foreground/10 ml-0.5 inline-flex min-h-6 min-w-6 items-center justify-center rounded-full focus-visible:ring-1 focus-visible:outline-none"
          @click.stop="emit('close')"
        >
          <X class="size-3" aria-hidden="true" />
        </button>
      </span>
    </template>
  • app/components/ui/chip/ChipGroup.vue 1.5 kB
    <script setup lang="ts">
    import type { HTMLAttributes } from 'vue'
    
    const props = withDefaults(
      defineProps<{
        class?: HTMLAttributes['class']
        selected?: string[]
        multiple?: boolean
        filter?: boolean
        column?: boolean
        mandatory?: boolean
        max?: number
        disabled?: boolean
      }>(),
      {
        selected: () => [],
        multiple: false,
        filter: false,
        column: false,
        mandatory: false,
        disabled: false,
      },
    )
    
    const emit = defineEmits<{
      'update:selected': [value: string[]]
    }>()
    
    function isSelected(value: string) {
      return props.selected.includes(value)
    }
    
    function toggle(value: string) {
      if (props.disabled) return
    
      let newSelected: string[]
    
      if (props.multiple) {
        if (isSelected(value)) {
          newSelected = props.selected.filter((v) => v !== value)
        } else {
          if (props.max && props.selected.length >= props.max) {
            newSelected = [...props.selected.slice(1), value]
          } else {
            newSelected = [...props.selected, value]
          }
        }
      } else {
        if (isSelected(value) && !props.mandatory) {
          newSelected = []
        } else {
          newSelected = [value]
        }
      }
    
      emit('update:selected', newSelected)
    }
    </script>
    
    <template>
      <div
        :class="['flex flex-wrap gap-2', column ? 'flex-col' : '', filter ? 'flex-wrap' : '', props.class]"
        :data-chip-group="true"
        :data-multiple="multiple || undefined"
        :data-filter="filter || undefined"
      >
        <slot :selected="selected" :multiple="multiple" :filter="filter" :is-selected="isSelected" :toggle="toggle" />
      </div>
    </template>
  • app/components/ui/chip/chip.variants.ts 1.6 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(
      'inline-flex items-center justify-center gap-1 rounded-full text-xs font-medium w-fit whitespace-nowrap shrink-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',
            elevated: 'bg-primary/10 text-primary shadow-sm hover:bg-primary/20',
            success: 'bg-[var(--success)]/10 text-[var(--success)] dark:text-[var(--success)] hover:bg-[var(--success)]/20',
            warning: 'bg-[var(--warning)]/10 text-[var(--warning)] dark:text-[var(--warning)] hover:bg-[var(--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',
          },
        },
        defaultVariants: {
          variant: 'default',
          size: 'default',
        },
      },
    )
    
    export type ChipVariants = VariantProps<typeof chipVariants>
  • app/components/ui/chip/index.ts 0.3 kB
    export { default as Chip } from './Chip.vue'
    export { default as ChipGroup } from './ChipGroup.vue'
    
    // Re-export variant API from the sibling file (kept separate to avoid the
    // Component.vue <-> index.ts circular import that broke dev SSR for Card).
    export { chipVariants, type ChipVariants } from './chip.variants'

Raw manifest: https://uipkge.dev/r/vue/chip.json