Introduction
React primitives, hooks, and a custom renderer for Stream Deck key, dial, and touch surfaces.
@fcannizzaro/streamdeck-react lets you build Stream Deck plugins with React instead of imperative SDK callbacks and manual image generation. You write JSX, state, and hooks; the library turns that tree into images and pushes them to Stream Deck hardware.
Why
The @elgato/streamdeck SDK is powerful but low-level. You track state manually, wire hardware events by hand, and generate images yourself. Even a simple counter key quickly becomes a mix of event handlers, state bookkeeping, and rendering code.
@fcannizzaro/streamdeck-react gives Stream Deck development the same model you already use in React apps:
- Declarative rendering -- describe your key as JSX, not imperative draw calls.
- Full React hooks --
useState,useEffect,useRef,useContext, custom hooks -- all work as expected. - Hardware-aware hooks --
useKeyDown,useDialRotate,useTouchTap, gesture hooks (useTap,useLongPress,useDoubleTap), settings hooks, lifecycle hooks, and SDK hooks compose with the rest of React. - Built-in primitives --
Box,Text,Image,Icon,ProgressBar,CircularGauge, andErrorBoundaryhelp you build compact device UIs fast. - Plugin-first workflow -- define actions with
defineAction(), register them withcreatePlugin(), and connect once. - Flexible styling -- use inline styles,
className, and thecn()helper for Tailwind-like utility strings.
Quick Example
import {
createPlugin,
defineAction,
useKeyDown,
googleFont,
cn,
} from "@fcannizzaro/streamdeck-react";
import { useState } from "react";
function CounterKey() {
const [count, setCount] = useState(0);
useKeyDown(() => {
setCount((c) => c + 1);
});
return (
<div
className={cn(
"flex h-full w-full flex-col items-center justify-center gap-1",
"bg-linear-to-br from-[#0f172a] to-[#1d4ed8]",
)}
>
<span className="text-[12px] font-semibold uppercase tracking-[0.2em] text-white/70">
Count
</span>
<span className="text-[34px] font-black text-white">{count}</span>
</div>
);
}
const counterAction = defineAction({
uuid: "com.example.react-basic.counter",
key: CounterKey,
info: {
name: "Counter",
icon: "imgs/actions/counter",
},
});
const inter = await googleFont("Inter");
const plugin = createPlugin({
fonts: [inter],
actions: [counterAction],
});
await plugin.connect();Core Workflow
- Create a React component for a key or encoder surface.
- Register it with
defineAction()using a UUID andinfofor manifest generation. - Pass your actions and font files to
createPlugin(). - Call
await plugin.connect()in your plugin entry file. - The bundler plugin auto-generates
manifest.jsonfrom your code at build time.
What You Can Build
- Keys -- counters, toggles, timers, status indicators, dashboards.
- Encoders -- volume controls, scrubbers, compact meters, quick actions.
- Touch interactions -- handle Stream Deck+ touch taps with
useTouchTap(). - Persistent state -- save per-action or global settings with the built-in settings hooks.
- Shared state -- connect Zustand, Jotai, React Query, or your own providers with wrappers.