Components

TagSelector

Tag-based multi-select compound component with built-in search and CRUD

Overview

TagSelector is a compound component for tag-based multi-selection. It has built-in search, create, edit, and delete functionality.

Features

  • Tag search and filtering
  • Inline tag creation
  • Tag edit/delete
  • Keyboard navigation
  • Close on outside click
  • Customizable trigger
  • TypeScript support

Anatomy

import { TagSelector } from '@illog/ui'
 
<TagSelector.Root
  tags={tags}
  selectedTags={selectedTags}
  onAddTag={handleAddTag}
  onRemoveTag={handleRemoveTag}
  onCreateTag={handleCreateTag}
  onDeleteTag={handleDeleteTag}
  onUpdateTag={handleUpdateTag}
>
  <TagSelector.Trigger>
    {/* Custom trigger button */}
  </TagSelector.Trigger>
 
  <TagSelector.Content>
    <TagSelector.Search />
    <TagSelector.List />
  </TagSelector.Content>
</TagSelector.Root>

API Reference

Root

PropTypeRequiredDescription
tagsTag[]YesAll available tags
selectedTagsTag[]YesCurrently selected tags
onAddTag(tag: Tag) => voidYesTag select handler
onRemoveTag(tagId: string) => voidYesTag deselect handler
onCreateTag(name: string, color: string) => voidYesTag create handler
onDeleteTag(tagId: string) => voidYesTag delete handler
onUpdateTag(tagId: string, updates: Tag) => voidYesTag update handler
classNamestringNoCSS class
maxTagLengthnumberNoMax tag name length (default: 10)
emptyMessagestringNoMessage when no search results
createTagLabelstringNoCreate button label (default: "Create Tag")
manageTagsLabelstringNoManage button label (default: "Manage Tags")
childrenReactNodeYesMust include Trigger and Content

Trigger

PropTypeRequiredDescription
asChildbooleanNoMerge props with child element (default: true)
childrenReactNodeYesTrigger content

Data Attributes: data-state: "open" | "closed"

Content

PropTypeRequiredDescription
childrenReactNodeYesUsually Search and List
PropTypeRequiredDescription
placeholderstringNoPlaceholder text (default: "Search...")

List

No props. Renders the filtered tag list.

Examples

Custom Trigger

<TagSelector.Root {...handlers}>
  <TagSelector.Trigger asChild>
    <button className="my-custom-button">
      {selectedTags.length > 0
        ? `${selectedTags.length} tags`
        : 'Select tags'}
    </button>
  </TagSelector.Trigger>
  {/* ... */}
</TagSelector.Root>

With Custom Labels

<TagSelector.Root
  tags={tags}
  selectedTags={selectedTags}
  createTagLabel="Create New"
  manageTagsLabel="Edit Tags"
  maxTagLength={15}
  emptyMessage="No tags found."
  {...handlers}
>
  {/* ... */}
</TagSelector.Root>

Accessibility

Keyboard Interactions

KeyAction
EnterToggle tag selection / create new tag (while searching)
BackspaceRemove last selected tag (when search is empty)
EscapeClose dropdown
Space (trigger)Toggle dropdown
Enter (trigger)Toggle dropdown

ARIA

  • Trigger: aria-expanded, aria-haspopup="listbox"
  • Search: role="searchbox", aria-label
  • List: Proper tag button semantics

Focus Management

  • Focus returns to trigger when dropdown closes
  • Auto-focuses search input when dropdown opens
  • Full keyboard navigation without mouse

Custom API

useTagSelectorContext

import { useTagSelectorContext } from '@illog/ui'
 
function CustomTagList() {
  const {
    tags, selectedTags, searchTerm, isOpen,
    filteredTags, isTagSelected,
    selectTag, removeTag, createTag, deleteTag, updateTag,
    setSearchTerm, setIsOpen,
    maxTagLength, emptyMessage, createTagLabel, manageTagsLabel,
  } = useTagSelectorContext()
 
  return (
    <div>
      {filteredTags.map((tag) => (
        <button key={tag.id} onClick={() => selectTag(tag)}>
          {isTagSelected(tag.id) ? '✓ ' : ''}{tag.name}
        </button>
      ))}
    </div>
  )
}
PropertyTypeDescription
tagsTag[]All tags
selectedTagsTag[]Selected tags
searchTermstringCurrent search term
isOpenbooleanDropdown open state
filteredTagsTag[]Tags matching search
isTagSelected(tagId: string) => booleanCheck if tag is selected
selectTag(tag: Tag) => voidAdd tag to selection
removeTag(tagId: string) => voidRemove tag from selection
createTag(name: string, color: string) => voidCreate new tag
deleteTag(tagId: string) => voidDelete tag
updateTag(tagId: string, updates: Tag) => voidUpdate tag