Change `tags` layout to be an autocomplete kind of deal.

4 replies
Last updated: Nov 29, 2025
Hey folks! 👋 I feel like I should be able to figure this out on my own, but I'm stuck and would be grateful for any help 🙏
I have a field to add tags to photos, nothing fancy:


{
      name: 'tags',
      title: 'Tags',
      type: 'array',
      description: 'Add tags that describe this photo.',
      of: [{type: 'string'}],
      options: {
        layout: 'tags',
        isHighlighted: true
      },
      validation: Rule => Rule.required().min(2).error('At least 2 tags are required.')
}
What I'm trying to do is to change
tags
layout to be an autocomplete kind of deal. When a person starts to type a tag, I want to check all existing tags that start with this input (this is the easy part) and display them as an autocomplete dropdown for this
tags
field (this is the hard part). On Enter, the tag selected from the dropdown is added to the array, and the user can start typing next tag with the same autocomplete dropdown experience.
Any ideas or pointers? Thank you so very much!
🙇‍♂️
AI Update

Great question! You'll want to create a custom input component that replaces the default array string input with an autocomplete experience. Here's how to approach this:

The Solution

You need to build a custom input component that uses the Sanity UI Autocomplete component and queries existing tags from your content. Here's the basic structure:

import {set, unset} from 'sanity'
import {Autocomplete, Card} from '@sanity/ui'
import {useCallback, useState, useEffect} from 'react'
import {useClient} from 'sanity'

function TagsAutocompleteInput(props) {
  const {onChange, value = [], schemaType} = props
  const [inputValue, setInputValue] = useState('')
  const [existingTags, setExistingTags] = useState([])
  
  const client = useClient({apiVersion: '2024-01-01'})
  
  // Fetch existing tags from your content
  useEffect(() => {
    client.fetch(`array::unique(*[defined(tags)].tags[])`)
      .then(setExistingTags)
  }, [client])
  
  // Filter tags based on input
  const suggestions = existingTags.filter(tag => 
    tag.toLowerCase().startsWith(inputValue.toLowerCase())
  )

  const handleSelect = useCallback((selectedTag) => {
    // Add the selected tag to the array
    const newValue = [...(value || []), selectedTag]
    onChange(set(newValue))
    setInputValue('') // Clear input for next tag
  }, [value, onChange])

  const handleRemoveTag = useCallback((index) => {
    const newValue = value.filter((_, i) => i !== index)
    onChange(newValue.length ? set(newValue) : unset())
  }, [value, onChange])

  return (
    <Card>
      {/* Display existing tags with remove buttons */}
      <div>
        {value?.map((tag, index) => (
          <span key={index}>
            {tag}
            <button onClick={() => handleRemoveTag(index)}>×</button>
          </span>
        ))}
      </div>
      
      {/* Autocomplete input */}
      <Autocomplete
        options={suggestions.map(tag => ({value: tag}))}
        value={inputValue}
        onChange={setInputValue}
        onSelect={handleSelect}
        placeholder="Type to search tags..."
      />
    </Card>
  )
}

Integrate it into your schema

Modify your field definition to use the custom component:

{
  name: 'tags',
  title: 'Tags',
  type: 'array',
  of: [{type: 'string'}],
  components: {
    input: TagsAutocompleteInput
  },
  validation: Rule => Rule.required().min(2).error('At least 2 tags are required.')
}

Fetching Existing Tags

The GROQ query array::unique(*[defined(tags)].tags[]) flattens all tag arrays across your documents and returns unique values - perfect for your autocomplete suggestions!

Key Points

  • Custom input components receive value, onChange, and other props to integrate with Studio's form system
  • Use set() and unset() from the sanity package to patch changes back to the Content Lake
  • The Sanity UI Autocomplete component handles the dropdown UI and keyboard navigation for you
  • You can wrap your component with FormField from sanity if you want to preserve the label, description, and validation UI around your custom input

Check out the custom input component guide for more detailed examples and best practices. The guide walks through all the props available and how to properly handle patch events!

Show original thread
4 replies

Sanity – Build the way you think, not the way your CMS thinks

Sanity is the developer-first content operating system that gives you complete control. Schema-as-code, GROQ queries, and real-time APIs mean no more workarounds or waiting for deployments. Free to start, scale as you grow.

Was this answer helpful?