Custom grid UI for multi-type reference array selection in Sanity Studio
Yes, you can absolutely customize the "Add" button selection for an array of references in Sanity Studio! There are a couple of approaches depending on how much customization you want.
The Decorated Component Approach
The cleanest way to add a visual grid selector is to create a custom input component that wraps the default array input. This is called a "decorated" component because you're adding functionality around the existing input rather than replacing it entirely.
Here's how you'd implement it:
First, create a custom input component that renders the default array input plus your custom buttons:
// ./schema/MyArrayInput.tsx
import {Grid, Stack, Button} from '@sanity/ui'
import {AddIcon} from '@sanity/icons'
import {ArrayOfObjectsInputProps} from 'sanity'
export function MyArrayInput(props: ArrayOfObjectsInputProps) {
return (
<Stack space={3}>
{props.renderDefault(props)}
<Grid columns={3} gap={2}>
<Button
icon={AddIcon}
text="Type A"
mode="ghost"
onClick={() => {/* add Type A reference */}}
/>
<Button
icon={AddIcon}
text="Type B"
mode="ghost"
onClick={() => {/* add Type B reference */}}
/>
{/* Add more buttons as needed */}
</Grid>
</Stack>
)
}Then configure your array field to use this component:
defineField({
name: 'myReferences',
type: 'array',
of: [
{type: 'reference', to: [{type: 'typeA'}, {type: 'typeB'}, {type: 'typeC'}]}
],
components: {
input: MyArrayInput
}
})Making the Buttons Functional
To actually add references when buttons are clicked, you'll need to query for the documents and patch the array. Here's a complete example based on the official guide:
import {useClient, insert, setIfMissing, Reference} from 'sanity'
import {randomKey} from '@sanity/util/content'
import {useCallback} from 'react'
export function MyArrayInput(props: ArrayOfObjectsInputProps) {
const {onChange} = props
const client = useClient({apiVersion: '2023-04-01'})
const handleAddType = useCallback(async (typeName: string) => {
// Query for documents of this type
const query = `*[_type == $typeName && !(_id in path("drafts.**"))]._id`
const docIds: string[] = await client.fetch(query, {typeName})
// Create reference objects
const references: Reference[] = docIds.map(id => ({
_key: randomKey(12),
_type: 'reference',
_ref: id
}))
// Patch the document
const patches = references.map(ref => insert([ref], 'after', [-1]))
onChange([setIfMissing([]), ...patches])
}, [onChange, client])
return (
<Stack space={3}>
{props.renderDefault(props)}
<Grid columns={3} gap={2}>
<Button onClick={() => handleAddType('typeA')}>Type A</Button>
<Button onClick={() => handleAddType('typeB')}>Type B</Button>
</Grid>
</Stack>
)
}Adding Visual Representations
To display icons or images for each type in your grid, you can customize the buttons further:
<Grid columns={3} gap={2}>
{TYPES.map(type => (
<Button
key={type.value}
mode="ghost"
onClick={() => handleAddType(type.value)}
>
<Stack space={2} align="center">
{type.icon && <type.icon />}
<Text>{type.title}</Text>
</Stack>
</Button>
))}
</Grid>Alternative: Conditional Fields Workaround
As mentioned in this community answer, you could also use an object wrapper with conditional fields to achieve type selection, though this is more of a workaround and less elegant than the custom input approach.
Key Resources
- Form Components API documentation
- Custom Input Components guide
- Array input with selectable templates example
The decorated component approach gives you complete flexibility while preserving all the built-in functionality of the array input (drag-and-drop reordering, validation, etc.). You can style it however you like using Sanity UI components!
Show original thread3 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.