UIDynamicTags
UIDynamicTags
A versatile tag management component that allows users to create, edit, and delete tags with support for validation, suggestions, and customization.
<template> <UIDynamicTags v-model="tags" :suggestions="tagSuggestions" :validate="validateTag" :maxTags="10" placeholder="Add tags" @change="handleTagsChange" > <template #tag="{ tag, index, remove }"> <UITag :key="index" :closable="true" class="mr-2 mb-2" @close="remove(index)" > {{ tag }} </UITag> </template> </UIDynamicTags></template>
<script setup lang="ts">const tags = ref<string[]>([])const tagSuggestions = [ 'JavaScript', 'TypeScript', 'Vue.js', 'React', 'Angular']
const validateTag = (tag: string) => { if (tag.length < 2) { return 'Tag must be at least 2 characters' } if (tag.length > 20) { return 'Tag must be less than 20 characters' } return true}
const handleTagsChange = (newTags: string[]) => { console.log('Tags updated:', newTags)}</script>Props
modelValue(array): Array of tagssuggestions(array): Tag suggestionsvalidate(function): Tag validation functionmaxTags(number): Maximum number of tagsplaceholder(string): Input placeholderdisabled(boolean): Disable componentloading(boolean): Loading stateallowDuplicates(boolean): Allow duplicate tagscaseSensitive(boolean): Case-sensitive matchingaddOnBlur(boolean): Add tag on bluraddOnPaste(boolean): Add tags on pastedelimiter(string): Tag delimiterinputClass(string): Input CSS classtagClass(string): Tag CSS class
Events
update:modelValue: Tags updatedchange: Tags changedadd: Tag addedremove: Tag removedinvalid: Invalid tag attemptedexceed: Max tags exceededfocus: Input focusedblur: Input blurred
Slots
tag: Custom tag rendersuggestion: Custom suggestion renderinput: Custom input renderprefix: Input prefix contentsuffix: Input suffix content
Usage Examples
- Basic Tag Input:
<template> <UIDynamicTags v-model="tags" placeholder="Enter tags..." /></template>
<script setup>const tags = ref([])</script>- Advanced Tag Management:
<template> <div class="space-y-4"> <div class="flex items-center justify-between"> <label class="text-sm font-medium"> Skills </label>
<span class="text-sm text-gray-500"> {{ tags.length }}/{{ maxTags }} tags </span> </div>
<UIDynamicTags v-model="tags" :suggestions="suggestions" :validate="validateTag" :maxTags="maxTags" :loading="loading" placeholder="Add skills" class="min-h-[100px]" > <template #tag="{ tag, index, remove }"> <UITag :key="index" :closable="true" class="mr-2 mb-2" @close="remove(index)" > <div class="flex items-center space-x-1"> <component :is="getSkillIcon(tag)" class="w-4 h-4" /> <span>{{ tag }}</span> </div> </UITag> </template>
<template #suggestion="{ suggestion, active, select }"> <div class="flex items-center p-2 cursor-pointer" :class="{ 'bg-primary-50': active }" @click="select" > <component :is="getSkillIcon(suggestion)" class="w-5 h-5 mr-2" /> <div> <div class="font-medium"> {{ suggestion }} </div> <div class="text-sm text-gray-500"> {{ getSkillDescription(suggestion) }} </div> </div> </div> </template> </UIDynamicTags>
<div v-if="error" class="text-sm text-error-500" > {{ error }} </div> </div></template>
<script setup>const maxTags = 5const tags = ref([])const suggestions = [ 'JavaScript', 'TypeScript', 'Vue.js', 'React', 'Node.js']const loading = ref(false)const error = ref('')
const validateTag = (tag: string) => { if (tag.length < 2) { return 'Tag must be at least 2 characters' } if (tags.value.includes(tag)) { return 'Tag already exists' } return true}
const getSkillIcon = (skill: string) => { // Return appropriate icon component based on skill}
const getSkillDescription = (skill: string) => { // Return skill description}</script>- Tag Categories:
<template> <div class="space-y-6"> <div v-for="category in categories" :key="category.id" class="space-y-2" > <label class="text-sm font-medium"> {{ category.label }} </label>
<UIDynamicTags v-model="categoryTags[category.id]" :suggestions="category.suggestions" :validate="tag => validateCategoryTag(tag, category)" :placeholder="`Add ${category.label.toLowerCase()}`" > <template #tag="{ tag, index, remove }"> <UITag :key="index" :closable="true" :variant="category.color" class="mr-2 mb-2" @close="remove(index)" > {{ tag }} </UITag> </template>
<template #suggestion="{ suggestion, active, select }"> <div class="flex items-center justify-between p-2 cursor-pointer" :class="{ 'bg-primary-50': active }" @click="select" > <span>{{ suggestion }}</span> <UIBadge v-if="isPopularTag(suggestion, category)" variant="success" size="sm" > Popular </UIBadge> </div> </template> </UIDynamicTags> </div> </div></template>
<script setup>const categories = [ { id: 'technologies', label: 'Technologies', color: 'primary', suggestions: ['Vue', 'React', 'Angular'] }, { id: 'features', label: 'Features', color: 'success', suggestions: ['Authentication', 'API', 'Dashboard'] }]
const categoryTags = reactive({ technologies: [], features: []})
const validateCategoryTag = (tag: string, category: any) => { const tags = categoryTags[category.id] if (tags.includes(tag)) { return `This ${category.label.toLowerCase()} already exists` } return true}
const isPopularTag = (tag: string, category: any) => { // Check if tag is popular in category}</script>Best Practices
-
User Experience:
- Clear feedback
- Intuitive input
- Keyboard support
- Visual validation
-
Performance:
- Debounced input
- Efficient updates
- Memory management
- Optimized rendering
-
Accessibility:
- ARIA labels
- Focus management
- Screen reader support
- Keyboard navigation
-
Mobile:
- Touch targets
- Responsive layout
- Clear feedback
- Easy deletion
Common Use Cases
- Product Tags:
<template> <div class="space-y-2"> <label class="text-sm font-medium"> Product Tags </label>
<UIDynamicTags v-model="productTags" :suggestions="popularTags" :validate="validateProductTag" placeholder="Add product tags" > <template #prefix> <TagIcon class="w-5 h-5 text-gray-400" /> </template> </UIDynamicTags> </div></template>- Email Recipients:
<template> <UIDynamicTags v-model="recipients" :validate="validateEmail" placeholder="Add recipients" delimiter="," class="w-full" > <template #tag="{ tag, remove }"> <UITag :closable="true" class="mr-2 mb-2" > <div class="flex items-center space-x-2"> <UIAvatar :src="getAvatarUrl(tag)" size="xs" :round="true" /> <span>{{ tag }}</span> </div> </UITag> </template> </UIDynamicTags></template>
<script setup>const validateEmail = (email: string) => { const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/ return emailRegex.test(email) || 'Invalid email address'}</script>- Search Filters:
<template> <div class="flex items-center space-x-4"> <UISearch v-model="search" placeholder="Search..." class="flex-1" />
<UIDynamicTags v-model="filters" :suggestions="filterSuggestions" placeholder="Add filters" class="flex-1" > <template #tag="{ tag, remove }"> <UITag :closable="true" :variant="getFilterVariant(tag)" class="mr-2" @close="remove" > <div class="flex items-center space-x-1"> <component :is="getFilterIcon(tag)" class="w-4 h-4" /> <span>{{ formatFilter(tag) }}</span> </div> </UITag> </template> </UIDynamicTags> </div></template>Component Composition
The UIDynamicTags component works well with:
- UITag for tag display
- UIInput for tag input
- UIButton for actions
- UITooltip for help text
- UIBadge for status/counts
- UIAvatar for user tags
- UIIcon for visual indicators
- UIPopover for suggestions