
Grab your gear: The official Sanity swag store
Read Grab your gear: The official Sanity swag storeYes, 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 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
}
})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>
)
}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>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.
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!
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.
Content operations
Content backend


The only platform powering content operations
By Industry


Tecovas strengthens their customer connections
Build and Share

Grab your gear: The official Sanity swag store
Read Grab your gear: The official Sanity swag store