Sigurd Heggemsnes
Developer who loves making life easier for people
Tired of pressing "New item" all the time? Render an array of references as checkboxes
import { Box, Card, Checkbox, Flex, Stack, Text } from '@sanity/ui';
import { useCallback, useEffect, useMemo, useState } from 'react';
import type { ArrayOfObjectsInputProps } from 'sanity';
import { set, useClient } from 'sanity';
interface ReferenceItem {
_id: string;
title?: string;
}
/**
* Renders a list of checkbox items based on documents that match the schema's "reference" fields.
*/
export function ReferenceCheckbox(props: ArrayOfObjectsInputProps) {
const [referenceItems, setReferenceItems] = useState<ReferenceItem[]>([]);
const client = useClient({ apiVersion: '2025-01-12' });
// Flatten the preview.title fields that exist in the schema.
const previewProjections = useMemo(() => {
// Each "of" type could have "to" array. We collect any "preview.select.title".
const titles = props.schemaType.of.flatMap((type) =>
'to' in type ? type.to?.flatMap((to) => to.preview?.select?.title ?? []) : []
);
// If multiple preview fields exist, we can use `coalesce` with "||" or a fallback approach.
return titles.length ? titles.join(',') : 'title';
}, [props.schemaType]);
// Collect all possible _type names from "to" fields in schema.
const referenceTypes = useMemo(
() =>
props.schemaType.of
.flatMap((type) => ('to' in type ? type.to?.map((to) => to.name) : []))
.filter(Boolean),
[props.schemaType]
);
// Fetch reference items on mount or if schema changes
useEffect(() => {
const fetchData = async () => {
if (!referenceTypes.length) return;
const query = `*[_type in $types] {
_id,
"title": coalesce(${previewProjections}, title)
}`;
const items: ReferenceItem[] = await client.fetch(query, { types: referenceTypes });
setReferenceItems(items);
};
fetchData();
}, [client, referenceTypes, previewProjections]);
/**
* Toggles a reference in the array (adds if missing, removes if present).
*/
const handleToggle = useCallback(
(itemId: string) => {
const currentValue = props.value ?? [];
const exists = currentValue.some((val) => '_ref' in val && val._ref === itemId);
const newValue = exists
? currentValue.filter((val) => '_ref' in val && val._ref !== itemId)
: [
...currentValue,
{
_key: itemId.slice(0, 10),
_type: 'reference',
_ref: itemId,
},
];
props.onChange(set(newValue));
},
[props]
);
return (
<Stack space={2}>
{referenceItems.map((item) => {
const isChecked =
props.value?.some((val) => '_ref' in val && val._ref === item._id) || false;
return (
<Card key={item._id} padding={2}>
<Flex align="center">
<Checkbox id={item._id} checked={isChecked} onChange={() => handleToggle(item._id)} />
<Box flex={1} paddingLeft={3}>
<Text>
<label htmlFor={item._id}>{item.title}</label>
</Text>
</Box>
</Flex>
</Card>
);
})}
</Stack>
);
}
Some content types can be limited in number but highly reusable across your site. Using the array picker for reference works, but this custom component renders the references as checkboxes to make it faster to check references.
This component use the specified "preview" path on a document to figure out what title to display. It currently does not take into account any filters, but this should be easy to implement.
Obviously this works best with when you are referencing a document type with limited entries.
Developer who loves making life easier for people
Open editor and preview pane in split view all the time
Go to Open editor and preview pane in split view all the timeGet parents parent in reference filter
Go to Get parents parent in reference filterOrder by last name
Go to Use GROQ to order by last name in where name is stored as full name