Skip to content

Property Inspector

Stream Deck's integrated HTML5 Property Inspector allows you to communicate with your plugin from Stream Deck software's property pane.

Since the Property Inspector is based on standard HTML5 it is easy to layout shown items as you like.

For easier and more stream-lined usage, however, we recommend using our integrated CSS and tools, as we will improve these over time. So you will benefit from improvements automatically.

Our PISamples plugin demonstrates all supported HTML elements in the Property Inspector.


General operation

Stream Deck's Property Inspector (PI) is an HTML5 view, based on a standard Flexbox-Layout. So you can use any standard Javascript, HTML5 and CSS-techniques to show your content.

For easier setup, we already included some layout and styling to get you started quickly, without further adding your own styles.

PI's main view is wrapped in a simple HTML-node of the class sdpi-wrapper, like so:

<div class="sdpi-wrapper">
    ...
    your content goes here
    ...
<div>

To make sure proper styling is applied to the wrapper's contents, you would link our included stylesheet in the <head> section of PI's HTML page. Alternatively you can just clone one of the included samples and start from there.

A basic layout of a custom PI looks like this:

<head>
    <meta charset="utf-8" />
    <title>My Property Inspector</title>
    <link rel="stylesheet" href="sdpi.css">
</head>

<body>
    <div class="sdpi-wrapper">
        ...
    <div>
</body>

which brings up an empty property inspector as soon as you click your plugin's default action.

Adding elements

Standard elements of Stream Deck's PI always consist of a label and a value, each of which are identified by their respective classes: sdpi-item-label and sdpi-item-value.

Since you can add multiple label/value pairs, each of these pairs are wrapped into an sdpi-item node.

<div class="sdpi-item"> is basically all that's required. For layout purposes, sometimes it is required to adjust the html-elements slightly. To accomodate for this, there are helper-classes in the standard sdpi.css file, which make sure the items are aligned properly. Therefore it is highly recommended to add a type to the sdpi-item, so it can make use of the included helper css.

<div class="sdpi-item" type="textarea">

Supported Types

HTML-Element SDPI-element Description
field type="field" <optional> An input control, which lets the user enter short text (e.g. her name).
password type="password" An input control, which lets the user enter obscured text. Instead of characters, the field shows '•'. Text-input is contained in the element's value.
email type="email" An input control, which lets the user enter an email-address. Validation is NOT performed automatically.
date type="date" An input control to enter a date.
month type="month" An input control to select/display a month.
week type="week" An input control to select/display a week.
time type="time" An input control to select/display a time.
datetime-local type="datetime-local" An input control to select/display a date/time string in ISO format (e.g. "2019-01-06T12:22").
button type="button" <optional> A simple styled HTML-button.
textarea type="textarea" An input control, which lets the user enter multiple lines of text.
select type="select" A regular select element can have options to select from.
checkbox type="checkbox" A checkbox which lets a user selet ('check') one or more of one or more choices.
radio type="radio" A radio-button (mostly used in a group of radio-buttons) which lets a user select only one of a number of choices.
range type="range" A range (or slider) control, to let the user adjust a value (e.g. number or color)
color type="color" Shows a color-preview and let the user open a color-picker to change it's value visually
file type="file" A file-upload element with label, which opens a file-dialog and lets the user choose a file. It differs a bit from the regular fileselector, in that it allows you to pick up the full path of the selected file. (You will find a sample how to do this in the PISamples plugin.
list type="list" A regular list element to show items to select from. Ordered, unordered lists and some other list-types are supported. (Btw: in the PISamples plugin, there's a short utility javascript, which already make the lists interactive/clickable... Click here for more info
table type="table" Also table elements are supported, where each cell can contain a (clickable) value (in the PISamples plugin, you'll find a short utility javascript, which already make the table interactive/clickable... Click here for more info
group type="group" A container, which allows grouping of arbitrary HTML elements (as the ones mentioned above)
line A regular <hr> element will add a horizontal line and some spacing
heading class="sdpi-heading" Draws an horizontal line and additionally a nicely centered heading
meter type="meter" A horizontal meter, showing a value within a known range. More info (mozilla.org)
progress type="progress" A progressbar, typically showing the state of a certain progess (e.g. completion). More info (mozilla.org)
details The details element is a complete widget on it's own. It therefore occupies the full width of the PI's view by default. It can be used to create an interactive widget that the user can open and close. You can put any content into it.
message The message element is similar to the detailselement. It doesn't show an disclosure-triangle, but you can add some notification icons to it. It can be used to create an interactive widget that the user can open and close. You can put any content into it.

Note that only 'flat' sdpi-item structures are supported. That means, sdpi-item within another sdpi-item is unsupported (some exeptions apply, see Group).

Hint: If you plan to later add interactivity to your elements, it is recommended that you add an id to the element/value in question, so it is easier to later identify it's purpose.


Textfield

The simplest complete PI-view would be

<div class="sdpi-item">
    <div class="sdpi-item-label">Hello</div>
    <input class="sdpi-item-value" value="My name is StreamDeck">
</div>

Simplest Layout

Since you can use default HTML5 attributes (e.g. a placeholder attribute), you can quickly beef up the layout by duplicating the first item and adding a couple of placeholders.

<div class="sdpi-item">
    <div class="sdpi-item-label">Name</div>
    <input class="sdpi-item-value" value="" placeholder="Enter your name">
</div>
<div class="sdpi-item">
    <div class="sdpi-item-label">Email</div>
    <input class="sdpi-item-value" value="" placeholder="Enter your email-address">
</div>

Simple Layout

The required attribute

If you want to notify the user of a required value, you can simply add a required control, which shows a tiny exclamation mark, as long as there's no text in the field.

Required

<div type="textarea" class="sdpi-item" id="required_text">
    <div class="sdpi-item-label">Some Text</div>
    <span class="sdpi-item-value">
        <input type="text" required></input>
    </span>
</div>

Once the required-condition is fulfilled, a tiny ok-icon is shown:

Required OK

The pattern attribute:

You can use the pattern attribute to specify a pattern to validate the IP-Address.

<div class="sdpi-item" id="your_name" title="This field lets you enter an IP-Adress. The little exclamation mark changes to a checkmark, if the condition is met (e.g. you entered an IP-address.).">
    <div class="sdpi-item-label">IP-Address</div>
    <input class="sdpi-item-value" value="" placeholder="e.g. 192.168.61.1" required pattern="\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}">
</div>

Note: This is not a good validation, but one that - at least - hints the user, he entered a potentially valid value.

There are many patterns available, which you can just add and they will trigger this quick validation. Here are some examples:

  • 8 characters or more: pattern=".{6,}"
  • 2-character country code (e.g. EN): pattern="[A-Za-z]{2}"
Pattern Description
pattern="\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}" IP-address
pattern=".{8,}" 8 characters or more
pattern="[A-Za-z]{2}" 2 character country code
pattern="[^'\x22]+" no single or double quotes
pattern="(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{4,}" at least 4 characters, 1 uppercase, 1 lowercase and 1 number

Textarea

If you need to enter some more text, you can easily add a textarea control, which allows for multi-line text.

<div type="textarea" class="sdpi-item" id="message_only">
    <div class="sdpi-item-label">Message</div>
    <span class="sdpi-item-value textarea">
        <textarea type="textarea"></textarea>
    </span>
</div>

Textarea

You can also add an little info-text to the bottom of the textarea by adding a label right after the textarea, and give the label a for attribute with the value of the textarea's id.

Textarea

<div type="textarea" class="sdpi-item" id="message_only">
    <div class="sdpi-item-label">Message</div>
    <span class="sdpi-item-value textarea">
        <textarea type="textarea" maxlength="50" id="txa1"></textarea>
        <label for="txa1" >0/50</label>
    </span>
</div>

Adding a little Javascript to PI's webview, can make this into a nice counter:

    document.querySelectorAll('textarea').forEach(e => {
    const maxl = e.getAttribute('maxlength');
    e.targets = document.querySelectorAll(`[for='${e.id}']`);
    if (e.targets.length) {
        let fn = () => {
            for(t of e.targets) {
                t.innerText = maxl ? `${e.value.length}/${maxl}` : `${e.value.length}`;
            }
        };
        fn();
        e.onkeyup = fn;
    }
});

Button

Adding a button is also as simple as adding a default HTML5 button as sdpi-item-value.

<div class="sdpi-item">
     <div class="sdpi-item-label">Button</div>
     <button class="sdpi-item-value">Click Me</button>
</div>

Button

Per default the button's width takes the full available width. We included a couple of utility-classes in sdpi.css to restrict width of a button. E.g class max20 limits the width to approx 20% of the total width:

<div class="sdpi-item">
    <div class="sdpi-item-label">Button</div>
    <button class="sdpi-item-value max20">Click Me</button>
</div>

Button

Multiple buttons

Placing two buttons side by side, will automatically arrange them nicely (by the limits of the available space).

2 Buttons

 <div class="sdpi-item">
     <div class="sdpi-item-label">Button</div>
     <button class="sdpi-item-value">Click Me</button>
     <button class="sdpi-item-value">Or Me</button>
</div>

This also works for other combination of elements:

Select & Button

<div class="sdpi-item">
     <div class="sdpi-item-label">Button</div>
     <select class="sdpi-item-value select">
            <option value="janedoe@example.com">Jane Doe</option>
            <option value="someguy@example.com">Some Guy</option>
        </select>
      <button class="sdpi-item-value">Or Me</button>
</div>

Select

Creating a HTML5 select box with a label is straightforward:

Select

<div class="sdpi-item" id="select_single">
    <div class="sdpi-item-label">Select</div>
    <select class="sdpi-item-value select">
       <option value="20">20</option>
       <option value="50">50</option>
       <option value="100">100</option>
    </select>
</div>

HTML5 optgroups inside the select are supported as well:

Select with optgroups

<div class="sdpi-item" id="select_single">
    <div class="sdpi-item-label">Select</div>
    <select class="sdpi-item-value select">
       <optgroup label="Women">
           <option value="janedoe@example.com">Jane Doe</option>
           <option value="tinatank@example.com">Some Girl</option>
           <option value="yoko@example.com">Yoko Hama</option>
       </optgroup>
       <optgroup label="Men">
           <option value="johndoe@example.com">John Doe</option>
           <option value="tiptronic@example.com">Some Man</option>
           <option value="kingofprawns@example.com">Another Chap</option>
       </optgroup>
    </select>
</div>

Checkbox

Creating a HTML5 checkboxes with a label is getting a bit more involved, because there's no themeable checkbox available in today's browsers and HTML renderers. Creating checkboxes generally works the same way:

<div type="checkbox" class="sdpi-item">
    <div class="sdpi-item-label">Check Me</div>
    <div class="sdpi-item-value ">
        <input id="chk1" type="checkbox" value="left">
        <label for="chk1" class="sdpi-item-label"><span></span>left</label>
        <input id="chk2" type="checkbox" value="right">
        <label for="chk2" class="sdpi-item-label"><span></span>right</label>
    </div>
</div>

Checkbox

Please note the additional <span> element, which is used to override the browser's default checkbox. This is a common technique to allow drawing custom checkboxes, radio-buttons, etc...


Many checkboxes

If you need more than 2 or 3 checkboxes, alignment can get tricky and you end up with this:

<div type="checkbox" class="sdpi-item">
    <div class="sdpi-item-label">Check Me</div>
    <div class="sdpi-item-value ">
            <input id="chk1" type="checkbox" value="left">
            <label for="chk1" class="sdpi-item-label"><span></span>Monday</label>
            <input id="chk2" type="checkbox" value="right">
            <label for="chk2" class="sdpi-item-label"><span></span>Tuesday</label>
            <input id="chk3" type="checkbox" value="left">
            <label for="chk3" class="sdpi-item-label"><span></span>Wednesday</label>
            <input id="chk4" type="checkbox" value="right">
            <label for="chk4" class="sdpi-item-label"><span></span>Thursday</label>
            <input id="chk5" type="checkbox" value="left">
            <label for="chk5" class="sdpi-item-label"><span></span>Friday</label>
            <input id="chk6" type="checkbox" value="right">
            <label for="chk6" class="sdpi-item-label"><span></span>Saturday</label>
    </div>
</div>

Alignment

Alignment

That's why we added a simple helper-node sdpi-item-child, which you can use to group checkboxes/radiobuttons and their labels for easier alignment.

Please note the min100 class in the parent element sdpi-item-value min100, which means the subsequent sdpi-item-value elements will be at least 100px wide. Of course you can add your own helper css or even inline-styles to override this behaviour.

Anyway, using the sdpi-item-child class to 'group' input and label, will produce a much nicer output:

<div type="checkbox" class="sdpi-item" id="multi-items">
    <div class="sdpi-item-label">Select day</div>
    <div class="sdpi-item-value min100">
        <div class="sdpi-item-child">
            <input id="days1" type="checkbox" value="left">
            <label for="days1" class="sdpi-item-label"><span></span>Monday</label>
        </div>
        <div class="sdpi-item-child">
            <input id="days2" type="checkbox" value="right">
            <label for="days2" class="sdpi-item-label"><span></span>Tuesday</label>
        </div>
        <div class="sdpi-item-child">
            <input id="days3" type="checkbox" value="center">
            <label for="days3" class="sdpi-item-label"><span></span>Wednesday</label>
        </div>
        <div class="sdpi-item-child">
            <input id="days4" type="checkbox" value="red" checked>
            <label for="days4" class="sdpi-item-label"><span></span>Thursday</label>
        </div>
        <div class="sdpi-item-child">
            <input id="days5" type="checkbox" value="green">
            <label for="days5" class="sdpi-item-label"><span></span>Friday</label>
        </div>
        <div class="sdpi-item-child">
            <input id="days6" type="checkbox" value="blue">
            <label for="days6" class="sdpi-item-label"><span></span>Saturday</label>
        </div>
    </div>
    </div>

Checkboxes aligned


Radio

Creating an input of type radio follows the same logic as creating an input of type checkbox:

Radio

<div type="radio" class="sdpi-item" id="adjust_radio">
    <div class="sdpi-item-label">Adjust Radio</div>
    <div class="sdpi-item-value ">
        <span class="sdpi-item-child">
            <input id="rdio1" type="radio" name="rdio1" >
            <label for="rdio1" class="sdpi-item-label"><span></span>on</label>
        </span>
        <span class="sdpi-item-child">
            <input id="rdio2" type="radio" value="off" name="rdio2" checked>
            <label for="rdio2" class="sdpi-item-label"><span></span>off</label>
        </span>
        <span class="sdpi-item-child">
            <input id="rdio3" type="radio" value="mute" name="rdio3">
            <label for="rdio3" class="sdpi-item-label"><span></span>mute</label>
        </span>
    </div>
</div>

Range

Creating a slider (aka. HTML5 range control) is as easy as changing the 'type' attribute of the input. Just like on a plain HTML-page.

Range

<div type="range" class="sdpi-item" id="temperatureslider">
    <div class="sdpi-item-label">Temperature</div>
    <div class="sdpi-item-value">
        <input type="range" min="0" max="100" value=37>
    </div>
</div>

Btw. for simple control-items like this, you can avoid the additonal

<div class="sdpi-item-value">

and add the class directly to the input, like so:

<div type="range" class="sdpi-item" id="temperatureslider">
    <div class="sdpi-item-label">Temperature</div>
    <input type="range" class="sdpi-item-value" min="0" max="100" value=37>
</div>

Range with labels

To add labels to a range, however, a range must be grouped into an sdpi-item-value and the labels get added as simple span elements:

Range with datalist

<div type="range" class="sdpi-item" id="range_with_meters">
    <div class="sdpi-item-label">Range (with label)</div>
    <div class="sdpi-item-value">
        <span class="clickable">0</span>
        <input type="range" min="0" max="100" value=74>
        <span class="clickable">100</span>
    </div>
</div>

Note: In the PISamples-plugin a span of class clickable is intercepted and the value of it's 'range' element is set to the value specified in the 'value' attribute of the span element.

Range with datalist

Adding a datalist to a range produces steps. Pi's slider will snap to those steps.

Range with datalist and labels

<div type="range" class="sdpi-item" id="range_with_datalist">
    <div class="sdpi-item-label">Range (with datalist)</div>
    <div class="sdpi-item-value">
        <span class="clickable" value="0">0</span>
        <input type="range" min="0" max="100" value=74 list="numbers">
        <span class="clickable" value="100">100</span>
        <datalist id="numbers">
          <option>10</option>
          <option>40</option>
          <option>60</option>
          <option>75</option>
        </datalist>
    </div>
</div>

Color

Creating a color-selector is as easy as changing the 'type' attribute of the input. Just like on a plain HTML-page.

<div type="range" class="sdpi-item" id="temperatureslider">
    <div class="sdpi-item-label">Temperature</div>
    <div class="sdpi-item-value">
        <input type="color" value="#ff0000">
    </div>
</div>

Color

or the shorter version, which produces the same output:

<div type="color" class="sdpi-item" id="colorselection">
    <div class="sdpi-item-label">Color</div>
    <input type="color" class="sdpi-item-value" value="#ff0000">
</div>

Color with predefined color selection

To present some pre-defined color values, you can add a datalist to the color control. To make sure the list gets appended properly, set the list attribute of the input control to the id of the datalist. (in our example 'clrs')

Color

 <div type="color" class="sdpi-item" id="colorselection">
    <div class="sdpi-item-label">Color</div>
    <input type="color" class="sdpi-item-value" value="#3333cc" list="clrs" >
    <datalist id="clrs">
        <option>#ff0000</option>
        <option>#0000ff</option>
        <option>#00ff00</option>
        <option>#ffff00</option>
        <option>#3333cc</option>
        <option>#00ffff</option>
      </datalist>
</div>

Date/Time

There are various date and time controls supported, using Chrome's integrated date and time pickers.

Datepickers

Date

Showing a date-element is as easy as setting the type to 'date' and the value to the date to be shown.

Date

<div class="sdpi-item">
    <div class="sdpi-item-label">Date</div>
    <input class="sdpi-item-value" type="date" value="2019-01-15">
</div>

Datepicker

A user can change the values individually or, click an disclosure-triangle (which appears onhover) to show a date-picker control:

Datepicker

Here are a couple of more examples, how to show date and time.

Date range

Adding a min and a max value to the date-control, will allow the user to set a date in between these dates:

<div class="sdpi-item">
    <div class="sdpi-item-label">Date range</div>
    <input id="when" name="when" type="date" min="2018-12-01" max="2019-02-01" value="2018-12-31">
</div>

Datalist

You can present the user with a collection of pre-defined dates as well. Just add a <datalist> node to the sdpi-item. To make sure the list gets appended properly, set the list attribute of the input control to the id of the datalist. (in our example 'events')

Datepicker

<div class="sdpi-item" id="date">
    <div class="sdpi-item-label">Date</div>
    <input class="sdpi-item-value" type="date" value="yyyy-mm-dd" list="events">
    <datalist id="events">
        <option label="Elgato Live Stream">2019-01-06</option>
        <option label="Incredible event">2019-01-15</option>
        <option label="Palo Alto LAN party">2019-02-05</option>
        <option label="Something else">2019-12-31</option>
      </datalist>
</div>

Month

It is also possible to restrict the settable values to months:

Month

<div class="sdpi-item">
    <div class="sdpi-item-label">Month</div>
    <input class="sdpi-item-value" type="month" value="2019-01">
</div>

Month with pre-defined values

You can present the user with a collection of pre-defined months. Just add a <datalist> node to the sdpi-item. To make sure the list gets appended properly, set the list attribute of the input control to the id of the datalist. (in our example 'months')

<div class="sdpi-item" id="month">
    <div class="sdpi-item-label">Month</div>
    <input class="sdpi-item-value" type="month" value="2019-01" list="months">
    <datalist id="months">
        <option label="Neil on the moon">1967-07</option>
        <option label="First month of this century">2000-01</option>
        <option label="Last month of last century">1999-12</option>
        <option label="Last month of 2019">2019-12</option>
      </datalist>
</div>

Week

It is also possible to restrict the settable values to weeks:

Week

<div class="sdpi-item">
    <div class="sdpi-item-label">Week</div>
    <input class="sdpi-item-value" type="week" value="2019-W02">
</div>

Week with pre-defined data

You can present the user with a collection of pre-defined weeks. Just add a <datalist> node to the sdpi-item. To make sure the list gets appended properly, set the list attribute of the input control to the id of the datalist. (in our example 'weeks')

<div class="sdpi-item" id="week">
    <div class="sdpi-item-label">Week</div>
    <input class="sdpi-item-value" type="week" value="2019-W02" list="weeks">
    <datalist id="weeks">
        <option label="First week of 2019">2019-W01</option>
        <option label="Second week of 2019">2019-W02</option>
        <option label="24th week of 2019">2019-W24</option>
        <option label="Last week of 2019">2019-W52</option>
      </datalist>
</div>

DateTime control

A combined datetime control allows to set both date and time based on the user's current time-zone:

Week

<div class="sdpi-item">
    <div class="sdpi-item-label">Week</div>
    <input class="sdpi-item-value" type="week" value="2019-W02">
</div>

List

Using a list control, you can present the user a list of items. In our samle-plugin we show, how you can easily make them interactive/clickable by adding a class to the list. Supported classes are:

Class Description
no-select <optional> List just shows scrollable items
single-select List hilites the currently clicked/active item
multi-select Allows selecting multiple items

For single-select and multi-select a selected class is applied to the selected/clicked list-item, so it's easy to find it's state. Obviously you can also pre-select an item by adding a class selected when constructing the list.

List

Unordered List

<div type="list" class="sdpi-item list" id="unorderedListContainer">
    <div class="sdpi-item-label">List (unordered)</div>
    <ul class="sdpi-item-value no-select" id="unorderedList">
        <li>Milk</li>
        <li>Bread</li>
        <li>Chocolate</li>
        <li>More chocolate</li>
    </ul>
</div>

Ordered List

Ordered list

<div type="list" class="sdpi-item list" id="orderedListContainer">
    <div class="sdpi-item-label">List (ordered)</div>
    <ol class="sdpi-item-value" id="orderedList" type="none">
        <li>Milk</li>
        <li>Bread</li>
        <li>Chocolate</li>
        <li>More chocolate</li>
    </ol>
</div>
Property Description
none List just shows it's text
decimal Shows a decimal number before the row
decimal-leading-zero Shows a decimal number padded with a leading '0' before the row
lower-alpha Shows a lowercase alphabetical character before the row
upper-alpha Shows a uppercase alphabetical character before the row
lower-roman Shows a lowercase roman number before the row
upper-roman Shows a uppercase roman number before the row

Single Select

Single-select

<div type="list" class="sdpi-item list" id="orderedListContainer1">
    <div class="sdpi-item-label">List</div>
    <ol class="sdpi-item-value single-select" id="orderedList1" type="decimal">
        <li>Milk</li>
        <li>Bread</li>
        <li>Chocolate</li>
        <li>More chocolate</li>
    </ol>
</div>

Multi Select

Ordered list

<div type="list" class="sdpi-item list" id="orderedListContainer2">
    <div class="sdpi-item-label">Multi select</div>
    <ol class="sdpi-item-value multi-select" id="orderedList2" type="lower-alpha">
        <li>Coke</li>
        <li>Sprite</li>
        <li>Juice</li>
        <li>Water</li>
        <li>Mocktail</li>
        <li>Smoothies</li>
    </ol>
</div>

Tables

Using a table control, you can present the user a table of values.

Table

In our samle-plugin we show, how you can easily make them interactive/clickable by adding a class to the table. Supported classes are:

Class Description
no-select <optional> Table just shows scrollable items
single-select Table hilites the currently clicked/active item
multi-select Allows selecting multiple items. (no modifier key required)

For single-select and multi-select a selected class is applied to the selected/clicked table-item td, so it's easy to find it's state. Obviously you can also pre-select an item by adding a class selected when constructing the table.

 <div class="sdpi-item" id="tableid">
    <div class="sdpi-item-label">Table (no-select)</div>
    <table class="sdpi-item-value no-select" width="100%">
        <caption>Table without selection</caption>
        <tr>
            <td>One</td>
            <td>Two</td>
            <td>Three</td>
        </tr>
        <tr>
            <td>1.00</td>
            <td>2.00</td>
            <td>3.00</td>
        </tr>
        <tr>
            <td>west</td>
            <td>east</td>
            <td>south</td>
        </tr>
    </table>
</div>

Table with single selection

Table single-select

Creating a table which shows a single selection is as easy as adding a single-select class to the table:

<div class="sdpi-item" id="tableid1">
    <div class="sdpi-item-label">Table (single-select)</div>
    <table class="sdpi-item-value single-select" width="100%">
        <caption>Table with single selection</caption>
        <tr>
            <td>Four</td>
            <td>Five</td>
            <td>Six</td>
        </tr>
        <tr>
            <td>4.00</td>
            <td>5.00</td>
            <td>6.50</td>
        </tr>
        <tr>
            <td>west</td>
            <td>east</td>
            <td>south</td>
        </tr>
    </table>
</div>

Table with multi selection

Similarily creating a table which can show multiple selected cells just needs adding a multi-select class to the table:

Table multi-select

<div class="sdpi-item" id="tableid2">
    <div class="sdpi-item-label">Table (multi-select)</div>
    <table class="sdpi-item-value multi-select" width="100%">
        <caption>Table with multi selection</caption>
        <tr>
            <td>Seven</td>
            <td>Eight</td>
            <td>Nine</td>
        </tr>
        <tr>
            <td>7.00</td>
            <td>8.00</td>
            <td>9.99</td>
        </tr>
        <tr>
            <td>west</td>
            <td>east</td>
            <td>south</td>
        </tr>
    </table>
</div>

File

Original file-selector

To add a file-selector to the PI, you can just add an type of file and a class sdpi-item-value to the HTML. The integrated file-selector unfortunately returns a 'fakepath' as it's value (and shows it in it's label), so this selector is basically only of any use, if you want to read the contents of the file and pass it to your plugin. You will need to write your own code for that.

File-Selector original

<div class="sdpi-item" id="my_private_file_selectorx">
    <div class="sdpi-item-label">Select File</div>
    <input class="sdpi-item-value" type="file" id="elgfilepicker" accept=".jpg, .jpeg, .png">
</div>

Property inspector's file-selector

Most of the time, however, you will want to pass a file-path back to your plugin. That's why we added some custom elements and styling to the file-selector.

SDPI File-Selector

<div class="sdpi-item" id="my_private_file_selector">
    <div class="sdpi-item-label">Select File</div>
    <div class="sdpi-item-group file" id="filepickergroup">
        <input class="sdpi-item-value" type="file" id="elgfilepicker" accept=".jpg, .jpeg, .png">
        <label class="sdpi-file-info " for="elgfilepicker">no file...</label>
        <label class="sdpi-file-label" for="elgfilepicker">Choose file...</label>
    </div>
</div>

Here we add to label elements to the DOM, whose 'for'-attribute contains the reference to the ìnputelement (in the example aboveelgfilepicker`).

spdi-file-info will receive the file-name sdpi-file-label replaces the original 'Choose file...' button.

Clicking both elements will trigger and show the file-selector-dialog. To ensure proper styling, just group those elements in a sdpi-item-group and add a class file. The included sample-code will fill the elements as needed. If you don't need to show the file-name, you can just leave the sdpi-file-info out. Only the sdpi-file-labelis required, since it triggers the file-dialog.

The file-selector returns an URLencoded absolute filepath. (e.g. %2FUsers%2Fandy%2FDesktop%2Fmarina.png). To convert this back to a javascript string, you must decode this string first using decodeURIComponent.

Please note: Chromium adds (for security reasons) a 'fake' string C:\\fakepath\\, so you must strip this fakepath to get to the 'real' path, like so:

if (element.type === 'file') {
    result = decodeURIComponent(element.value.replace(/^C:\\fakepath\\/, ''));
}

Group

Sometimes you will want to group some inputs or other elements to visually group them together. For this case, there's a sdpi-item-group element, which allows grouping of arbitrary elements.

As with any other sdpi-item, a group can have a label. The main difference to a regular sdpi-item-label is that the group's label is left-aligned and can fill the whole space above a group (e.g. it can be a longer and more descriptive string).

The structure of a group is similar to a regular sdpi-item, with the exeption, the group's value is the actual sdpi-item-group, which - in turn - contains any regular sdpi-item (just as shown above)

Group

<div type="group" class="sdpi-item" id="messagegroup">
    <div class="sdpi-item-label">Send A Message</div>
    <div class="sdpi-item-group" id="messagegroup_items">
        <div type="select" class="sdpi-item" id="messagegroup_account">
            <div class="sdpi-item-label">Account ID</div>
            <select class="sdpi-item-value select">
                <option value="janedoe@example.com">Jane Doe</option>
                <option value="someguy@example.com">Some Guy</option>
                <option value="anotherchap@example.com">Another Chap</option>
            </select>
        </div>
        <div type="textarea" class="sdpi-item" id="messagegroup_message">
            <div class="sdpi-item-label">Message</div>
            <div class="sdpi-item-value textarea">
                <textarea type="textarea"></textarea>
            </div>
        </div>
    </div>
</div>

Line

You can draw a horizontal line using HTML5's <hr> element. This just draws a line from left to right over the full width of PI's viewable pane (minus some margin). This can be helpful to separate different inspector elements or groups.

<hr>

Line


Heading

A heading lets you place a nice heading above (or below) an sdpi-item. It is meant as an optical separation between sdpi-items.

Heading

To add an heading, simply place a simple div with a class sdpi-heading outside an sdpi-item:

<div class="sdpi-heading">MY HEADLINE</div>

Meter

A horizontal meter.

Meter

<div class="sdpi-item" id="your_name">
    <div class="sdpi-item-label">Meter</div>
    <meter class="sdpi-item-value" value="0.7"></meter>
</div>

Multiple meters

Like other controls, you can easily add multiple meters to the output.

Please note: Since Meter controls are meant 'read-only', you should give every meter node it's own class of sdpi-item-value (especially, if you want to later make them interactive - e.g. clickable individually).

Multiple Meters

<div class="sdpi-item" type="progress" id="multimeter">
    <div class="sdpi-item-label">Meter</div>
    <meter class="sdpi-item-value" value="0.6"></meter>
    <meter class="sdpi-item-value" value="0.2"></meter>
    <meter class="sdpi-item-value" value="0.8"></meter>
</div>

Full width meters

To stack meter-controls vertically and apply full width, we included a helper-class 'full' in the sdpi.css, which will produce full-width controls:

Meter full width

<div class="sdpi-item" id="meter_full">
    <div class="sdpi-item-label">Meter</div>
    <div class="sdpi-item-value sdpi-item-group" id="metergroup">
        <meter class="sdpi-item-value full" value="0.4"></meter>
        <meter class="sdpi-item-value full" value="0.2"></meter>
        <meter class="sdpi-item-value full" value="0.7"></meter>
    </div>
 </div>

Meters with labels

Adding some labels to a meter requires grouping the meters in a separate child-node containing the left label, the meter and the right label, which then will produce an output similar to this:

Meter with labels

<div class="sdpi-item" type="meter" id="meter_with_labels">
    <div class="sdpi-item-label">Meter</div>
    <div class="sdpi-item-value" id="metergroup">
        <div class="sdpi-item-child">
            <span for="meter1">0</span>
            <meter id="meter1" class="sdpi-item-value full" value="0.4"></meter>
            <span for="meter1">100</span>
        </div>
        <div class="sdpi-item-child">
            <span>-17</span>
            <meter class="sdpi-item-value full" value="0.2"></meter>
            <span>37.2</span>
        </div>
        <div class="sdpi-item-child">
            <span>Min</span>
            <meter class="sdpi-item-value full" value="0.7"></meter>
            <span>Max</span>
        </div>
    </div>
 </div>

Hint: Our included PISamples plugin shows how to make these (and other controls) clickable easily. Just take a look or copy the code from there.


Progress

A progress-element is similar to the meter-element, but has a slightly different visual output.

Progress

 <div class="sdpi-item" type="progress" id="progress">
    <div class="sdpi-item-label">Progress</div>
    <progress class="sdpi-item-value" value="0.6" max="1"></progress>
</div>

Note: For the progress element you need to set the max attribute, otherwise a value from 0..1 is assumed.

Multiple progresses

Like other controls, you can easily add multiple progresses to the output.

Please note: Since Progress controls are meant 'read-only', you should give every meter node it's own class of sdpi-item-value (especially, if you want to later make them interactive - e.g. clickable individually).

Multiple Progresses

<div class="sdpi-item" type="progress" id="multiprogress">
    <div class="sdpi-item-label">Progress</div>
    <progress class="sdpi-item-value" value="0.6" max="1"></progress>
    <progress class="sdpi-item-value" value="0.2" max="1"></progress>
    <progress class="sdpi-item-value" value="0.8" max="1"></progress>
</div>

Full width meters

To stack progress-controls vertically and apply full width, we included a helper-class 'full' in the sdpi.css, which will produce full-width controls:

Progress full width

<div class="sdpi-item" type="progress" id="progress_full">
    <div class="sdpi-item-label">Progress (full)</div>
    <div class="sdpi-item-value sdpi-item-group" id="metergroup">
        <progress class="sdpi-item-value full" value="0.4" max="1"></progress>
        <progress class="sdpi-item-value full" value="0.2" max="1"></progress>
        <progress class="sdpi-item-value full" value="0.7" max="1"></progress>
    </div>
 </div>

Progress with labels

Adding some labels to a meter requires grouping the meters in a separate child-node containing the left label, the meter and the right label, which then will produce an output similar to this:

Progress with labels

<div class="sdpi-item" type="progress" id="progress_with_labels">
    <div class="sdpi-item-label">Progress <br>(with labels)</div>
    <div class="sdpi-item-value sdpi-item-group" id="progressgroup">
        <div class="sdpi-item-child">
            <span for="progress1">0</span>
            <progress id="progress1" class="sdpi-item-value full" value="40" max="100"></progress>
            <span for="progress1">100</span>
        </div>
        <div class="sdpi-item-child">
            <span>0</span>
            <progress class="sdpi-item-value full" value="25" max="37.2"></progress>
            <span>37.2</span>
        </div>
        <div class="sdpi-item-child">
            <span>Min</span>
            <progress class="sdpi-item-value full" value="70" max="200"></progress>
            <span>Max</span>
        </div>
    </div>
 </div>

Hint: Our included PISamples plugin shows how to make these (and other controls) clickable easily. Just take a look or copy the code from there.


Details

This element's content can get shown or collapsed (much like an accordion). It can contain a summary and/or additional text or html-elements.

<details>
    <summary>More Info</summary>
    <p>Put some text here.</p>
    <h4>Create Headlines</h4>
    <p>Whatever you like</p>
</details>

Details

A details element's default state is 'closed'. In this state it only shows a disclosure triangle. Clicking the disclosure triangle will (obviously) disclose the details of the element:

Details content

The details element is a kind of 'widget' on it's own, in that it can contain arbitrary other HTML-elements, which are disclosed as soon as the disclosure-triangle is clicked.

You can wrap the details-item into a regular sdpi-item like so:

<div class="sdpi-item">
<div class="sdpi-item-label">Details</div>
<details class="sdpi-item-value">
    <summary>Some Details here</summary>
    <p>Here are some details.</p>
    ...
</details>
</div>

Details wrapped

but it will also work on it's own:

<details>
    <summary>More Info</summary>
    <p>This is Stream Deck's Property Inspector.</p>
    <p>To find out how things work, just open the 'index_pi.html' included in this plugin and compare to what's shown in StreamDeck</p>
    <a href="#">More info...</a>
</details>

Details

That's because you most likely want to show the user some information about your plugin (or it's usage). Allowing the details element to flow accross the full width of PI's view pane, will give you some more space to present your information.


Message

The messageelement is similar to the details. It allows you to show a bolder message to your users:

Details

Message icons

Depending on the significance of the message, you can also add an icon to the message, by just apending a class to the message class:

Details

<details class="message question">
    <summary>This is some message</summary>
</details>

Supported icons types are:

Class Description
info Details
caution Details
question Details

This element's content can get shown or collapsed. It can contain a summary and/or additional text or html-elements.

Message content

A message element's default state is 'closed'. In this state it only shows the message in summary. If the message element contains further information, you can click the summary to disclose this information.

Details

<details class="message">
    <summary>This is some message</summary>
    <h4>Information:</h4>
    <p>In this area you can type some information to your user.</p>
    <a class="info" href="#">Click here</a>
</details>

Hint: If you add an anchor element to the HTML, you can add one of the supported icons here as well...


Interactivity

In order to gain full interactivity from Property Inspector to the plugin, the Property Inspector must be able to send messages to the plugin.

Since the PI is just a regular webpage, you can include common scripting techniques to communicate from Property Inspector to Stream Deck to Plugin and vice versa.

Registration

Once the Property Inspector is instantiated (and thus it's webpage is loaded), the Stream Deck application sends a connectSocket message to it, which contains various information needed to communicate between the Property Inspector and the Stream Deck software.

function connectSocket(inPort, inPropertyInspectorUUID, inRegisterEvent, inInfo, inActionInfo)

Please note that the Property Inspector is instantiated every time it is opened/shown.

Using the connectSocket's inPort parameter, you are now able to establish a proper Websocket-communication with Stream Deck software using a standard HTML5 WebSocket instance:

websocket = new WebSocket('ws://localhost:' + inPort);

Now the Property Inspector registers itself with Stream Deck software, like so:

websocket.onopen = function () {
    var json = {
        "event": inRegisterEvent,
        "uuid": inPropertyInspectorUUID
    };

    websocket.send(JSON.stringify(json));
};

The Property Inspector will receive messages through the Websocket's onmessage event:

websocket.onmessage = function (evt) {
    ... do something with the messages
}

More details about this procedure is found here: Property Inspector registration

Sending Messages to a plugin

After the connection from the Property Inspector to the Stream Deck application is established, you are able to send messages to your plugin. The Stream Deck software expects a stringified JSON structure to be able to pass the message to the proper plugin:

const json = {
    "action": "com.example.tutorial.action1",
    "event": "sendToPlugin",
    "context": <opaqueValue>, // as received from the 'connectSocket' event
    "payload": {}
};

websocket.send(JSON.stringify(json));

The JSON's payload can be anything you like, as long as it is possible to send it as string through a websocket (Your plugin is responsible to pickup the data and do something with it).

You will find more details about the message and message-format here: Property Inspector Events

PI lifecycle events

When the Property Inspector closes, standard HTML5 lifecycle events are sent. You can add a listener to these events, e.g. to signal your plugin, the PI is no longer available and thereby you don't need to send further data to it. This is good practice to avoid unnessesarry traffic on the websocket.

To subscribe to these events, you may install an event listener on the PI window. When you receive one of these events, you can signal your plugin, the PI will be closed.

Here are some examples (not you only need one of these):

beforeunload-event

window.addEventListener('beforeunload', function (e) {
    e.preventDefault();
    sendValueToPlugin('propertyInspectorWillDisappear', 'property_inspector');
    // Don't set a returnValue to the event, otherwise Chromium with throw an error.
});

pagehide-event

/** the pagehide event is fired, when the view disappears */

window.addEventListener('pagehide', function (event) {
    sendValueToPlugin('propertyInspectorPagehide', 'property_inspector');

});

unload-event

/** the unload event is fired, when the PI will finally disappears */
window.addEventListener('unload', function (event) {
    sendValueToPlugin('propertyInspectorDisconnected', 'property_inspector');
});

Sample Property Inspector

With these bits of information, we are now able to create a working Property Inspector quickly.

First of all, we need an input control to enter some data. For the sake of this example, we use a simple HTML select, to send some values to the plugin.

According to the documentation above, we add a simple select control to Property Inspector's sdpi-wrapper:

<div class="sdpi-wrapper">

    <div type="select" class="sdpi-item">
        <div class="sdpi-item-label">Change Value</div>
        <select class="sdpi-item-value" onchange="sendValueToPlugin(event.target.value, 'myIdentifier')">
            <option selected value="0">0</option>
            <option value="1">1</option>
            <option value="2">2</option>
            <option value="3">3</option>
            <option value="4">4</option>
            <option value="5">5</option>
            <option value="6">6</option>
            <option value="7">7</option>
            <option value="8">8</option>
            <option value="9">9</option>
        </select>
    </div>
 </div>

Note the sendValueToPlugin call on the select's onChange event (which is fired, every time the value of the select changes). It adds 2 parameters:

  • the value of the event's target (in this case the select)
  • and a custom identifier (which can be anything you like)
Parameter Description
value the value of the event's target (in this case the select)
identifier a custom identifier (which can be anything you like). This value will get the keyof our value, so the plugin's receiving function can identify what the value is supposed to do. Note in our example, we could simply pass the value with an arbitrary key, because there's only one value sent, but as soon as there are more controls involved, the plugin needs to know, which of the control's value is being sent. So it seems to be a good practice to use this strategy even for simple setups.

With this information, we are now able to write a simple function, which handles the reception of the onChange event and passes it's value on to Stream Deck (and therby to the plugin):

 function sendValueToPlugin(value, param) {

    // say the websocket connection is saved in the variable 'websocket'
    if (websocket) {
        // compile our JSON object.

        const json = {
                "action": "com.example.tutorial.action1",
                "event": "sendToPlugin",
                "context": <opaqueValue>, // as received from the 'connectSocket' callback
                "payload": {
                    // here we can use ES6 object-literals to use the  'param' parameter as a JSON key. In our example this resolves to {'myIdentifier': <value>}
                    [param] : value;  
                }
         };
         // send the json object to the plugin
         // please remember to 'stringify' the object first, since the websocket
         // just sends plain strings.
         websocket.send(JSON.stringify(json));
    }

}

Putting the above HTML/Javascript together already makes up a nice and functional Property Inspector.

One missing part is the included styling contained in sdpi.css which is included in the samples and can quickly be added to the HTML's <head> section:

<head>
    <meta charset="utf-8" />
    <title>com.elgato.sample PI</title>
    <link rel="stylesheet" href="sdpi.css">
</head>

Now we have all needed elements set, to try out our first custom Property Inspector. Here's the complete HTML:

<!DOCTYPE html>
<html>

<head>
    <meta charset="utf-8" />
    <title>com.elgato.sample PI</title>
    <link rel="stylesheet" href="sdpi.css">
</head>

<body>
    <div class="sdpi-wrapper">
        <div type="select" class="sdpi-item">
            <div class="sdpi-item-label">Change Value</div>
            <select class="sdpi-item-value" onchange="sendValueToPlugin(event.target.value, 'myIdentifier')">
                <option selected value="0">0</option>
                <option value="1">1</option>
                <option value="2">2</option>
                <option value="3">3</option>
                <option value="4">4</option>
                <option value="5">5</option>
                <option value="6">6</option>
                <option value="7">7</option>
                <option value="8">8</option>
                <option value="9">9</option>
            </select>
        </div>
     </div>
     <script>
        // this is our global websocket, used to communicate from/to Stream Deck software
        // and some info about our plugin, as sent by Stream Deck software
        var websocket = null,
        uuid = null,
        actionInfo = {};

        function connectSocket(inPort, inUUID, inRegisterEvent, inInfo, inActionInfo) {
            uuid = inUUID;
            // please note: the incoming arguments are of type STRING, so 
            // in case of the inActionInfo, we must parse it into JSON first
            actionInfo = JSON.parse(inActionInfo); // cache the info
            websocket = new WebSocket('ws://localhost:' + inPort);

            // if connection was established, the websocket sends
            // an 'onopen' event, where we need to register our PI
            websocket.onopen = function () {
                var json = {
                    event:  inRegisterEvent,
                    uuid:   inUUID
                };
                // register property inspector to Stream Deck
                websocket.send(JSON.stringify(json));
            }
        }

        // our method to pass values to the plugin
        function sendValueToPlugin(value, param) {
            if (websocket) {
                const json = {
                        "action": actionInfo['action'],
                        "event": "sendToPlugin",
                        "context": uuid, 
                        "payload": {
                            [param] : value
                        }
                 };
                 websocket.send(JSON.stringify(json));
            }
        }
     </script>
</body>
</html>

Debugging

To debug your Property Inspector, you can use Chrome's developer tools. By default you will find the remote-debugger at http://localhost:23654/.

For more information how to activate debugging, please follow the 'debugging' section here: Create your own plugin

Once activated our freshly created Property Inspector will look something like this:

Debugging