Developing Custom Input Components in Sanity
Sanity allows replacing standard field UI with custom React component. Used for complex input types: maps, tags with autocomplete, color palettes, non-standard editors.
Basic Custom Input
// components/studio/ColorPickerInput.tsx
import { useCallback } from 'react'
import { set } from 'sanity'
import type { StringInputProps } from 'sanity'
const PRESET_COLORS = ['#FF5733', '#33FF57', '#3357FF', '#FF33A8', '#FFAA00', '#00AAFF']
export function ColorPickerInput(props: StringInputProps) {
const { value, onChange, readOnly } = props
const handleSelect = useCallback(
(color: string) => {
onChange(color ? set(color) : undefined)
},
[onChange]
)
return (
<div>
<div style={{ display: 'flex', gap: 8, flexWrap: 'wrap', marginBottom: 8 }}>
{PRESET_COLORS.map(color => (
<div
key={color}
onClick={() => !readOnly && handleSelect(color)}
style={{
width: 28,
height: 28,
borderRadius: '50%',
background: color,
cursor: readOnly ? 'default' : 'pointer',
border: value === color ? '3px solid var(--card-focus-ring-color)' : 'none',
}}
/>
))}
</div>
<input
type="text"
value={value || ''}
onChange={e => handleSelect(e.target.value)}
placeholder="#000000 or rgb(0,0,0)"
disabled={readOnly}
style={{ width: '100%' }}
/>
</div>
)
}
Tag Input with Autocomplete
// components/studio/TagInput.tsx
import { useState, useCallback } from 'react'
import { insert, remove } from 'sanity'
import type { ArrayInputProps } from 'sanity'
const SUGGESTED_TAGS = ['TypeScript', 'React', 'Next.js', 'DevOps', 'Python']
export function TagInput(props: ArrayInputProps) {
const { value = [], onChange, readOnly } = props
const [input, setInput] = useState('')
const addTag = useCallback(
(tag: string) => {
const trimmed = tag.trim()
if (!trimmed || (value as string[]).includes(trimmed)) return
onChange(insert([trimmed], 'after', [-1]))
setInput('')
},
[value, onChange]
)
return (
<div>
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 6, marginBottom: 8 }}>
{(value as string[]).map((tag, i) => (
<span
key={i}
style={{
padding: '2px 10px',
background: '#f0f0f0',
borderRadius: 12,
fontSize: 13,
}}
>
{tag}
{!readOnly && (
<button
onClick={() => onChange(remove([i]))}
style={{ background: 'none', border: 'none', cursor: 'pointer', marginLeft: 4 }}
>
×
</button>
)}
</span>
))}
</div>
<input
value={input}
onChange={e => setInput(e.target.value)}
onKeyDown={e => { if (e.key === 'Enter') { e.preventDefault(); addTag(input) } }}
placeholder="Add tag..."
disabled={readOnly}
/>
</div>
)
}
Timeline
Development of 3–5 custom input components — 2–3 days.







