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.
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.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 need"minAppVersion": "0.1.10".
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 or the
$secret mechanism.
The full field tables are in
the manifest reference.
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, defaultsolid, stored under the keypanel). Putsettings="panelStyle"on yourdd-paneland the component applies it itself; you write zero code:<dd-panel id="panel" settings="panelStyle"> ... </dd-panel> -
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.
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.
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 = {};
let poller = null;
await dd.settings.bind((next) => {
settings = next;
poller?.refresh(); // re-poll now with the new city and units
});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.)
Reacting to changes
What "react" means depends on the setting:
-
Values that feed a fetch: call
refresh()on yourdd.pollhandle, as above. -
Pure display toggles: just 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.