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