@fcannizzaro/streamdeck-react

How to Use

The day-to-day workflow for building Stream Deck plugins with @fcannizzaro/streamdeck-react.

@fcannizzaro/streamdeck-react is easiest to use when you think of it as a small runtime with four jobs: render components, map them to actions, connect to the SDK, and keep state in sync.

1. Build UI as React Components

Each action surface is just a React component.

  • Use normal React state and effects.
  • Render raw JSX, or use built-ins like Box, Text, Image, and ProgressBar.
  • Style with Tailwind classes via className and cn() for static styling. Use inline style only for dynamic values computed at runtime.
function StatusKey() {
  return (
    <div className="flex h-full w-full items-center justify-center bg-[#111827]">
      <span className="text-[18px] font-bold text-white">Ready</span>
    </div>
  );
}

2. Map Components to Actions

Use defineAction() to connect a UUID to one or more surfaces. Add info with name and icon so the bundler can auto-generate manifest.json.

export const statusAction = defineAction({
  uuid: "com.example.plugin.status",
  key: StatusKey,
  info: {
    name: "Status",
    icon: "imgs/actions/status",
  },
});

For encoder actions, provide a dial component. If dial is omitted, the key component is used as a fallback when the action is placed on an encoder.

For full-strip rendering across all encoders, use touchStrip instead of dial. See the TouchStrip Component guide.

3. Register Everything with createPlugin()

Your plugin entry file loads fonts, passes in the action list, and connects once.

const plugin = createPlugin({
  fonts: [font],
  actions: [statusAction],
});

await plugin.connect();

This file is the root of your plugin runtime.

4. Respond to Hardware with Hooks

Use the exported hooks instead of wiring SDK listeners manually.

  • Events: useKeyDown, useKeyUp, useDialRotate, useDialDown, useDialUp, useTouchTap, useDialHint
  • Gestures: useTap, useLongPress, useDoubleTap
  • TouchStrip: useTouchStrip, useTouchStripTap, useTouchStripDialRotate, useTouchStripDialDown, useTouchStripDialUp
  • Settings: useSettings, useGlobalSettings
  • Lifecycle: useWillAppear, useWillDisappear
  • Context: useDevice, useAction, useCanvas, useStreamDeck
  • Size: useSize
  • Coordinator: useChannel, useActionPresence, useCoordinator
  • Theme: useTheme
  • Utility: useInterval, useTimeout, usePrevious, useTick
  • Animation: useSpring, useTween
  • SDK helpers: useOpenUrl, useSwitchProfile, useSendToPI, usePropertyInspector, useShowAlert, useShowOk, useTitle

5. Pick a State Strategy

You have several options:

  • Local component state for simple one-off action UIs.
  • Built-in settings hooks when state should persist across reloads.
  • Action Coordinator for simple cross-action state sharing without external dependencies.
  • External shared state with wrappers when multiple action roots should see the same store.

The samples show all three approaches:

  • samples/basic/ for local state and settings
  • samples/zustand/ for shared module-scope state
  • samples/jotai/ and samples/pokemon/ for wrapper-based providers

6. Bundle for the Stream Deck Runtime

The bundler plugin exported from @fcannizzaro/streamdeck-react/vite handles everything:

  • streamDeckReact({ manifest: { ... } }) handles native Takumi bindings (lazy-loaded by default) and auto-generates manifest.json.
  • Action metadata is extracted from defineAction({ info }) calls at build time — no hand-written manifest needed.

7. Learn the API by Section

On this page