@fcannizzaro/streamdeck-react

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, and ErrorBoundary help you build compact device UIs fast.
  • Plugin-first workflow -- define actions with defineAction(), register them with createPlugin(), and connect once.
  • Flexible styling -- use inline styles, className, and the cn() 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

  1. Create a React component for a key or encoder surface.
  2. Register it with defineAction() using a UUID and info for manifest generation.
  3. Pass your actions and font files to createPlugin().
  4. Call await plugin.connect() in your plugin entry file.
  5. The bundler plugin auto-generates manifest.json from 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.

Start Here

On this page