Visual Editing with Next.js App Router and Sanity Studio
Setup "Live by Default" fetches and interactive live preview with Presentation in Sanity Studio
Go to Visual Editing with Next.js App Router and Sanity StudioThis guide teaches how to add a custom input component to a field for Sanity Studio v3. It covers replacing a default input component with a custom React component, passing necessary properties into an input element, and patching real-time changes to the Content Lake from the input component.
This step-by-step guide will take you through the necessary steps of adding a custom input component to a field for Sanity Studio v3. You will be taken through 3 steps and learn the essential concepts you need to know in order to:
You will do this by adding a custom string input field that includes a simple character count.
Completing the lesson should take you about 30–40 minutes. That doesn’t mean you should feel bad if it takes you longer. We all learn at different paces.
Open a Sanity Studio v3 in your favorite code editor and get started!
This guide is made for people who are new to custom input components for Sanity Studio. All code examples are copy-pasteable and should work if you follow the instructions closely.
Running a Sanity Studio project locally
This lesson assumes that you know how to get started with Sanity Studio and have basic knowledge about its project structure and schema files.
Basic HTML, CSS, and JavaScript
You could probably make it through this section without any preexisting knowledge of these languages, but it will be a lot easier if you understand the basic concepts. Mozilla Developer Network (MDN) has a great introduction to the basics of HTML, CSS, and JavaScript.
How to build with React
To build custom components for Sanity Studio, you will need to know basic React and JSX syntax. The lesson will provide you with all the code you need and explain how it works, so you can use it to sharpen your React skills too. You can check out the official docs (in beta) and Next.js’ introduction to React for an excellent introduction to React.
Sanity Studio v3 comes with a customization framework PIs that lets you override different parts of the content form. In this lesson, you will focus on the input component part of a field. This is the element in a field that makes it possible for someone to change content. Customizing an input field will not affect a field’s other properties, such as its title, description, validation, presence, and change indicators. If you want to customize any of these, you need to use the Field Component API.
The details of how to override a default input component can vary depending on the field’s schema type (for example, string, number, image, or object). It’s also possible to implement custom components that replace input fields globally with plugins. This lesson covers how to do it for a specific string schema field.
The following will assume that you have installed the blog project template in the CLI. You can, however, follow these instructions with any type: "string"
field.
Start by making a new folder in your Sanity Studio v3 project called components
and inside it, a new file called MyCustomStringInput.jsx
(if you use TypeScript, make sure the file ending is .tsx
):
~/sanity-studio-v3 . ├── README.md ├── components │ └── MyCustomStringInput.jsx ├── node_modules ├── package-lock.json ├── package.json ├── sanity.cli.js ├── sanity.config.js ├── schemas └── static
Open the MyCustomStringInput.jsx
file and add the following code to it:
// /components/MyCustomStringInput.jsx
export const MyCustomStringInput = (props) => {
return <div>Value: {props.value}</div>
}
This component will only render the value of a field as a text string. You will replace this code with the actual input later in this lesson. For now, you want to make sure that you import this component into your schema and field configuration and that it is rendering in the correct place.
Import your new component to the schema file, and add it to the field property components.input
:
// /schemas/post.js
import { MyCustomStringInput } from '../components/MyCustomStringInput'
export default {
name: 'post',
title: 'Post',
type: 'document',
fields: [
{
name: 'title',
title: 'Title',
type: 'string',
components: {
input: MyCustomStringInput
}
},
// ... the rest of the fields
]
}
That is it! Make sure both files are saved and run the local development server with npm run
. Open a post document to check if your component renders. If it’s new, you will only see “Value:”
, and if had a Post document there from before, you will also see the contents of the title field being rendered.
You have now learned how to add a custom React component to your Sanity Studio v3 project and how to import it into the schema configuration.
In theory, you can use any input component from the HTML specification supported by a modern browser or even import a React component published on npm. As long as the component has a way to call a function whenever it has changes made to it. In this lesson, you will import the TextInput
component from Sanity UI. Sanity UI is the component library that is already used to make everything you see in the Studio. Using its components makes it easier to keep the editor experience accessible and consistent.
Start by importing the TextInput component from Sanity UI in your MyCustomStringInput.jsx
file, and replace it with your current code. You don’t have to install @sanity/ui
as a dependency since it comes with the Studio out of the box.
// /components/MyCustomStringInput.jsx
import {TextInput} from '@sanity/ui'
export const MyCustomStringInput = (props) => {
return <TextInput />
}
Save the file and make sure your development server is running. You should now see a string input field in your Post document. As it is now, it will do nothing when you type into it, and it will not show any content that you might have in the title field from before.
Sanity Studio v3’s form has a lot of accessibility and behavior built into it. When we add custom components, it is important to ensure we are also accommodating this behavior. Fortunately, a custom input component receives properties (props
) that help you with that. At least for basic inputs. In the code under, you can see what these properties are:
// /components/MyCustomStringInput.jsx
import {TextInput} from '@sanity/ui'
export const MyCustomStringInput = (props) => {
const {
elementProps: {
id,
onBlur,
onFocus,
placeholder,
readOnly,
ref,
// value
},
onChange,
schemaType,
validation,
value = ''
} = props
return <TextInput />
}
It can be useful to know what some of these different properties are responsible for:
ref
is used by the Studio to automatically set focus to an input if it’s first in a form or a view. This is great for editor experience, and accessibilityonBlur
brings in the Studio behavior when the input loses focus.onChange
is where you will add a handler function that is responsible for patching data to the content lake when the value of the input changesonFocus
brings in the Studio behavior for when an input gets focusreadOnly
is the behavior for when the value is set to be read-onlyschemaType
contains different information that comes from the current schema type that this input is connected to. You will use this to add any placeholder that comes in the input configuration.validation
is a list of any validation issues related to this particular value. We will not use it in this lesson.value
is the current content of the input that’s stored in the Content Lake. This will update in real-time whenever someone makes changes to a particular field in a document. Notice that there is a value
inside of elementProps
that will always be converted to a string.
Difference between elementProps.value
and props.value
props.value
will always be either undefined
or a value of the data type mandated by the schema.
elementProps.value
will always be a string:
"true"
or "false"
if the input type is a boolean (or empty string if the input has no value)You can now pass and spread the elementProps
to the input
component. Many of these will “just work” on any input component from Sanity UI and on most HTML input elements.
// /components/MyCustomStringInput.jsx
import {TextInput} from '@sanity/ui'
export const MyCustomStringInput = (props) => {
const { elementProps } = props
return (
<TextInput {...elementProps} />
)
}
What you have now is actually the same string input component that Sanity Studio v3 comes with out of the box. To make it a bit more interesting and… custom, you can add a reactive character counter below it.
First, import the Stack
and Text
components from Sanity UI. These will make sure that your component looks good. The Stack
component wraps the whole input component and makes it possible to get consistent spacing (space={2}
) between the elements and the Text
component provides the correct padding and font for the custom text.
// /components/MyCustomStringInput.jsx
import {Stack, Text, TextInput} from '@sanity/ui'
export const MyCustomStringInput = (props) => {
const { elementProps, value = '' } = props
return (
<Stack space={2}>
<TextInput {...elementProps} />
<Text>Characters: {value.length}</Text>
</Stack>
)
}
Excellent work so far! You have now implemented the necessary properties for making sure the input component behaves in an accessible way, gets the current value, and even added a bit of customization with the character count text below it.
You have almost everything you need in place for this custom input component to work. The only thing you miss is the mechanism for actually updating content in real-time to the Content Lake.
Sanity Studio and Content Lake are built to provide a real-time editing experience that allows multiple people (and machines) to simultaneously make changes to the same document. In order for this to work, you need to add a change handler to the input component that emits (read: sends) a patch with its new value to the Content Lake.
Fortunately, the Studio handles all the hard parts of that, and all you have to do is to call a function called onChange
that is passed through a custom input component’s props
.
The onChange
function takes the return value of a patch
function as its argument. In this lesson, you will use the set()
and the unset()
patch functions that generate the patch object that’s sent to the Content Lake.
Start by importing the set
and unset
functions from sanity
, make a new function inside the component called handleChange
, and add it to the TextInput
's onChange
prop:
// /components/MyCustomStringInput.jsx
import {useCallback} from 'react'
import {Stack, Text, TextInput} from '@sanity/ui'
import {set, unset} from 'sanity'
export const MyCustomStringInput = (props) => {
const {elementProps, onChange, value = ''} = props
const handleChange = (event) => {/* more code to come */}
return (
<Stack space={2}>
<TextInput
{...elementProps}
onChange={handleChange}
value={value}
/>
<Text>Characters: {value.length}</Text>
</Stack>
)
}
Now you can add the final piece of logic, where you first extract the input element’s current value from the event
that is passed to the handleChange
function, and use it to update the Content Lake:
// /components/MyCustomStringInput.jsx
import {Stack, Text, TextInput} from '@sanity/ui'
import {set, unset} from 'sanity'
export const MyCustomStringInput = (props) => {
const {elementProps, onChange, value = ''} = props
const handleChange = (event) => {
const nextValue = event.currentTarget.value
onChange(nextValue ? set(nextValue) : unset())
}
return (
<Stack space={2}>
<TextInput
{...elementProps}
onChange={handleChange}
value={value}
/>
<Text>Characters: {value.length}</Text>
</Stack>
)
}
Note that inside the onChange
function, you have a ternary if statement that will remove the field (unset()) from the document if the input component is empty.
You should also consider the performance of this custom input component. Especially since you are using React and since you are adding it to a real-time environment where the state can change a lot. Without going too far into the technicalities, what you want to avoid is that React unnecessarily re-renders your component and unnecessarily re-declares variables and functions.
React comes with a hook called useCallback
that lets you wrap a function inside of a component and declare which props should make the component redeclare your handler function. In this case, that’s only changes that are passed into the onChange
prop.
Start by importing the useCallback
hook from React and wrap the anonymous function in it, adding onChange
to its dependency array:
// /components/MyCustomStringInput.jsx
import {useCallback} from 'react'
import {Stack, Text, TextInput} from '@sanity/ui'
import {set, unset} from 'sanity'
export const MyCustomStringInput = (props) => {
const {elementProps, onChange, value = ''} = props
const handleChange = useCallback((event) => {
const nextValue = event.currentTarget.value
onChange(nextValue ? set(nextValue) : unset())
}, [onChange])
return (
<Stack space={2}>
<TextInput
{...elementProps}
onChange={handleChange}
value={value}
/>
<Text>Characters: {value.length}</Text>
</Stack>
)
}
And that’s it!
Congratulations! You have now built your own custom input component for a string field. You have done so by importing a React component to the components.input
property on a field configuration. You have used Sanity UI to build a custom user interface that will be consistent with how the Studio looks and works. Your string input component has additional functionality for counting how many characters it has. You have added a mechanism for updating content in real-time to the Content Lake and made sure that the input component is performant.
Were you able to follow this lesson? Anything that was unclear or you wished we had explained better? Or was it awesome, and you want to let us know?
Head over to our GitHub Discussions and share your thoughts in the thread belonging to this lesson. Thanks for your time and attention!
Sanity Composable Content Cloud is the headless CMS that gives you (and your team) a content backend to drive websites and applications with modern tooling. It offers a real-time editing environment for content creators that’s easy to configure but designed to be customized with JavaScript and React when needed. With the hosted document store, you query content freely and easily integrate with any framework or data source to distribute and enrich content.
Sanity scales from weekend projects to enterprise needs and is used by companies like Puma, AT&T, Burger King, Tata, and Figma.
Setup "Live by Default" fetches and interactive live preview with Presentation in Sanity Studio
Go to Visual Editing with Next.js App Router and Sanity StudioA complete guide to setting up your blog using Astro and Sanity
Go to Build your blog with Astro and SanityA thorough intro to using GROQ-projections in a webhook contest
Go to GROQ-Powered Webhooks – Intro to ProjectionsA thorough intro to using GROQ-filters in a webhook-context
Go to GROQ-Powered Webhooks – Intro to Filters