@fcannizzaro/streamdeck-react

Installation

Install @fcannizzaro/streamdeck-react, React, and the tooling needed to bundle a Stream Deck plugin.

Requirements

  • Node.js 20+
  • React 19+
  • TypeScript 6+ if your plugin is written in TypeScript

Install the Library

bun add @fcannizzaro/streamdeck-react react

@fcannizzaro/streamdeck-react ships the Stream Deck SDK integration and Takumi renderer bindings as package dependencies, so a basic plugin does not need extra runtime packages just to render keys.

Start with the Create CLI

If you want a ready-to-run plugin scaffold, use create-streamdeck-react:

bun create streamdeck-react

The CLI asks for your plugin UUID, author, supported platforms, native targets, and starter example, then generates a complete .sdPlugin project.

Add the Bundler

Use Vite 8+ with Rolldown:

bun add -d vite@8.0.0 @vitejs/plugin-react@6.0.1

If you want to use React Compiler, also add:

bun add -d @rolldown/plugin-babel @babel/core babel-plugin-react-compiler

Native Bindings

Native Takumi .node binaries are lazy-loaded by default. On first plugin startup, the binary matching the current platform is downloaded from npm and cached on disk. No manual installation of platform-specific packages is needed.

If you prefer to bundle binaries at build time (e.g. for offline environments), set nativeBindings: "copy" on streamDeckReact() and install the matching @takumi-rs/core-* packages:

TargetPackage
darwin-arm64@takumi-rs/core-darwin-arm64
darwin-x64@takumi-rs/core-darwin-x64
win32-arm64@takumi-rs/core-win32-arm64-msvc
win32-x64@takumi-rs/core-win32-x64-msvc

Example (copy mode only):

bun add @takumi-rs/core-darwin-arm64

Custom Native Modules

If your plugin depends on additional NAPI-RS (or other native .node) packages, you can register them with the same lazy-load or copy infrastructure via the nativeModules option:

streamDeckReact({
  manifest: {
    /* ... */
  },
  nativeModules: [
    {
      importSpecifier: "@mypkg/native-core",
      scope: "@mypkg", // optional — auto-inferred from scoped packages
      bindings: {
        "darwin-arm64": { pkg: "native-core-darwin-arm64", file: "native.darwin-arm64.node" },
        "darwin-x64": { pkg: "native-core-darwin-x64", file: "native.darwin-x64.node" },
        "win32-x64": { pkg: "native-core-win32-x64", file: "native.win32-x64-msvc.node" },
      },
      exports: ["MyClass", "helperFn"],
    },
  ],
});

Each entry gets the same treatment as the built-in Takumi binding:

  • Lazy mode (default): a virtual module downloads the .node file from npm at runtime.
  • Copy mode: .node files are copied from node_modules during the build.

The nativeBindings option controls the strategy for all native modules. See the NativeModuleConfig type for the full interface.

What Gets Installed

  • react-reconciler powers the custom React renderer.
  • @elgato/streamdeck handles the Stream Deck SDK connection.
  • @takumi-rs/core and @takumi-rs/helpers render your JSX tree to hardware-ready images.

Fonts

You must provide at least one font to createPlugin(). The easiest approach is googleFont() which downloads TTF fonts directly from Google Fonts and caches them to disk:

import { createPlugin, googleFont } from "@fcannizzaro/streamdeck-react";

const inter = await googleFont("Inter");

const plugin = createPlugin({
  fonts: [inter],
  actions: [...],
});

For fonts not available on Google Fonts, load them manually via readFile:

import { readFile } from "node:fs/promises";

const font = {
  name: "MyFont",
  data: await readFile(new URL("../fonts/MyFont-Regular.ttf", import.meta.url)),
  weight: 400,
  style: "normal" as const,
};

See Font Management for the full API and caching details.

Next Steps

On this page