React · TypeScript · Zero deps

Wire your
parameters

Modular controls for React. Knobs, faders, and toggles wired to your UI values. Link parameters together. Morph between scenes. Export as code.

Turn the knobs. See the change.

Every control is wired to the simulated app. Twist a knob, drag a fader, switch a scene. Everything updates live.

PATCH
SCENE MORPH 0%
TYPOGRAPHY
SIZE
WEIGHT
TRACK
HEIGHT
SPACING
PAD
GAP
RADIUS
SURFACE
SHADOW
ACCENT
10 params · 3 modules

Not just another slider panel.

PATCH rethinks parameter controls from the ground up.

Rotary Knobs

Hardware-inspired knobs with arc indicators. Drag to adjust, use arrow keys, or double-click to type a value. Feels like turning a real dial.

Scene Morphing

Save parameter states as scenes. Drag the morph slider to crossfade between them. Every parameter interpolates smoothly.

Parameter Linking

Wire parameters together with expressions. When shadow depth increases, blur follows. When scale grows, opacity adapts. Reactive by default.

Modules

Group parameters into collapsible modules with solo and mute. Focus on typography, then spacing, then surfaces. One group at a time.

Copy as Code

One click to export current values as React props, CSS custom properties, Tailwind config, or a JSON snapshot. Paste into your codebase.

Keyboard First

Tab between knobs, arrow keys to adjust, Enter to confirm. Number keys jump to scenes. Fully operable without a mouse.

Three lines. Full control.

  1. Declare

    Call usePatch with your parameter config. Numbers get knobs. Booleans get toggles. Colors get wells. Nested objects become modules.

  2. Tweak

    Open the panel. Turn knobs, flip toggles, drag faders. Every change updates your component in real time. Save states as scenes.

  3. Ship

    Found the right values? Copy as code. Paste into your component. Remove the hook. The panel is dev-only and tree-shakes out in production.

Prompts to get started.

The fastest way to use PATCH is to tell your coding agent what you want. Here are some that work well.

Add to existing component

I have a card component with hover effects. Add PATCH controls for border radius, shadow depth, hover scale, and background opacity. Group shadow controls into their own module. Link shadow blur to shadow offset so they scale together.

Build with scenes

Create a hero section with PATCH. Add controls for font size, weight, letter spacing, padding, and accent color. Define three scenes: editorial with large light type, bold with tight heavy type, and playful with rounded colorful elements. Enable scene morphing.

Tune layout

Add PATCH to this dashboard grid. I want knobs for column count, gap, card padding, and card border radius. Add compact and spacious scenes. Group card-specific controls into a Card module with solo and mute.

Animation tuning

Create a modal with spring animation using Motion. Add PATCH controls for the entrance spring, backdrop blur, content border radius, and a replay action button. Link backdrop opacity to blur amount.

Drop it in.

terminal
npm install patchui
layout.tsx
import { PatchRoot } from 'patchui'
import 'patchui/styles.css'

export default function Layout({ children }) {
  return (
    <>
      {children}
      <PatchRoot />
    </>
  )
}

Wire it up.

Call usePatch in any component. Each call creates a module in the panel.

Card.tsx
import { usePatch, link } from 'patchui'
import { motion } from 'motion/react'

function Card() {
  const p = usePatch('Card', {
    blur:    [24, 0, 100],       // [default, min, max] → knob
    opacity: [0.8, 0, 1],
    scale:   1.18,              // auto-range → knob
    color:   '#ff5500',          // → color well
    visible: true,              // → toggle

    // Nested objects become modules
    shadow: {
      offsetY: [8, 0, 24],
      blur:    link('shadow.offsetY', v => v * 2), // linked!
      spread:  [0, -10, 10],
    },

    spring: {
      type: 'spring',
      visualDuration: 0.3,
      bounce: 0.2,
    },
  })

  return (
    <motion.div
      style={{
        filter: `blur(${p.blur}px)`,
        opacity: p.visible ? p.opacity : 0,
        color: p.color,
        boxShadow: `0 ${p.shadow.offsetY}px ${p.shadow.blur}px rgba(0,0,0,0.2)`,
      }}
      animate={{ scale: p.scale }}
      transition={p.spring}
    />
  )
}

Save. Morph. Compare.

Define named scenes and crossfade between them at any speed.

Hero.tsx
const p = usePatch('Hero', {
  fontSize:  [48, 24, 96],
  weight:    [800, 300, 900],
  padding:   [40, 16, 80],
  radius:    [16, 0, 32],
  accent:    '#f59e0b',
}, {
  scenes: {
    editorial: { fontSize: 72, weight: 300, padding: 64, radius: 0, accent: '#ffffff' },
    playful:   { fontSize: 36, weight: 900, padding: 24, radius: 24, accent: '#f472b6' },
    corporate: { fontSize: 42, weight: 600, padding: 48, radius: 8, accent: '#3b82f6' },
  },
  morphDuration: 400, // ms to crossfade
})

Wire parameters together.

The link() helper creates reactive connections between parameters. When the source changes, the linked parameter updates automatically.

Card.tsx
import { usePatch, link } from 'patchui'

const p = usePatch('Card', {
  elevation: [2, 0, 5],

  // These follow elevation automatically
  shadowY:       link('elevation', e => e * 4),
  shadowBlur:    link('elevation', e => e * 8),
  shadowOpacity: link('elevation', e => e * 0.04),
  translateY:    link('elevation', e => e * -1),

  // Cross-module links work too
  borderOpacity: link('elevation', e => 1 - e * 0.15),
})

// Turn one knob. Five values respond.

Config types.

The config object determines what controls appear in the panel.

Configuration format to control type mapping
FormatControlDescription
[default, min, max, step?] Knob Explicit range with optional step. Renders a rotary knob.
number Knob Auto-inferred range based on value magnitude.
boolean Toggle On/off switch with physical toggle appearance.
"#ff5500" Color Well Auto-detected from hex strings. Shows swatch and editable hex.
"Hello" Text Input Non-hex strings become editable text fields.
link(path, fn) Linked Knob Computed from another parameter. Shows a cable indicator.
{ type: "spring", ... } Spring Editor Visual spring curve with physics mode.
{ type: "select", options } Selector Dropdown with string options array.
{ type: "action" } Button Triggers onAction callback when pressed.
{ nested: ... } Module Collapsible group with solo/mute. Use _collapsed: true to start closed.

usePatch API

usePatch hook parameters
PropTypeDescription
namestringModule title in the panel
configPatchConfigParameter definitions (see config types)
options.scenesRecord<string, Partial>Named parameter snapshots for morphing
options.morphDurationnumberCrossfade duration in ms (default 300)
options.onAction(action: string) => voidCallback for action buttons
options.collapsedbooleanStart module collapsed (default false)

PatchRoot

Mount once at your app root. The panel renders via a portal.

PatchRoot component props
PropTypeDefault
position'top-right' | 'top-left' | 'bottom-right' | 'bottom-left''top-right'
shortcutstring'Alt+P'
theme'dark' | 'light' | 'auto''dark'
zIndexnumber99999