templates/shader/src/config-panel/config-panel.md
A collapsible, reusable configuration panel component for building interactive controls for shader templates. The config panel provides a consistent UI for adjusting shader parameters in real-time.
The config panel system consists of:
All controls are styled to match the tldraw UI and automatically handle state persistence.
The main container component that wraps all configuration controls.
children: React nodes containing the control componentsonReset: Callback function to reset configuration to defaultsonReset callbackimport { ConfigPanel } from '../config-panel/ConfigPanel'
import { resetMyConfig } from './config'
function MyConfigPanel() {
return <ConfigPanel onReset={resetMyConfig}></ConfigPanel>
}
A slider control for numeric values with automatic range mapping.
prop: Property name (passed to onChange)label: Display label for the slidermin: Minimum valuemax: Maximum valuevalue: Current valuetype: Either 'float' or 'int'onChange: Callback (prop: string, value: number) => voidtype='int'TldrawUiSlider component<ConfigPanelSlider
prop="splatRadius"
label="Splat Radius"
min={0.01}
max={1}
value={config.splatRadius}
type="float"
onChange={handleChange}
/>
A checkbox control for boolean values.
prop: Property name (passed to onChange)label: Display label for the checkboxvalue: Current boolean valueonChange: Callback (prop: string, value: boolean) => void<ConfigPanelBooleanControl
prop="paused"
label="Paused"
value={config.paused}
onChange={handleChange}
/>
The config panel works with tldraw's reactive atom system for automatic state management and persistence.
import { atom, react } from 'tldraw'
// 1. Define default configuration
const DEFAULT_CONFIG = {
quality: 0.5,
paused: false,
// ... other properties
}
// 2. Load from localStorage with fallback
const STORAGE_KEY = 'my-shader-config'
let initialValue = DEFAULT_CONFIG
try {
const value = localStorage.getItem(STORAGE_KEY)
if (value) initialValue = JSON.parse(value)
} catch {
// Use defaults if parse fails
}
// 3. Create reactive atom
export const myConfig = atom('my-config', initialValue)
// 4. Reset function
export function resetMyConfig() {
myConfig.set(DEFAULT_CONFIG)
}
// 5. Auto-save to localStorage
react('save to local storage', () => {
localStorage.setItem(STORAGE_KEY, JSON.stringify(myConfig.get()))
})
import { useValue } from 'tldraw'
import { myConfig } from './config'
export function MyConfigPanel() {
// Subscribe to reactive config changes
const config = useValue('config', () => myConfig.get(), [])
// Update config properties
const handleChange = useCallback((prop: string, value: number | boolean) => {
myConfig.update((prev) => ({ ...prev, [prop]: value }))
}, [])
return (
<ConfigPanel onReset={resetMyConfig}>
<ConfigPanelSlider
prop="quality"
label="Quality"
min={0}
max={1}
value={config.quality}
type="float"
onChange={handleChange}
/>
<ConfigPanelBooleanControl
prop="paused"
label="Paused"
value={config.paused}
onChange={handleChange}
/>
</ConfigPanel>
)
}
The config panel uses CSS classes that can be customized:
.shader-config-panel: Main panel container.shader-config-panel--expanded: Applied when expanded.shader-config-panel--collapsed: Applied when collapsed.shader-config-panel__header: Header with reset and toggle buttons.shader-config-panel__content: Content area (only visible when expanded).shader-config-panel__control: Base class for individual controls.shader-config-panel__control--slider: Modifier for slider controls.shader-config-panel__control--boolean: Modifier for boolean controls.shader-config-panel__boolean-input: Checkbox input element.shader-config-panel__label: Label text for controlsThe config panel is typically placed in the tldraw component tree and accessed by a shader manager:
function MyShaderRenderer({ editor }: { editor: Editor }) {
const canvas = useRef<HTMLCanvasElement>(null)
useEffect(() => {
if (!canvas.current) return
// Initialize shader with reactive config
const manager = new MyShaderManager(canvas.current, editor, myConfig)
return () => manager.dispose()
}, [editor])
return <canvas ref={canvas} className="shader-app__canvas" />
}
export default function ShaderExample() {
return (
<div className="tldraw__editor">
<Tldraw>
<MyShaderRenderer />
<MyConfigPanel />
</Tldraw>
</div>
)
}
See templates/shader/src/fluid/ for a full example implementing:
config.ts: Reactive atom with localStorage persistenceFluidConfigPanel.tsx: Complete configuration UI with 20+ controlsFluidManager.ts: Shader manager that reads from the config atomThis demonstrates the full pattern for creating a configurable shader effect with a professional UI.