Mitchell Christ
Building SanityPress 🖤
Want to add some preset buttons/chips below your text input field? Look no further!
'use client'
import { useCallback, type FormEvent } from 'react'
import { Badge, Card, Flex, Stack, Text, TextInput } from '@sanity/ui'
import type { StringInputProps, StringSchemaType } from 'sanity'
export type Preset =
| string
| {
label: string
value: string
}
export function getPreset(
preset: Preset,
property: 'label' | 'value' = 'value',
) {
return typeof preset === 'string' ? preset : preset[property]
}
export default function TextInputWithPresets({
elementProps,
prefix,
suffix,
presets,
}: {
prefix?: string
suffix?: string
presets?: Preset[]
} & StringInputProps<StringSchemaType>) {
const handleChange = useCallback(
(value: string) => {
elementProps.onChange({
currentTarget: { value },
} as FormEvent<HTMLInputElement>)
},
[elementProps.onChange],
)
return (
<Stack space={2}>
<Flex align="center" gap={1}>
{prefix && (
<Text size={1} muted>
{prefix}
</Text>
)}
<Card flex={1}>
<TextInput
{...elementProps}
onChange={(e) => handleChange(e.currentTarget.value)}
/>
</Card>
{suffix && (
<Text size={1} muted>
{suffix}
</Text>
)}
</Flex>
{presets && (
<Flex gap={1} paddingLeft={prefix ? prefix.length : undefined}>
{presets?.map((preset) => {
const presetValue = getPreset(preset)
const label = getPreset(preset, 'label')
return (
<Badge
style={{ cursor: 'pointer' }}
padding={2}
tone={
presetValue === elementProps.value ? 'primary' : 'default'
}
onClick={() => handleChange(presetValue)}
key={presetValue}
>
{label}
</Badge>
)
})}
</Flex>
)}
</Stack>
)
}
import TextInputWithPresets, {
getPreset,
type Preset,
} from '@/sanity/ui/TextInputWithPresets'
const presets: Preset[] = [
{ label: 'Tablet and below', value: '(width < 48rem)' },
{ label: 'Mobile only', value: '(width < 24rem)' },
{ label: 'Dark mode', value: '(prefers-color-scheme: dark)' },
]
export default defineType({
name: 'img',
title: 'Image',
type: 'object',
fields: [
defineField({
name: 'image',
type: 'image',
}),
defineField({
name: 'media',
title: 'Media query',
type: 'string',
placeholder: `e.g. ${presets.map((p) => getPreset(p)).join(', ')}`,
initialValue: getPreset(presets[0]),
components: {
input: (props) => (
<TextInputWithPresets
prefix="@media"
presets={presets}
{...props}
/>
),
},
}),
],
})😒 Want more to your basic text input field?
🥂 Want to add some presets as one-click buttons just below to save a couple seconds, but still have the ability to type custom values?
✨ This single file snippet (and easy implementation) adds some additional UX improvements.
👀 Here are some examples:
***
This custom input component is included out-of-the-box in SanityPress, a fully customizable Next.js + Sanity.io + Tailwind 4 starter template. Go check that out! 🖤
Building SanityPress 🖤
Use modules to build your pages? Want to grab a link to a specific module? This schema is just for you!
Go to Adding Jump Links to Page ModulesContent 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