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):
| variant | Use |
|---|---|
body (default) | plain body text |
title | panel or section heading (semibold, large, display face) |
subtitle | secondary heading (muted, medium) |
label | uppercase tracked label |
muted | secondary or status text |
value | big 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 -->namepicks 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.srcrenders an SVG file from your widget folder (or adata:URI) as a real image, so the SVG's own colors show. Addtintto mask it in the current text color instead. The image carries partimg.
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>| Attribute | Effect |
|---|---|
heading="..." | render a title row. Use heading, not title (the native tooltip) |
transparent | glassy surface instead of solid |
bare | no paint at all: the card keeps its box (padding, layout) but drops background and border |
row | lay children horizontally |
settings="panelStyle" | auto-wire the panel-style preset (see below) |
| Slot | For |
|---|---|
| (default) | the panel body |
heading | a custom or dynamic heading, instead of the attribute |
actions | controls pinned to the right of the header |
| Part | For |
|---|---|
surface | the card itself (override padding, layout, border) |
header | the heading row |
heading | the 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>| Attribute | Effect |
|---|---|
edge | left 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. |
place | where 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. |
pill | paint 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 buttondd-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>| Attribute | Effect |
|---|---|
src="..." | an image icon (full color) |
glyph="A" | a letter or emoji fallback when there is no image |
add | the 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 |
disabled | block 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 finedd-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-settingshides 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:
- The tag above, paired with the
pinspreset: no widget code. - Restyle. The host is the box: size
dd-appin 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;sortingon<dd-apps>itself for a drag's duration), sodd-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 bymax);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). - 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 asdd-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.styleis the style the open sends; edit it in place),dd-sort{ key, before }on the drop of a sort, before the move (beforeis the entry key the row lands in front of,nullfor the end).preventDefault()replaces the default (fordd-sort: the host order comes back). - Your own reconcile:
<dd-app>standalone, fed aBarEntrythrough itsentryproperty, keeps its click, icon, menu tag and launch state. - Your own markup:
dd.bar.store()anddd.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 firesdd-change).indicator,maxandlabelforward to every entry. A widget that writes inline geometry on the entries should rest while<dd-apps>carriessorting.- Lifecycle: a new entry carries
enteringuntil its finite animations end (at once when none run); a removed one carriesleaving, 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 inlineanimation-delay) per entry; read it in your own keyframes asanimation-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 hosttransformalone 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.itemsis 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 bydd.ui.pressGuard) mirrors onto the inner button.low-motionon 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 manifestpopout,pane-wxpane-hCSS px, anchored on the button) and reflectspressedfrom the host's state only: the host closes the pane on its own (an outside press, Escape, a duck), so the pressed look followspopout.changed, never the click. Noaction: a plain button, the host click is yours. ThepaneDataproperty reaches the pane asctx.popout.data.- The icon is the default slot (an inline SVG whose parts you animate, a
<dd-icon>) or theiconattribute (a built-in glyph name, or an SVG path rendered tinted). A start button with neither draws the four-pane logo itself, incurrentColorand sized by--dd-icon-sizeon the host: partslogoandpane,pane-1topane-4, so a bar can animate the panes one by one (dd-bar-button:hover::part(pane-2) { animation-delay: 150ms }).labelis 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. Reflectspressed,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
showingattribute while visible, for neighboring layout in widget CSS. separatorrenders 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).