npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

patchui

v0.1.0

Published

Modular parameter controls for React. Rotary knobs, toggles, color wells, scene morphing, and parameter linking — wired directly to your UI values.

Readme

PATCH

Modular parameter controls for React.

Rotary knobs, toggles, color wells, scene morphing, and parameter linking — wired directly to your component values.

Think synth rack for your UI. Drop a hook into any component, get a floating panel with hardware-style controls, and tweak values in real time. Copy the final values as code. Ship it.

Install

npm install patchui

Quick Start

1. Mount the panel root (once, at your layout level):

import { PatchRoot } from 'patchui'
import 'patchui/styles.css'

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

2. Wire parameters in any component:

import { usePatch, link } from 'patchui'

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

    shadow: {                   // nested object → module
      offsetY: [8, 0, 24],
      blur: link('shadow.offsetY', v => v * 2),  // linked param
    },
  })

  return (
    <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)`,
      }}
    />
  )
}

That's it. A PATCH panel appears with rotary knobs for every numeric value, toggles for booleans, and color wells for hex strings. Nested objects become collapsible modules.

Scenes

Define named parameter snapshots and crossfade between them:

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

Click a scene button and all values animate smoothly. Drag the morph slider to manually crossfade between any two scenes. Colors interpolate per-channel.

Parameter Linking

Wire parameters together so turning one knob drives multiple values:

const p = usePatch('Card', {
  elevation: [2, 0, 5],
  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),
})

// Turn one knob. Four values respond.

Config Types

| Format | Control | Description | |--------|---------|-------------| | [default, min, max, step?] | Knob | Explicit range with optional step | | number | Knob | Auto-inferred range | | boolean | Toggle | On/off switch | | "#hex" | Color Well | Hex color picker | | "string" | Text Input | Editable text field | | link(path, fn) | Linked Knob | Computed from another param | | { type: 'spring', ... } | Spring Editor | Visual spring curve | | { type: 'select', options } | Selector | Dropdown | | { type: 'action' } | Button | Triggers callback | | { nested: ... } | Module | Collapsible group with solo/mute |

API

usePatch(name, config, options?)

| Param | Type | Description | |-------|------|-------------| | name | string | Module title in the panel | | config | PatchConfig | Parameter definitions | | options.scenes | Record<string, Partial> | Named parameter snapshots | | options.morphDuration | number | Crossfade duration in ms (default 300) | | options.onAction | (action: string) => void | Callback for action buttons | | options.collapsed | boolean | Start module collapsed |

Returns a reactive object matching the shape of your config, with live values updated by the panel.

<PatchRoot />

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

| Prop | Type | Default | |------|------|---------| | position | 'top-right' \| 'top-left' \| 'bottom-right' \| 'bottom-left' | 'top-right' | | shortcut | string | 'Alt+P' | | theme | 'dark' \| 'light' \| 'auto' | 'dark' | | zIndex | number | 99999 |

link(source, transform)

Creates a reactive link between parameters. When the source value changes, the linked parameter recomputes automatically.

link('elevation', e => e * 4)  // follows 'elevation', multiplied by 4

Accessibility

PATCH follows WCAG 2.1 AA guidelines:

  • All controls are keyboard-operable (arrow keys, Page Up/Down, Home/End)
  • Proper ARIA roles (slider, switch) with live value announcements
  • Visible focus indicators on every interactive element
  • prefers-reduced-motion disables all animations
  • Color contrast ratios meet 4.5:1 minimum throughout

Tree-Shaking

PATCH is dev-only by design. Wrap it in an environment check to strip it from production:

// layout.tsx
const DevPatch = process.env.NODE_ENV === 'development'
  ? require('patchui').PatchRoot
  : () => null

Or use dynamic imports:

import dynamic from 'next/dynamic'
const PatchRoot = dynamic(() => import('patchui').then(m => m.PatchRoot), {
  ssr: false,
})

License

MIT