Sidebar controls in depth
Create a custom control
Create your own sideEditProp UI when the built-in controls are not enough for the editing experience you want.
In this how-to, you'll learn how to create a custom sidebar control with SideEditPropType.Custom.
This is the escape hatch for cases where the built-in controls are not enough.
Docs reference: Sidebar controls
When a custom control makes sense
Reach for a custom control when editors need a specialized UI such as:
- a compound spacing picker
- a map-based location picker
- a preset picker with thumbnails
- a custom token selector tied to your design system
Before using a custom control, it is worth checking whether Select, Autocomplete, Image, or Relationship already covers the use case.
The 2 required pieces
A custom side edit prop needs:
- a
type: types.SideEditPropType.Custom - a
componentfunction that renders the editing UI
React Bricks passes the component:
value: the current value of the proponChange: the function you call when your custom control should save a new valueisValid: whether the current value passes validation, useful for your custom UI to show an error state
Example
This example creates a tiny preset picker for section spacing:
import clsx from 'clsx'
import { Text, types } from 'react-bricks/rsc'
type SpacingPreset = 'compact' | 'comfortable' | 'spacious'
interface SectionBlockProps {
title: types.TextValue
spacing: SpacingPreset
}
const spacingClasses: Record<SpacingPreset, string> = {
compact: 'py-6',
comfortable: 'py-12',
spacious: 'py-20',
}
const SpacingControl = ({
value,
onChange,
isValid,
}: {
value?: SpacingPreset
onChange: (value: SpacingPreset) => void
isValid: boolean
}) => {
const options: SpacingPreset[] = ['compact', 'comfortable', 'spacious']
return (
<div className={clsx('grid grid-cols-3 gap-2', !isValid && 'rounded border border-red-500 p-2')}>
{options.map((option) => (
<button
key={option}
type="button"
onClick={() => onChange(option)}
className={clsx(
'rounded border px-2 py-2 text-xs',
value === option ? 'border-sky-500 bg-sky-50' : 'border-slate-200'
)}
>
{option}
</button>
))}
</div>
)
}
const SectionBlock: types.Brick<SectionBlockProps> = ({ title, spacing }) => {
return (
<section className={spacingClasses[spacing]}>
<Text
propName="title"
value={title}
placeholder="Section title..."
renderBlock={({ children }) => <h2>{children}</h2>}
/>
</section>
)
}
SectionBlock.schema = {
name: 'section-block',
label: 'Section Block',
getDefaultProps: () => ({
title: 'A section with spacing presets',
spacing: 'comfortable',
}),
sideEditProps: [
{
name: 'spacing',
label: 'Spacing',
type: types.SideEditPropType.Custom,
component: SpacingControl,
validate: (value) =>
['compact', 'comfortable', 'spacious'].includes(value) ||
'Choose a spacing preset',
},
],
}Keep the value simple
Even when the UI is custom, the stored value should usually stay simple.
Good examples:
- a string preset
- a small object
- a number
That makes the brick easier to render, validate, and migrate later.
Use custom controls sparingly
Custom controls are powerful, but they also create a larger maintenance surface.
Try to keep them:
- focused on one editing task
- visually clear for editors
- consistent with your design system
If the control becomes too large or too application-specific, it may be a sign that the value belongs somewhere else in the app workflow.
Summary
Custom controls are the advanced extension point for sideEditProps.
Use them when you need a better editing experience than the built-in controls can offer, but still want the value to flow through normal brick props.