Settings and presets
Declare user-facing settings fields, reuse the shared presets, and read and react to values with dd.settings.
Settings are how users make a widget theirs. You declare fields in the
manifest, the host renders them in the widget's settings dialog, and your
code reads the values and reacts when they change. The division of labor is
strict and worth internalizing up front: widgets read settings, the host
writes them. There is no dd.settings.set; the dialog is the only writer.
That is a deliberate boundary, since several field types carry user consent.
A widget can still ask the host to open that dialog from a click, and even
point at one field, with
dd.settings.open().
Declaring fields
Each entry in the manifest's settings array declares one field:
"settings": [
{ "key": "city", "type": "text", "label": "City", "default": "" },
{ "key": "units", "type": "select", "label": "Units", "default": "celsius", "options": ["celsius", "fahrenheit"] },
{ "key": "refreshMin", "type": "number", "label": "Refresh (minutes)", "default": 30, "min": 10, "max": 180 }
]That is the Weather widget's real block. A quick tour of the types:
text,number(with optionalmin/max),boolean, andselect(a dropdown overoptions; with nodefaultthe first option wins) cover most needs.dateandtimegive the user the native calendar and clock pickers. Adatereads back as"YYYY-MM-DD", atimeas"HH:MM", and either is""while unset. Turn a date into a localDatewithdd.dates.fromDayKey(nullwhile unset); never pass the string tonew Date(), which parses it as UTC and lands a day early west of Greenwich. Both are app 0.3.4+.listgives the user text rows they add, reorder and remove, and your code astring[]. Use it when the widget shows one thing per entry, such as a world clock's cities: the order of the rows is the order you render. The value is always an array,[]before they add anything, and adefaultarray seeds it.maxcaps the rows. App 0.3.7+.colorgives the user a color picker. Before reaching for it, check whether the sharedaccentColorpreset below fits; a lone custom color that ignores the theme usually looks wrong.placeholderadds hint text to empty inputs, andpickadds a Browse button beside afilePathfield. Both are app 0.1.10+.showIfhides a field until a sibling setting has a given value, so a dependent option only appears when it can matter (app 0.2.6+):
{ "key": "autoCollapse", "type": "boolean", "label": "Collapse after launch",
"default": false, "showIf": { "key": "collapsible", "equals": true } }Three types are different in kind: secretRef, filePath, and instances
are consent-bearing. Their value grants access to something the user
owns: a stored credential, a file on disk, other widgets. They never inherit
a manifest default, only a value the user actively picked counts, and your
code never sees the underlying secret or path contents directly; the value
works as a key you pass back to APIs like dd.files.read, dd.image.load
or the
$secret mechanism.
The full field tables are in
the manifest reference. A bar manifest
may not declare these three types at all; see
the taskbars guide.
In the app's
widget settings dialog every secretRef dropdown carries a + button that
opens Settings > Secrets with a suggested name prefilled, and the new secret
selects itself when the user returns; labels don't need to explain the vault
anymore.
Parts the user can turn off
Most widgets have pieces some people do not want: the seconds on a clock,
the vitals on a taskbar, a row of buttons. Make each one a part. Add
part to the boolean that switches it, and mark the elements:
{ "key": "showClock", "type": "boolean", "label": "Clock", "default": true, "part": "clock" }<div class="clock" data-dd-part="clock">…</div>That is all. The SDK hides every data-dd-part="clock" element while
showClock is false, live while the user tries the switch in the dialog, and
the dialog lists your parts first, in a Show group. You write no class
toggle and no CSS rule. App 0.4.5+.
A few details:
- The value stays an ordinary setting. Your code reads it like any other,
showIfcan point at it, and values your users already saved carry over when you addpartto a field you already ship. - A part needs no marked element. When your code shows a row only if the
setting is on and there is data for it (a battery row on a desktop with
no battery), keep that logic and add
partjust for the Show group. <html>carriesdata-dd-parts-hidden="clock vitals"while parts are off, for CSS that closes the gap::root[data-dd-parts-hidden~="clock"] .row.- Mark elements in your own document. The hiding stylesheet does not reach
into a shadow root (a
dd-*element itself is fine to mark), and your owndisplay: … !importanton a marked element wins over it. - The
pins,systrayandnotificationCenterpresets join the Show group too; their components hide themselves.
Hidden items: what users hide on their own
Users can also hide elements you never marked. Edit widget... in the widget menu (Edit taskbar... in the taskbar menu) outlines the element under the cursor, and a click hides it. The SDK builds a CSS selector for that element and hides every match from then on; the settings dialog lists the hidden items with a Show button each. You write nothing for this, but you decide how well the picks survive your next update. App 0.4.5+.
- Give ids to the static elements a user may want gone. A unique id is the strongest anchor.
- Load the SDK script at the end of
<body>, before your own script. The SDK records the markup as written when it loads, and trusts positions in that markup only. - Put a
data-keyon the items of a list you build. Without a key, one whole item of a live list cannot be hidden (its place changes with the data), but a piece of an item still can: it hides in every item. - A click on an element you marked as a part turns that part off, so a part stays the better tool for a toggle you design: it has a name in the Show group and your code can react to it.
Presets: shared fields, shared behavior
Some settings recur in almost every widget, so they ship as named presets you mix into the same array:
"settings": [
{ "key": "showSeconds", "type": "boolean", "label": "Show seconds", "default": true },
"panelStyle",
"accentColor"
]-
panelStyleexpands to a "Panel style" select (none/solid/transparent/image, defaultsolid, stored under the keypanel), plus, from app 0.2.4, a "Background image" file picker (keypanelImage) and an "Image fit" select (keypanelImageFit,covercrops,containletterboxes onto the surface color; nothing stretches). The two image fields show in the settings dialog only while the style sits onimage. Putsettings="panelStyle"on yourdd-paneland the component applies all of it itself, loading the image through dd.image.load; you write zero code:<dd-panel id="panel" settings="panelStyle"> ... </dd-panel>One seam to know: the style and fit live-preview in the settings dialog, but the image itself updates on Save.
-
accentColorexpands to a "Use theme accent color" toggle (keyuseAccent, default on) plus a "Custom color" picker (keycolor). This one has no component-side auto-apply; resolve it in code withdd.theme.accent(settings), ordd.theme.bindAccent(cb)to stay reactive to both settings and theme changes. -
systrayandpinsare the bar presets (akind: "bar"widget, app 0.3.0+): a "Show the system tray" toggle (keyshowTray) that<dd-tray-caret settings="systray">applies, and a "Show pinned apps" toggle (keyshowPins) that<dd-apps settings="pins">applies. See the component reference. -
weekStart(app 0.3.4+) expands to a "Week starts on" select (monday/sunday, keyweekStart). No component applies it; hand the value to dd.dates.startOfWeek, which takes the string as stored. -
textScale(app 0.3.4+) expands to a "Text size" select (tiny/small/medium/large/huge, keytextSize). Mirror the value onto the body (document.body.dataset.textScale = value) and the base stylesheet turns it into the--dd-text-scalemultiplier, default 1. The autoscale skeleton rides it automatically; type sized by your own viewport units multiplies it in:calc(min(19vw, 52vh) * var(--dd-text-scale, 1)). Skip it when the type is measure-fitted with dd.ui.autofit: there the tile itself is the size knob.
Want a preset's behavior with a different default? Declare the field inline
with the same key and your default, and keep the preset name on the
component; it keys off the setting value regardless of how the field was
declared. Declare every field the preset carries, not just the one you are
changing, and do not also list the preset name in the manifest: expansion
appends without deduplicating, so shared fields would appear twice. Going
inline also means later additions to the preset skip your widget until you
copy them in.
Reading values
The default idiom is dd.settings.bind: it calls your callback immediately
with the current values and subscribes to changes in one step, so a change
landing between a one-shot read and a later subscription cannot be missed.
The Weather widget wires its whole settings story in a few lines:
let settings = {};
await dd.settings.bind((next) => {
settings = next; // repaint; the refetch is dd.poll's job, see below
});The values you receive are the effective settings: manifest defaults merged
with whatever the user changed (and for consent types, only what the user
picked). If you prefer the explicit pair, dd.settings.get() returns a
snapshot and dd.settings.onChange(cb) subscribes; bind is just both in
the right order.
Live preview
While the settings dialog is open, the host live-previews edits: your
callback fires with the not-yet-saved values as the user tries them, and if
they dismiss without saving, it fires once more with the stored values. Do
not try to distinguish preview events from real ones; treat every callback
the same and re-render from the values you were handed. That is what makes
the preview feel instant, and it costs you nothing since your code path is
identical. (dd.settings.get() always returns the persisted state, if you
ever need it.)
The Style tab next to your settings previews the same way, over
theme.tokens instead: the user's per-widget token overrides stream live
and revert on dismiss. There is nothing to declare; every token your CSS
uses is already covered.
Reacting to changes
What "react" means depends on the setting:
-
Values that feed a fetch: name them in your
dd.pollcall'srefreshOn, and leave the settings callback to repainting. Do NOT callrefresh()from the callback: the live preview below means one change per keystroke, so that re-fetches once per character of whatever the user is typing.refreshOnwaits for the values to rest and polls once. -
Pure display toggles: if a toggle only shows or hides elements, make it a part and there is nothing to react to. Otherwise re-render, or bind the value with a signal and let the effect update the element.
-
panelStyle: nothing, the panel handles itself. -
accentColor:dd.theme.bindAccent. System Controls, for example, tints its whole icon bar by re-pointing one token instead of coloring elements:await dd.theme.bindAccent((color) => { iconbarEl.style.setProperty("--dd-text", color); });
Reading settings needs no permission, and every widget gets the settings dialog for free; you never build the form UI yourself.
Writing the manifest
What goes in manifest.json: identity, API and app version floors, the entry document, sizing on the grid, and the schema line that gives you autocompletion.
Interactivity and input
How mouse input reaches a widget: the interactive flag, capture regions marked in the DOM, hover events, and what happens when widgets overlap.