@fcannizzaro/streamdeck-react

Dial & Encoder Support

Build encoder-aware actions with dial rendering, rotation handlers, and trigger hints.

Stream Deck+ encoder actions combine three capabilities:

  1. Dial rendering on the encoder display.
  2. Rotation and press events through the event hooks.
  3. Touch taps through useTouchTap().

Dial Rendering

The dial component defined in defineAction renders to the encoder display. The library pushes the image with action.setFeedback().

function VolumeDial() {
  const [volume, setVolume] = useState(50);
  const [muted, setMuted] = useState(false);

  useDialRotate(({ ticks }) => {
    if (!muted) {
      setVolume((v) => Math.max(0, Math.min(100, v + ticks * 2)));
    }
  });

  useDialDown(() => {
    setMuted((m) => !m);
  });

  useDialHint({
    rotate: "Adjust volume",
    press: muted ? "Unmute" : "Mute",
  });

  return (
    <div
      className={cn(
        "flex flex-col items-center justify-center w-full h-full gap-1",
        muted ? "bg-[#4a0000]" : "bg-[#1a1a1a]",
      )}
    >
      <span className="text-[#888] text-[12px]">Volume</span>
      <span className={cn("text-[24px] font-bold", muted ? "text-[#ff4444]" : "text-white")}>
        {muted ? "MUTE" : `${volume}%`}
      </span>
      {!muted && (
        <ProgressBar value={volume} height={4} color="#4CAF50" background="#333" borderRadius={2} />
      )}
    </div>
  );
}

export const volumeAction = defineAction<VolumeSettings>({
  uuid: "com.example.plugin.volume",
  dial: VolumeDial,
  defaultSettings: { volume: 50, muted: false },
  info: {
    name: "Volume",
    icon: "imgs/actions/volume",
    encoder: {
      layout: "$A0",
      triggerDescription: {
        rotate: "Adjust volume",
        push: "Toggle mute",
      },
    },
  },
});

Trigger Descriptions

Stream Deck+ shows contextual hints for what each dial action does. Use useDialHint to set these:

useDialHint({
  rotate: "Adjust volume",
  press: "Toggle mute",
  touch: "Open settings",
  longTouch: "Reset to default",
});

The hints update automatically when the values change. Under the hood this calls action.setTriggerDescription().

Touch Interaction

Use useTouchTap() when your encoder action needs touch input from the Stream Deck+ touch area:

function VolumeDial() {
  useTouchTap(({ tapPos, hold }) => {
    console.log("touch", tapPos, hold);
  });

  return <div className="w-full h-full" />;
}

Key + Dial Actions

An action can support both key and encoder placement. Define separate components:

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

If dial is not provided, the key component is used as a fallback for encoder placement.

On this page