How to create a custom input component in Sanity for updating fields based on dropdown selection
I can see you're looking to automatically sync a status dropdown with corresponding boolean checkboxes. While this seems straightforward, Sanity schemas don't support automatic bidirectional field synchronization out of the box. However, there are several practical approaches depending on your exact needs.
The Core Challenge
Sanity's schema system doesn't have built-in "computed fields" or automatic field synchronization. Each field stores its own independent value, and there's no native way to make one field automatically update another when it changes.
Recommended Approaches
Option 1: Use Conditional hidden Instead (Simplest)
Rather than trying to sync checkbox values with the status, consider whether you actually need the checkboxes at all. If the status dropdown already represents the state, you could use conditional fields to show/hide different content based on the status:
// management.js
{
name: 'hotelStatusType',
title: 'Hotel Status Type',
type: 'string', // Changed from 'tag' to 'string' for standard dropdown
options: {
list: [
{title: 'Extended', value: 'extended'},
{title: 'Shortlist', value: 'shortlist'},
{title: 'Omitted', value: 'omitted'},
]
}
}
// Then in other fields that depend on this status:
{
name: 'extendedDetails',
type: 'object',
hidden: ({document}) => document?.management?.hotelStatusType !== 'extended',
// ... fields specific to extended status
}This approach eliminates redundant data storage and keeps your single source of truth in the status field. The hidden callback receives the entire document context, allowing you to conditionally show/hide fields anywhere in your schema based on the status value.
Option 2: Visual Indicators Without Redundant Storage
If you need to display the boolean state visually but don't want to store duplicate data, you could make the displayOptions fields read-only and use conditional logic to show which ones are "active":
// displayOptions.js
fields: [
{
name: 'extended',
title: 'Extended',
type: 'boolean',
readOnly: true,
hidden: ({document}) => document?.management?.hotelStatusType !== 'extended',
initialValue: true, // Always true when shown
},
{
name: 'shortlist',
title: 'Shortlist',
type: 'boolean',
readOnly: true,
hidden: ({document}) => document?.management?.hotelStatusType !== 'shortlist',
initialValue: true,
},
{
name: 'omitted',
title: 'Omitted',
type: 'boolean',
readOnly: true,
hidden: ({document}) => document?.management?.hotelStatusType !== 'omitted',
initialValue: true,
},
]This displays checkboxes that reflect the status but prevents manual editing and doesn't actually store redundant boolean values.
Option 3: Document Actions with Manual Sync (User-Triggered)
If you want synchronization to happen in the Studio but don't need it to be completely automatic, you could create a custom document action that users trigger manually (like a "Sync Display Options" button):
// In your document actions resolver
import {useDocumentOperation} from 'sanity'
export function SyncDisplayOptionsAction(props) {
const {patch, publish} = useDocumentOperation(props.id, props.type)
return {
label: 'Sync Display Options',
onHandle: () => {
const status = props.draft?.management?.hotelStatusType ||
props.published?.management?.hotelStatusType
patch.execute([
{set: {
'displayOptions.extended': status === 'extended',
'displayOptions.shortlist': status === 'shortlist',
'displayOptions.omitted': status === 'omitted'
}}
])
}
}
}This gives editors explicit control over when synchronization happens.
Option 4: Validation to Ensure Consistency
Instead of automatic synchronization, you could use field validation to warn editors when the checkboxes don't match the status:
// In displayOptions.js
validation: Rule => Rule.custom((displayOptions, context) => {
const status = context.document?.management?.hotelStatusType
const matchingField = displayOptions?.[status]
if (status && !matchingField) {
return `Display options don't match status: ${status}`
}
return true
})Why Not Custom Input Components with Patch Interception?
You might see suggestions about custom input components that intercept PatchEvent operations to automatically update other fields. While PatchEvent is a legitimate API for custom components to update their own field values, using it to update other fields across the document has significant drawbacks:
- Not a documented pattern: The official documentation doesn't recommend cross-field patching from input components
- Fragile: May break with Studio updates or create race conditions
- User confusion: Changes happening "magically" in different parts of the form can be disorienting
- Validation complexity: Can create conflicts with validation logic and real-time collaboration
My Recommendation
Start with Option 1 (conditional hidden) if possible. Ask yourself: do you really need both the status field AND the boolean checkboxes stored as separate data? If the status dropdown fully represents the state, use that as your single source of truth and conditionally show/hide other fields based on it.
If you need visual indicators without data redundancy, use Option 2 (read-only conditional display).
If you genuinely need both fields to store values independently (perhaps for historical reasons or complex querying), use Option 3 (manual document action) to give editors explicit control over synchronization.
The key principle is to avoid storing the same information in multiple places unless absolutely necessary. Redundant data inevitably leads to inconsistencies. When you do need related fields, use conditional visibility, validation, or explicit user actions rather than trying to make the Studio automatically synchronize them behind the scenes.
Note on your schema: I changed the type: 'tag' to type: 'string' with a list option, which is the standard way to create dropdowns in Sanity. The 'tag' type isn't a built-in Sanity field type unless you have a custom plugin installed.
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.