Skip to content

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 tags
  • suggestions (array): Tag suggestions
  • validate (function): Tag validation function
  • maxTags (number): Maximum number of tags
  • placeholder (string): Input placeholder
  • disabled (boolean): Disable component
  • loading (boolean): Loading state
  • allowDuplicates (boolean): Allow duplicate tags
  • caseSensitive (boolean): Case-sensitive matching
  • addOnBlur (boolean): Add tag on blur
  • addOnPaste (boolean): Add tags on paste
  • delimiter (string): Tag delimiter
  • inputClass (string): Input CSS class
  • tagClass (string): Tag CSS class

Events

  • update:modelValue: Tags updated
  • change: Tags changed
  • add: Tag added
  • remove: Tag removed
  • invalid: Invalid tag attempted
  • exceed: Max tags exceeded
  • focus: Input focused
  • blur: Input blurred

Slots

  • tag: Custom tag render
  • suggestion: Custom suggestion render
  • input: Custom input render
  • prefix: Input prefix content
  • suffix: Input suffix content

Usage Examples

  1. Basic Tag Input:
<template>
<UIDynamicTags
v-model="tags"
placeholder="Enter tags..."
/>
</template>
<script setup>
const tags = ref([])
</script>
  1. 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 = 5
const 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>
  1. 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

  1. User Experience:

    • Clear feedback
    • Intuitive input
    • Keyboard support
    • Visual validation
  2. Performance:

    • Debounced input
    • Efficient updates
    • Memory management
    • Optimized rendering
  3. Accessibility:

    • ARIA labels
    • Focus management
    • Screen reader support
    • Keyboard navigation
  4. Mobile:

    • Touch targets
    • Responsive layout
    • Clear feedback
    • Easy deletion

Common Use Cases

  1. 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>
  1. 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>
  1. 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