@fcannizzaro/streamdeck-react

defineAction

Define a Stream Deck action with React components for keys, dials, and touch displays.

defineAction maps a Stream Deck action UUID to React components that render on the hardware.

Basic Usage

import { defineAction, useKeyDown } from "@fcannizzaro/streamdeck-react";
import { useState } from "react";

function CounterKey() {
  const [count, setCount] = useState(0);
  useKeyDown(() => setCount((c) => c + 1));

  return (
    <div
      style={{
        width: "100%",
        height: "100%",
        alignItems: "center",
        justifyContent: "center",
        background: "#000",
      }}
    >
      <span style={{ color: "white", fontSize: 32 }}>{count}</span>
    </div>
  );
}

export const counterAction = defineAction({
  uuid: "com.example.plugin.counter",
  key: CounterKey,
  info: {
    name: "Counter",
    icon: "imgs/actions/counter",
  },
});

Configuration

interface ActionConfig<S extends JsonObject = JsonObject> {
  uuid: string;
  key?: ComponentType;
  dial?: ComponentType;
  touchStrip?: ComponentType;
  dialLayout?: EncoderLayout;
  wrapper?: WrapperComponent;
  defaultSettings?: Partial<S>;
  info?: ActionManifestInfo;
}

uuid (required)

The action UUID. Must start with the plugin UUID prefix (e.g., "com.example.plugin.").

key

Component rendered when the action is placed on a key (Keypad controller).

dial

Component rendered when the action is placed on an encoder slot (Stream Deck+). If not provided and the action is placed on an encoder, the key component is used as a fallback.

dialLayout

Customize the Stream Deck+ feedback layout sent to setFeedbackLayout(). By default, encoder actions use a full-width canvas layout:

{
  id: 'com.example.plugin.react-layout',
  items: [
    {
      key: 'canvas',
      type: 'pixmap',
      rect: [0, 0, 200, 100],
    },
  ],
}

If you provide a custom object layout, it should include a pixmap item keyed as canvas so the renderer can target it.

export const volumeAction = defineAction({
  uuid: "com.example.plugin.volume",
  dial: VolumeDial,
  dialLayout: "$A1",
});

touchStrip

Component for full-strip rendering across all encoders. When set, replaces per-encoder dial display with a single shared React tree that spans the entire Stream Deck+ touch strip. See the TouchStrip Component guide.

For encoder rendering, you can still pair a key surface with a dedicated dial component:

export const volumeAction = defineAction<VolumeSettings>({
  uuid: "com.example.plugin.volume",
  key: VolumeKey, // Rendered on a key
  dial: VolumeDial, // Rendered on the dial display
  defaultSettings: { volume: 50, muted: false },
  info: {
    name: "Volume",
    icon: "imgs/actions/volume",
    encoder: {
      layout: "$A0",
      triggerDescription: {
        rotate: "Adjust volume",
        push: "Mute / Unmute",
      },
    },
  },
});

wrapper

An optional component that wraps this action's root. Use this for action-scoped providers:

export const myAction = defineAction({
  uuid: "com.example.plugin.my-action",
  key: MyKey,
  wrapper: ({ children }) => <MyActionProvider>{children}</MyActionProvider>,
});

This wrapper is nested inside the global plugin wrapper (if any).

defaultSettings

Default settings for new instances. These are shallow-merged with the settings stored in the Stream Deck.

info

Action manifest metadata used by the bundler plugin to auto-generate manifest.json. Required if you want the action included in the generated manifest.

At minimum, provide name and icon:

export const counterAction = defineAction({
  uuid: "com.example.plugin.counter",
  key: CounterKey,
  info: {
    name: "Counter",
    icon: "imgs/actions/counter",
  },
});

For encoder actions, include the encoder block:

export const volumeAction = defineAction({
  uuid: "com.example.plugin.volume",
  dial: VolumeDial,
  info: {
    name: "Volume",
    icon: "imgs/actions/volume",
    encoder: {
      layout: "$A0",
      triggerDescription: {
        rotate: "Adjust volume",
        push: "Mute / Unmute",
      },
    },
  },
});

To exclude an action from the manifest while keeping it functional at runtime, set disabled: true:

export const debugAction = defineAction({
  uuid: "com.example.plugin.debug",
  key: DebugKey,
  info: {
    name: "Debug",
    icon: "imgs/actions/debug",
    disabled: true, // not included in manifest.json
  },
});

See Manifest Generation for the full ActionManifestInfo reference and how the auto-extraction pipeline works.

Typed Settings

Pass a type parameter to defineAction for typed settings:

type VolumeSettings = {
  volume: number;
  muted: boolean;
};

export const volumeAction = defineAction<VolumeSettings>({
  uuid: "com.example.plugin.volume",
  dial: VolumeDial,
  defaultSettings: { volume: 50, muted: false },
});

Your components then use useSettings<VolumeSettings>() to get typed access.

How It Maps to SingletonAction

Internally, each defineAction call produces an ActionDefinition object. When createPlugin processes it, a SingletonAction subclass is generated that:

  • Creates a React root on onWillAppear
  • Destroys it on onWillDisappear
  • Dispatches SDK events (onKeyDown, onDialRotate, etc.) into the root's event bus

On this page