Comprehensive reference for sdpi-components (Stream Deck Property Inspector Components) - the official UI library for building Property Inspectors...
This skill provides comprehensive guidance for using sdpi-components (Stream Deck Property Inspector Components), the official UI library for building Property Inspectors in Stream Deck plugins.
It is designed to be used with coding agents compatible with the add-skill specification.
References:
Use this skill whenever you need to:
Before using sdpi-components, keep these core concepts in mind:
sdpi-components
Component structure
sdpi- (e.g., <sdpi-textfield>, <sdpi-checkbox>).<sdpi-item> with a label attribute for consistent layout.setting attribute to bind to settings paths (supports nested paths like foo.bar.prop).Settings persistence
setting attribute.global attribute to persist to global settings (shared across all action instances).Communication
SDPIComponents.streamDeckClient for advanced communication patterns.Data sources
<sdpi-select>, <sdpi-checkbox-list>, <sdpi-radio>) support dynamic data sources.Before using sdpi-components, verify all of the following:
Property Inspector setup
ui/ directory.PropertyInspectorPath is configured in manifest.json (action or plugin level).Library inclusion
sdpi-components.js from releases.sdpi-components.js in the ui/ directory alongside your HTML file.Technical decisions
Property Inspector files using sdpi-components should be organized as follows:
my-streamdeck-plugin/
*.sdPlugin/
βββ bin/
βββ imgs/
βββ ui/ # Property Inspector files
β βββ my-action.html
β βββ sdpi-components.js # Local library (recommended)
βββ manifest.json
src/
βββ actions/
βββ plugin.ts
The ui/ directory contains all Property Inspector HTML files and the sdpi-components.js library.
Follow these steps to use sdpi-components in your Property Inspector:
Local (recommended for distribution):
sdpi-components.js from the releases page.ui/ directory.<script src="sdpi-components.js"></script>
Remote (development/prototyping only):
<script src="https://sdpi-components.dev/releases/v4/sdpi-components.js"></script>
β οΈ Important: Always use local references when distributing your plugin to ensure it works offline and provides a consistent experience.
Create your Property Inspector HTML file:
<!doctype html>
<html>
<head lang="en">
<meta charset="utf-8" />
<script src="sdpi-components.js"></script>
</head>
<body>
<sdpi-item label="Name">
<sdpi-textfield setting="name" placeholder="Enter name"></sdpi-textfield>
</sdpi-item>
<sdpi-item label="Enabled">
<sdpi-checkbox setting="enabled"></sdpi-checkbox>
</sdpi-item>
</body>
</html>
Use the setting attribute to bind components to your settings:
<sdpi-item label="Favorite Color">
<sdpi-color setting="favColor"></sdpi-color>
</sdpi-item>
<sdpi-item label="Timeout (seconds)">
<sdpi-range setting="timeout" min="1" max="60" step="1" showlabels></sdpi-range>
</sdpi-item>
To persist settings globally (shared across all action instances):
<sdpi-item label="API Key">
<sdpi-textfield setting="apiKey" global></sdpi-textfield>
</sdpi-item>
For advanced communication patterns:
<script>
const { streamDeckClient } = SDPIComponents;
// Get current settings
streamDeckClient.getSettings().then(payload => {
console.log('Current settings:', payload.settings);
});
// Listen for settings updates
streamDeckClient.didReceiveSettings.subscribe(ev => {
console.log('Settings updated:', ev.payload.settings);
});
// Set settings programmatically
streamDeckClient.setSettings({
name: "John Doe",
enabled: true
});
</script>
The sdpi-components library provides the following web components:
<sdpi-textfield>Single-line text input.
Attributes:
setting (string): Path in settings object (supports nested paths)value (string): Current valuedefault (string): Default value if setting is undefinedplaceholder (string): Placeholder textdisabled (boolean): Whether the input is disabledrequired (boolean): Shows icon when value is emptymaxlength (number): Maximum length for the input stringpattern (string): Regular expression for validationglobal (boolean): Persist to global settingsExample:
<sdpi-item label="Name">
<sdpi-textfield
setting="name"
placeholder="Enter your name"
required>
</sdpi-textfield>
</sdpi-item>
<sdpi-textarea>Multi-line text input.
Attributes:
setting (string): Path in settings objectvalue (string): Current valuedefault (string): Default valueplaceholder (string): Placeholder textdisabled (boolean): Whether the input is disabledrows (number): Number of visible rowsglobal (boolean): Persist to global settingsExample:
<sdpi-item label="Description">
<sdpi-textarea setting="description" rows="4"></sdpi-textarea>
</sdpi-item>
<sdpi-password>Password input (obscured text).
Attributes:
setting (string): Path in settings objectvalue (string): Current valuedefault (string): Default valueplaceholder (string): Placeholder textdisabled (boolean): Whether the input is disabledglobal (boolean): Persist to global settingsExample:
<sdpi-item label="API Key">
<sdpi-password setting="apiKey"></sdpi-password>
</sdpi-item>
<sdpi-checkbox>Single boolean checkbox.
Attributes:
setting (string): Path in settings objectvalue (boolean): Current statedefault (boolean): Default state if setting is undefinedlabel (string): Label shown next to checkboxdisabled (boolean): Whether the checkbox is disabledglobal (boolean): Persist to global settingsExample:
<sdpi-item label="Options">
<sdpi-checkbox setting="enabled" label="Enable feature"></sdpi-checkbox>
</sdpi-item>
<sdpi-checkbox-list>Multiple checkboxes (supports data sources).
Attributes:
setting (string): Path in settings object (stores array of selected values)datasource (string): Optional remote data source event nameloading (string): Text shown while loading data sourcehot-reload (boolean): Monitor sendToPropertyInspector for updatesglobal (boolean): Persist to global settingsExample (static):
<sdpi-item label="Features">
<sdpi-checkbox-list setting="features">
<option value="feature1">Feature 1</option>
<option value="feature2">Feature 2</option>
<option value="feature3">Feature 3</option>
</sdpi-checkbox-list>
</sdpi-item>
Example (with data source):
<sdpi-item label="Available Devices">
<sdpi-checkbox-list
setting="devices"
datasource="getDevices"
loading="Loading devices...">
</sdpi-checkbox-list>
</sdpi-item>
<sdpi-radio>Radio button group (single selection, supports data sources).
Attributes:
setting (string): Path in settings objectdatasource (string): Optional remote data source event nameloading (string): Text shown while loading data sourcehot-reload (boolean): Monitor sendToPropertyInspector for updatesglobal (boolean): Persist to global settingsExample (static):
<sdpi-item label="Mode">
<sdpi-radio setting="mode">
<option value="simple">Simple</option>
<option value="advanced">Advanced</option>
</sdpi-radio>
</sdpi-item>
<sdpi-select>Dropdown select (single selection, supports data sources).
Attributes:
setting (string): Path in settings objectvalue (string | number | boolean): Current selected valuedefault (string): Default value if setting is undefinedvalue-type ('string' | 'number' | 'boolean'): How to interpret the value (default: 'string')placeholder (string): Placeholder text when no selectionlabel-setting (string): Optional path to persist the label of selected optiondatasource (string): Optional remote data source event nameloading (string): Text shown while loading data sourcehot-reload (boolean): Monitor sendToPropertyInspector for updatesdisabled (boolean): Whether the select is disabledglobal (boolean): Persist to global settingsExample (static):
<sdpi-item label="Color">
<sdpi-select setting="color" placeholder="Choose a color">
<optgroup label="Primary Colors">
<option value="#ff0000">Red</option>
<option value="#00ff00">Green</option>
<option value="#0000ff">Blue</option>
</optgroup>
<option value="#000000">Black</option>
<option value="#ffffff">White</option>
</sdpi-select>
</sdpi-item>
Example (with data source):
<sdpi-item label="Device">
<sdpi-select
setting="device"
datasource="getDevices"
loading="Loading devices..."
placeholder="Select a device">
</sdpi-select>
</sdpi-item>
<sdpi-range>Range slider for numeric values.
Attributes:
setting (string): Path in settings objectvalue (string): Current valuedefault (string): Default valuemin (number): Minimum valuemax (number): Maximum valuestep (number): Step intervalshowlabels (boolean): Show min/max labelsdisabled (boolean): Whether the range is disabledglobal (boolean): Persist to global settingsExample:
<sdpi-item label="Brightness">
<sdpi-range
setting="brightness"
min="0"
max="100"
step="5"
showlabels>
</sdpi-range>
</sdpi-item>
Custom labels:
<sdpi-range setting="volume" min="0" max="100" showlabels>
<span slot="label-min">Mute</span>
<span slot="label-max">Max</span>
</sdpi-range>
<sdpi-calendar>Date, time, and datetime picker (supports multiple types).
Attributes:
type (string): One of 'date', 'datetime-local', 'month', 'time', 'week'setting (string): Path in settings objectvalue (string): Current value (ISO format)default (string): Default valuemin (string): Minimum date/time (ISO format)max (string): Maximum date/time (ISO format)step (number): Step interval (especially for time)disabled (boolean): Whether the calendar is disabledglobal (boolean): Persist to global settingsExamples:
<sdpi-item label="Date">
<sdpi-calendar type="date" setting="important_date"></sdpi-calendar>
</sdpi-item>
<sdpi-item label="Time">
<sdpi-calendar type="time" setting="alarm_time" step="300"></sdpi-calendar>
</sdpi-item>
<sdpi-item label="Date and Time">
<sdpi-calendar type="datetime-local" setting="event_datetime"></sdpi-calendar>
</sdpi-item>
<sdpi-color>Color picker (returns hex string).
Attributes:
setting (string): Path in settings objectvalue (string): Current color (hex format, e.g., "#ff0000")default (string): Default colordisabled (boolean): Whether the color picker is disabledglobal (boolean): Persist to global settingsExample:
<sdpi-item label="Background Color">
<sdpi-color setting="bgColor" default="#ffffff"></sdpi-color>
</sdpi-item>
<sdpi-file>File picker input.
Attributes:
setting (string): Path in settings objectaccept (string): File type filter (e.g., "image/*", ".json")label (string): Button label textdisabled (boolean): Whether the file picker is disabledglobal (boolean): Persist to global settingsExample:
<sdpi-item label="Icon File">
<sdpi-file
setting="iconPath"
accept="image/*"
label="Choose Icon">
</sdpi-file>
</sdpi-item>
<sdpi-button>Action button.
Attributes:
label (string): Button textdisabled (boolean): Whether the button is disabledExample:
<sdpi-item label="Actions">
<sdpi-button label="Test Connection" onclick="testConnection()"></sdpi-button>
</sdpi-item>
<sdpi-delegate>Custom component wrapper for plugin-invoked actions (e.g., folder picker).
Attributes:
setting (string): Path in settings object (value is set by plugin)label (string): Button label textdisabled (boolean): Whether the delegate is disabledglobal (boolean): Persist to global settingsExample:
<sdpi-item label="Folder">
<sdpi-delegate
setting="folderPath"
label="Choose Folder">
</sdpi-delegate>
</sdpi-item>
Plugin side (TypeScript example):
// Send folder picker request
streamDeck.actions.openUrl("file:///path/to/folder");
// Or use sendToPropertyInspector to set the value
streamDeck.actions.sendToPropertyInspector(context, {
event: "folderSelected",
path: "/selected/folder/path"
});
<sdpi-item>Wrapper component for consistent layout (label + component).
Attributes:
label (string): Label text displayed on the leftinfo (string): Optional info text/tooltipExample:
<sdpi-item label="Name" info="Enter your display name">
<sdpi-textfield setting="name"></sdpi-textfield>
</sdpi-item>
Data sources allow components to dynamically populate their options from the plugin at runtime.
Data sources are supported in:
<sdpi-select><sdpi-checkbox-list><sdpi-radio>HTML:
<sdpi-item label="Device">
<sdpi-select
setting="device"
datasource="getDevices"
loading="Fetching devices..."
hot-reload>
</sdpi-select>
</sdpi-item>
Configuration attributes:
datasource (string): Event name that will be sent to the pluginloading (string): Optional text shown while loadinghot-reload (boolean): Monitor sendToPropertyInspector for real-time updatesManual refresh:
const selectElement = document.querySelector('[setting="device"]');
selectElement.refresh(); // Manually refresh the data source
When a component with a datasource is initialized, the plugin receives a sendToPlugin event:
Request payload:
{
"action": "com.example.myaction",
"event": "sendToPlugin",
"context": "unique-context-id",
"payload": {
"event": "getDevices",
"isRefresh": undefined | true
}
}
Plugin response (via sendToPropertyInspector):
{
"action": "com.example.myaction",
"event": "sendToPropertyInspector",
"context": "unique-context-id",
"payload": {
"event": "getDevices",
"items": [
{
"label": "Device Group",
"children": [
{
"label": "Device 1",
"value": "device1"
},
{
"label": "Device 2",
"value": "device2"
}
]
},
{
"label": "Device 3",
"value": "device3"
}
]
}
}
TypeScript type definitions:
type DataSourcePayload = {
event: string;
items: DataSourceResult;
};
type DataSourceResult = DataSourceResultItem[];
type DataSourceResultItem = Item | ItemGroup;
type Item = {
disabled?: boolean;
label?: string;
value: string;
};
type ItemGroup = {
label?: string;
children: Item[];
};
Example plugin implementation (Node.js/TypeScript):
import streamDeck, { action, type SendToPluginEvent } from "@elgato/streamdeck";
@action({ UUID: "com.example.myaction" })
class MyAction {
override onSendToPlugin(ev: SendToPluginEvent): void {
if (ev.payload.event === "getDevices") {
// Fetch devices from your source
const devices = [
{ label: "Device 1", value: "device1" },
{ label: "Device 2", value: "device2" }
];
// Send response to Property Inspector
streamDeck.actions.sendToPropertyInspector(ev.context, {
event: "getDevices",
items: devices
});
}
}
}
The SDPIComponents.streamDeckClient provides methods and events for advanced communication between Property Inspector and plugin.
const { streamDeckClient } = SDPIComponents;
getSettings()Get current action-specific settings.
Returns: Promise resolving to { settings: object }
Example:
streamDeckClient.getSettings().then(payload => {
console.log('Current settings:', payload.settings);
});
setSettings(settings)Update action-specific settings.
Parameters:
settings (object): Settings object to persistExample:
streamDeckClient.setSettings({
name: "John Doe",
enabled: true
});
getGlobalSettings()Get plugin-wide global settings.
Returns: Promise resolving to { settings: object }
Example:
streamDeckClient.getGlobalSettings().then(payload => {
console.log('Global settings:', payload.settings);
});
setGlobalSettings(settings)Update plugin-wide global settings.
Parameters:
settings (object): Global settings object to persistExample:
streamDeckClient.setGlobalSettings({
apiKey: "secret-key",
theme: "dark"
});
getConnectionInfo()Get information about the Stream Deck environment.
Returns: Promise resolving to connection info object
Example:
streamDeckClient.getConnectionInfo().then(info => {
console.log('Plugin UUID:', info.plugin.uuid);
console.log('Action:', info.actionInfo.name);
console.log('OS:', info.applicationInfo.platform);
console.log('Language:', info.applicationInfo.language);
console.log('Devices:', info.devices);
});
send(event, payload)Send custom events to the plugin or host environment.
Parameters:
event (string): Event name (e.g., "sendToPlugin", "openUrl")payload (object): Event payloadExample:
// Send custom message to plugin
streamDeckClient.send("sendToPlugin", {
event: "customEvent",
data: { foo: "bar" }
});
// Open URL
streamDeckClient.send("openUrl", {
url: "https://example.com"
});
didReceiveSettingsSubscribe to settings updates from the plugin.
Example:
streamDeckClient.didReceiveSettings.subscribe(ev => {
console.log('Settings received:', ev.payload.settings);
// Update UI if needed
});
didReceiveGlobalSettingsSubscribe to global settings updates from the plugin.
Example:
streamDeckClient.didReceiveGlobalSettings.subscribe(ev => {
console.log('Global settings received:', ev.payload.settings);
});
sendToPropertyInspectorSubscribe to custom messages from the plugin.
Example:
streamDeckClient.sendToPropertyInspector.subscribe(ev => {
if (ev.payload.event === "deviceConnected") {
console.log('Device connected:', ev.payload.device);
}
});
Stream Deck supports localization via JSON files in your plugin directory:
File structure:
*.sdPlugin/
βββ manifest.json
βββ en.json # English (default)
βββ fr.json # French
βββ de.json # German
βββ es.json # Spanish
βββ ja.json # Japanese
βββ ko.json # Korean
βββ zh_CN.json # Chinese (Simplified)
Example en.json:
{
"Name": "My Plugin",
"Description": "A great plugin",
"Actions": {
"MyAction": {
"Name": "My Action",
"Tooltip": "Does something"
}
}
}
Example fr.json:
{
"Name": "Mon Plugin",
"Description": "Un super plugin",
"Actions": {
"MyAction": {
"Name": "Mon Action",
"Tooltip": "Fait quelque chose"
}
}
}
sdpi-components does not provide built-in localization for Property Inspector UI labels. To localize your Property Inspector:
const { streamDeckClient } = SDPIComponents;
streamDeckClient.getConnectionInfo().then(info => {
const language = info.applicationInfo.language; // e.g., "en", "fr", "de"
updateUILabels(language);
});
const translations = {
en: {
name: "Name",
enabled: "Enabled"
},
fr: {
name: "Nom",
enabled: "ActivΓ©"
}
};
function updateUILabels(language) {
const t = translations[language] || translations.en;
document.querySelector('[setting="name"]').parentElement
.querySelector('sdpi-item').setAttribute('label', t.name);
}
Use this checklist when building Property Inspectors with sdpi-components:
Component selection
<sdpi-item> with label for consistent layout.placeholder attributes to guide users.Settings management
setting attribute on components for automatic binding."user.name") for organized settings.global attribute only when settings should be shared across actions.default values so actions work immediately.Data sources
hot-reload for real-time updates when appropriate.loading text for better UX.Performance
Distribution
sdpi-components.js in distributed plugins.User experience
info attribute on <sdpi-item>).pattern on textfield, min/max on range).Communication
streamDeckClient for advanced communication patterns.<!doctype html>
<html>
<head lang="en">
<meta charset="utf-8" />
<script src="sdpi-components.js"></script>
</head>
<body>
<sdpi-item label="Action Name">
<sdpi-textfield setting="actionName" placeholder="Enter action name"></sdpi-textfield>
</sdpi-item>
<sdpi-item label="Enabled">
<sdpi-checkbox setting="enabled"></sdpi-checkbox>
</sdpi-item>
<sdpi-item label="Timeout (seconds)">
<sdpi-range setting="timeout" min="1" max="60" step="1" showlabels></sdpi-range>
</sdpi-item>
</body>
</html>
<!doctype html>
<html>
<head lang="en">
<meta charset="utf-8" />
<script src="sdpi-components.js"></script>
</head>
<body>
<sdpi-item label="Device">
<sdpi-select
setting="device"
datasource="getDevices"
loading="Loading devices..."
placeholder="Select a device">
</sdpi-select>
</sdpi-item>
<script>
const { streamDeckClient } = SDPIComponents;
// Listen for device updates
streamDeckClient.sendToPropertyInspector.subscribe(ev => {
if (ev.payload.event === "deviceListUpdated") {
// Refresh the select
document.querySelector('[setting="device"]').refresh();
}
});
</script>
</body>
</html>
<!doctype html>
<html>
<head lang="en">
<meta charset="utf-8" />
<script src="sdpi-components.js"></script>
</head>
<body>
<sdpi-item label="Mode">
<sdpi-select setting="mode">
<option value="simple">Simple</option>
<option value="advanced">Advanced</option>
</sdpi-select>
</sdpi-item>
<div id="advanced-settings" style="display: none;">
<sdpi-item label="Advanced Option">
<sdpi-textfield setting="advancedOption"></sdpi-textfield>
</sdpi-item>
</div>
<script>
const { streamDeckClient } = SDPIComponents;
const modeSelect = document.querySelector('[setting="mode"]');
const advancedDiv = document.getElementById('advanced-settings');
// Listen for settings changes
streamDeckClient.didReceiveSettings.subscribe(ev => {
const mode = ev.payload.settings?.mode;
if (mode === 'advanced') {
advancedDiv.style.display = 'block';
} else {
advancedDiv.style.display = 'none';
}
});
// Also handle local changes
modeSelect.addEventListener('change', (e) => {
if (e.target.value === 'advanced') {
advancedDiv.style.display = 'block';
} else {
advancedDiv.style.display = 'none';
}
});
</script>
</body>
</html>
<!doctype html>
<html>
<head lang="en">
<meta charset="utf-8" />
<script src="sdpi-components.js"></script>
</head>
<body>
<!-- Global settings (shared across all actions) -->
<sdpi-item label="API Key">
<sdpi-textfield setting="apiKey" global></sdpi-textfield>
</sdpi-item>
<!-- Action-specific settings -->
<sdpi-item label="Action Name">
<sdpi-textfield setting="actionName"></sdpi-textfield>
</sdpi-item>
<sdpi-item label="Enabled">
<sdpi-checkbox setting="enabled"></sdpi-checkbox>
</sdpi-item>
</body>
</html>
sdpi-components.js included (local for distribution, remote for development).ui/ directory.PropertyInspectorPath added to manifest.json (action or plugin level).setting attributes properly configured on components.<sdpi-item> with labels.datasource attribute set.onSendToPlugin handler for data source event.sendToPropertyInspector with correct payload structure.items array contains valid Item or ItemGroup objects.hot-reload implemented if real-time updates needed.loading text provided for better UX.sdpi-components.js included (not remote CDN).When you invoke a coding agent to work with sdpi-components:
setting attributes.This ensures a consistent, robust Property Inspector development workflow using the official sdpi-components library.