Reference

Component reference

The dd-* elements with their attributes, parts, slots, presets, and override points.

The house style ships as Web Components: custom elements that come pre-styled from the design tokens and react to theme changes for free. Nothing to import; linking the SDK registers every dd-* element for the document.

Each element renders into a Shadow DOM, styled by the same shared stylesheet raw-class widgets use, so a theme change (which rewrites the --dd-* tokens on :root) restyles component internals automatically. Your own CSS cannot reach inside with ordinary selectors; use the exposed hooks instead: ::part(name), the documented custom-property knobs, and named slots.

Unless a component says otherwise, it is presentational: clicks bubble from the host element and you wire them yourself; nothing acts on its own.

dd-text

Typography. Content is projected; the host carries the font and color.

<dd-text variant="title">Tasks</dd-text>
<dd-text variant="muted">3 open</dd-text>
<dd-text size="xl" weight="bold">42</dd-text>

variant picks a semantic default (size, weight, color):

variantUse
body (default)plain body text
titlepanel or section heading (semibold, large, display face)
subtitlesecondary heading (muted, medium)
labeluppercase tracked label
mutedsecondary or status text
valuebig readout (semibold, tabular, display face; add mono for metrics)

size (xs to 2xl), weight (normal, medium, semibold, bold) and the boolean mono override or extend the variant; they map straight onto the type scale tokens. The attribute values are the complete vocabulary: anything else does nothing. For a truly special size, put a plain font-size on the element; an id or class selector beats the variant default.

dd-icon

<dd-icon name="plus"></dd-icon>               <!-- built-in glyph, tinted -->
<dd-icon src="icons/rain.svg"></dd-icon>      <!-- your SVG, full color -->
<dd-icon src="icons/rain.svg" tint></dd-icon> <!-- your SVG, masked to the text color -->
  • name picks a built-in glyph, drawn as a currentColor mask so it follows the text color and themes. The full set: battery, check, chevron-down, chevron-up, close, gear, lock, pause, play, plus, power, refresh, search, sign-out, skip-back, skip-forward, sleep, volume, volume-mute, wifi. Names are validated (lowercase, digits, hyphens); anything else is ignored.
  • src renders an SVG file from your widget folder (or a data: URI) as a real image, so the SVG's own colors show. Add tint to mask it in the current text color instead. The image carries part img.

Size via --dd-icon-size (default 1.25em). Swap at runtime with icon.setAttribute("src", ...) or bind it: create("dd-icon", { src: () => ... }).

There is no external-sprite mode (one file, many icons by name); ship one SVG per icon, or build stateful art inline with dd.ui.create.

dd-panel

The bordered card that fills the widget and stacks its children.

<dd-panel heading="Weather" settings="panelStyle">
  <button slot="actions" class="dd-icon-btn">...</button>
  <div class="dd-scroll">...</div>
</dd-panel>
AttributeEffect
heading="..."render a title row. Use heading, not title (the native tooltip)
transparentglassy surface instead of solid
bareno paint at all: the card keeps its box (padding, layout) but drops background and border
rowlay children horizontally
settings="panelStyle"auto-wire the panel-style preset (see below)
SlotFor
(default)the panel body
headinga custom or dynamic heading, instead of the attribute
actionscontrols pinned to the right of the header
PartFor
surfacethe card itself (override padding, layout, border)
headerthe heading row
headingthe heading text

Knob: --dd-panel-pad sets the surface padding (default var(--dd-space-3) var(--dd-space-4)). The header row renders only when there is a heading attribute or slotted heading/actions content; an empty header hides itself.

/* the surface lives in the shadow, so reach it via the part */
#panel::part(surface) { flex-direction: row; gap: 4vw; padding: 0; }

dd-bar

A bar segment, for kind: "bar" (taskbar) widgets only. Wrap the bar's visible content in one or more of these: each segment's box is the area that takes the mouse, and bar-window space outside every segment clicks through to the desktop or the maximized window beneath. Items may animate between segments; the SDK follows the boxes automatically.

<dd-bar place="center" reach="35">
  <dd-panel row id="dock">...</dd-panel>
</dd-bar>
AttributeEffect
edgeleft or right (absent = bottom). The screen edge the bar window docks to; set it from ctx.bar.edge. The host fixes to that edge and lays out along it, as a column on a side edge.
placewhere along the edge. Bottom: left, center, right or fill (default). Side edges: top, center, bottom or fill. The first three shrink-wrap the content, fill spans the edge.
reach="35"extra interactive headroom on the inner side of the content (above a bottom bar, beside a side bar), in CSS px, inside the capture box. Hover and magnify animations that rise past the surface stay hoverable in it.
pillpaint the surface with the house pill look (rounded-full, strong surface, border, shadow). Default is unpainted: the segment is first a capture container; put a dd-panel (or anything) inside for visuals.

Part: surface (the inner edge-aligned flex row, a column on a side edge). The element marks itself data-dd-interactive one settled frame after connect; that first report is what switches the bar window from native input everywhere to segment-scoped capture. See Interactivity for the bar subsection. Grid widgets have no use for this element.

dd-button

The standard button. variant is solid (default), ghost, or outline; content (a label, a dd-icon) is projected; disabled blocks it. Part: button.

<dd-button>Save</dd-button>
<dd-button variant="ghost">Clear done</dd-button>
<dd-button variant="outline">Connect</dd-button>
btn.addEventListener("click", () => { /* ... */ });  // host click bubbles out
btn.setAttribute("disabled", "");                    // reflects onto the inner button

dd-icon-button

The square (or round circle) icon button: transparent at rest, surface on hover. The glyph comes from name (a built-in glyph) or the slot (a dd-icon, or custom SVG built with dd.ui.create). Boolean attributes: round, active (accent border and text), off (muted), disabled. Part: button.

<dd-icon-button name="plus" title="Add"></dd-icon-button>
<dd-icon-button round name="play" title="Play"></dd-icon-button>
<dd-icon-button round><dd-icon name="chevron-down"></dd-icon></dd-icon-button>
/* stateful toggle: swap the built-in glyph by name */
playBtn.setAttribute("name", playing ? "pause" : "play");

dd-meter

The horizontal track-and-fill meter. value is 0..100 (clamped). severity (success, warning, danger) swaps the fill to the matching status token; without it the fill is --dd-meter-color (default: the accent), so a widget can inject a resolved custom color. Height via --dd-meter-height (default 0.4em, so it rides the surrounding font size). Parts: track, fill.

<dd-meter value="63"></dd-meter>
<dd-meter value="97" severity="danger"></dd-meter>
meter.setAttribute("value", String(pct));
meter.style.setProperty("--dd-meter-color", resolvedAccent);

dd-chip

A pill toggle. Boolean attributes: dim (low emphasis), active (accent border and text), disabled. Content is projected. Part: chip.

<dd-chip dim>Other networks...</dd-chip>
<dd-chip active>On</dd-chip>
/* an app-specific state, via the part */
dd-chip.is-hidden::part(chip) { text-decoration: line-through; }

dd-app-tile

A launcher tile: an icon (or a letter fallback) with an optional label.

<dd-app-tile src="icons/steam.png" label="Steam"></dd-app-tile>
<dd-app-tile glyph="S" label="Steam"></dd-app-tile>
<dd-app-tile add label="Add"></dd-app-tile>
AttributeEffect
src="..."an image icon (full color)
glyph="A"a letter or emoji fallback when there is no image
addthe dashed plus affordance (ignores src and glyph)
label="..."caption under (or beside) the icon; omit for icon-only
orientation="row"lay icon and label horizontally
disabledblock the click

Parts: tile (the button surface), icon (the icon box), label. Size the host from your own CSS; the icon box follows --dd-tile-icon (default 2em).

tile.addEventListener("click", () => dd.apps.launch(app.id));
dd.peers.draggable(tile, { /* ... */ });   // binds to the host fine
dd-app-tile { width: 56px; height: 56px; --dd-tile-icon: 32px; }
dd-app-tile::part(label) { color: var(--dd-text-muted); }

dd-volume-pill

Mute button, slider, percent readout, and a sound-settings shortcut in one element. The one behavior-ful component: it subscribes to the vitals stream and drives the system volume itself, so the manifest must grant system and systemControls. Failed calls log a warning and the pill stays up. Built for popout pane documents, but works anywhere.

  • no-settings hides the settings gear.
  • Parts: pill, slider, value. The mute and gear buttons expose no parts.
  • Slider input is throttled internally to roughly 10 volume calls per second, and the thumb and percent hold still during a drag.
  • Event dragchange (bubbling, composed, detail: { dragging }) fires as the user grabs and releases the slider. A pane document should relay it so the base widget's flyout helper holds its hover-retract during a drag:
pill.addEventListener("dragchange", (e) => dd.popout.send(e.detail));

dd-apps and dd-app

The taskbar's buttons, in one tag:

<dd-apps settings="pins" indicator="dots"></dd-apps>

<dd-apps> renders one <dd-app> per entry of dd.bar.store() (the host's strip: pins and running apps in the user's order, windows merged by appKey) as its own light-DOM children, keyed and reconciled in place, never rebuilt. Every behavior is the store's, so two bars composed from this click, launch, preview, pin and reorder the same way: a launcher launches once, behind a lock that holds until its window lands; a single window focuses, or minimizes when it is the focused one; a stack toggles the host-drawn preview flyout; icons are cached, the flyout state mirrors the host, the pins toggle gates the launchers, a drag moves a row. Needs a kind: "bar" widget with the taskbar permission.

Go only as deep as the look needs:

  1. The tag above, paired with the pins preset: no widget code.
  2. Restyle. The host is the box: size dd-app in widget CSS, measure it, write inline custom properties on it, animate its layout on it. State reflects as host attributes (running, pin, focused, minimized, count="n", noicon, launching, entering, leaving, dragging, low-motion; sorting on <dd-apps> itself for a drag's duration), so dd-app[focused] { outline: ... } is a focus ring with no indicator at all. The anatomy is parts: button > lift > (icon | fallback) + ring; indicator > dot (one per window, capped by max); label. A host state goes in front of the part: dd-app[focused]::part(dot), dd-app:hover::part(label), dd-app[launching]::part(lift).
  3. Replace a piece. A <template> child of <dd-apps> is cloned into every entry as slotted light DOM (slot="indicator", slot="label" replace the built-in parts) and widget CSS styles it as dd-app[focused] > [slot=indicator]. Cancelable events (bubble, composed): dd-activate { entry } on click, dd-launch { entry } before the launch, dd-flyout-open { entry, style } before a preview opens (detail.style is the style the open sends; edit it in place), dd-sort { key, before } on the drop of a sort, before the move (before is the entry key the row lands in front of, null for the end). preventDefault() replaces the default (for dd-sort: the host order comes back).
  4. Your own reconcile: <dd-app> standalone, fed a BarEntry through its entry property, keeps its click, icon, menu tag and launch state.
  5. Your own markup: dd.bar.store() and dd.ui.create.
  • <dd-apps> attributes: settings="pins"; indicator = dots (default) | ticks (stacked, for a side bar) | none; max (default 3); label = hover (default) | none; previews = click (default) | hover (a 400 ms dwell opens the preview, a quick sweep opens nothing, a switch while one is open waits the same dwell, leaving a button closes nothing); dwell (ms); stagger (boot cascade step, ms, default 30); sortable (app 0.3.5+; drag to reorder: a press that travels past 6 px picks the entry up, it moves live under the pointer along the strip's axis, the release inside the segment commits the move through the store and the host's stream confirms it; a release outside the segment, a cancel or an unchanged order puts the host order back; the flyout closes and hover pauses for the drag, the click the release lands is swallowed, and every live move fires dd-change). indicator, max and label forward to every entry. A widget that writes inline geometry on the entries should rest while <dd-apps> carries sorting.
  • Lifecycle: a new entry carries entering until its finite animations end (at once when none run); a removed one carries leaving, stops taking input and leaves the DOM when its animations end (700 ms backstop). Low motion skips both. The first render sets --dd-app-delay (and an inline animation-delay) per entry; read it in your own keyframes as animation-delay: var(--dd-app-delay, 0s).
  • Reorders slide: when entries land in new slots (a host emit, a drag shuffle), every surviving entry plays a short FLIP on the host transform (Web Animations, --dd-anim-fast / --dd-ease); low motion skips it. Leave the host transform alone in widget CSS and keep hover and press transforms on the parts, or the slide fights them.
  • Geometry keyframes (width, height) belong on the host, in light DOM, where the capture-region tracker sees their animationend; parts animate transform and opacity only. Keyframes declared in widget CSS and applied through ::part() resolve in the widget stylesheet's scope.
  • dd-change (bubbles, detail: { entries }) fires on <dd-apps> after every settled reconcile, entrance and exit, for widget code that measures the entries. apps.items is the live entries in row order, apps.appFor(key) one of them.
  • <dd-app> stamps the app-menu tag (data-dd-app / data-dd-window / data-dd-pin) itself, so a right-click opens the host's per-app menu; disabled (set by dd.ui.pressGuard) mirrors onto the inner button. low-motion on the host stops every animation and transition inside the shadow; outside it, override the tokens the components read (body.low-motion { --dd-anim-slow: var(--dd-anim-fast) }) rather than a * selector, which stops at the shadow boundary.

dd-bar-button

The start button, or the trigger for a pane of the bar's own:

<dd-bar-button action="start" label="Start"></dd-bar-button>
<dd-bar-button action="pane" pane-w="560" pane-h="440" label="Games" icon="play"></dd-bar-button>
  • action="start" taps the Windows start menu (dd.bar.openStartMenu) behind the press guard. action="pane" toggles the bar's own pane (the manifest popout, pane-w x pane-h CSS px, anchored on the button) and reflects pressed from the host's state only: the host closes the pane on its own (an outside press, Escape, a duck), so the pressed look follows popout.changed, never the click. No action: a plain button, the host click is yours. The paneData property reaches the pane as ctx.popout.data.
  • The icon is the default slot (an inline SVG whose parts you animate, a <dd-icon>) or the icon attribute (a built-in glyph name, or an SVG path rendered tinted). A start button with neither draws the four-pane logo itself, in currentColor and sized by --dd-icon-size on the host: parts logo and pane, pane-1 to pane-4, so a bar can animate the panes one by one (dd-bar-button:hover::part(pane-2) { animation-delay: 150ms }). label is the hover label; delay (ms) staggers the entrance like an entry's.
  • Parts: button > press (the pressed backplate, a real element because a pseudo-element on a shadow host never renders) + lift > the icon; label. Reflects pressed, entering, low-motion, disabled. The host is the box, like <dd-app>.

dd-tray-caret

The system-tray caret for a bar widget, in one tag:

<dd-tray-caret settings="systray"></dd-tray-caret>

It subscribes to the host's notification-area mirror (dd.systray) and opens or closes the host-drawn list. Needs a kind: "bar" widget with the taskbar permission; it warns and stays hidden otherwise. Paired with the systray preset there is no widget code at all.

  • Hides itself until the setting is on and the host reports at least one icon, so a bar that does not use it costs no pixels.
  • Reflects a showing attribute while visible, for neighboring layout in widget CSS.
  • separator renders a leading hairline that hides with the caret.
  • Parts: button, separator. Put the widget's box tuning on the host element; the shape stays the component's.
  • The host closes the list on its own (an outside press, Escape, a left click on a row; a right-click keeps it open under the app's own menu), and the caret's pressed look follows that, never a local guess.

Settings presets on components

The settings attribute wires a component to a setting preset by name, and accepts a comma-separated list. <dd-panel settings="panelStyle"> observes the panel setting and toggles its own transparent/bare attributes, and (app 0.2.4+) paints the preset's optional background image by setting --dd-panel-image / --dd-panel-image-fit on its surface, with zero widget code. Raw-class widgets can set the same two custom properties on their .dd-panel element by hand. Reading settings needs no permission, and presets never write settings.

Two notes: the wiring happens once, when the element first connects (changing the attribute later does nothing), and accentColor is a fields-only preset with no component-side behavior; read it with dd.theme.accent() instead of putting it in a settings attribute.

Overriding

Two tiers, matching how much you want to change:

  • Style hooks. ::part(), the custom-property knobs, and named slots, as documented per component above.
  • Subclass and call super. The constructors are exposed on dd.ui.components (DdElement, DdPanel, DdText, DdIcon, DdButton, DdIconButton, DdChip, DdMeter, DdAppTile, DdVolumePill, DdTrayCaret, DdApps, DdApp, DdBarButton). Extend one, override a method (build, render, applyPreset), call the super method to keep the original behavior or skip it to replace it, then register your own tag:
class MyPanel extends dd.ui.components.DdPanel {
  render() {
    super.render();                                   // keep the panel's own logic...
    this.surface.classList.toggle("my-flag", flag);   // ...and add to it
  }
}
customElements.define("my-panel", MyPanel);

Building the rest

Components cover chrome. Build everything else with dd.ui.create; components compose with it and with each other (a dd-panel heading is itself a dd-text).