@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,
});

Configuration

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

uuid (required)

The action UUID. Must match the UUID declared in your manifest.json.

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',
});

touch

Reserved in the current action shape. For touch interactions on Stream Deck+, use useTouchTap from the mounted action root instead.

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 },
});

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.

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