Guides

Building a taskbar

What a taskbar is, what the host does for it, and a first bar from three tags: the folder, the manifest, the edge, input by segments, previews, a pane, settings and publishing.

A DeskDash taskbar is a widget with "kind": "bar". The same folder of plain HTML, CSS and JavaScript, the same sandbox, the same dd SDK, the same components and tokens as a desktop widget. What changes is the surface: instead of a tile on the grid, the widget renders as the taskbar, docked to a screen edge, and the Windows taskbar steps aside while it runs.

What the host does for you

The split of ownership is the whole model, so read it first.

The host owns the window. It creates one bar window per display, docks it to the edge your manifest names, reserves the strip with the OS so maximized windows stop above it, hides the native Windows taskbar and brings it back on quit, on a crash, or when the user turns the bar off. Your document never moves, never resizes and never knows about any of it.

The host owns the data. One ordered strip of pinned and running apps, the same for every bar on every display; the open windows; the pins, which live in the app config and survive a bar switch; the notification area, read back from Windows so a bar can offer it. A bar renders from these streams and never keeps an order or a pin list of its own.

The host owns every popup. Window previews, the per-app right-click menu, the tray list and your own pane are separate host-drawn windows that open beside the strip. A bar says when and for which app; the host draws, places, refreshes and closes.

You own the HTML and CSS. That is where two taskbars differ: the dock is a centered pill with a magnification wave, the rail is a tall glass column on a side edge, aurora is a full-width bar with a clock and a now-playing chip. Behind all three sit the same four components and the same store.

One bar runs at a time. The user picks it in the marketplace, and it shows on every display, or on the one they choose in Settings, Taskbar (app 0.3.4+). Bars have no instances: the document's role is "bar" and its instanceId is the sentinel "__bar" on every display.

Start from a folder

Bars load from the taskbars folder, next to the widgets folder (default Documents\DeskDash\taskbars). The split is enforced both ways: a kind: "bar" manifest in the widgets folder is quarantined with a pointer at the move, and only bar widgets load from the taskbars folder. The two folders share one id namespace.

The scaffolder in developer mode makes desktop widgets, so a taskbar starts by hand: copy one of the bundled bars from the taskbars folder, or create a folder with the files below. Add to a folder... on the Developer page writes the dev kit into it.

my-bar/
├─ manifest.json   # kind, the bar block, permissions, settings
├─ index.html      # the bar document
├─ style.css       # the look
└─ widget.js       # optional: only what the look needs

The manifest of the bundled dock, which is a complete taskbar:

{
  "id": "dock",
  "name": "Dock",
  "version": "0.4.5",
  "apiVersion": 1,
  "minAppVersion": "0.3.5",
  "description": "A macOS-style floating dock ...",
  "author": "deskdash",
  "kind": "bar",
  "bar": { "thickness": 128, "reserve": 68 },
  "settings": ["pins", "systray"],
  "permissions": { "taskbar": true }
}

Four things are bar-specific. kind makes it a taskbar. The bar block sizes the window (the manifest reference has every field and range). The taskbar permission unlocks the bar-only surface: windows, pins, the strip, previews, the tray and the app menu (see permissions). The two presets, pins and systray, give the user the "Show pinned apps" and "Show the system tray" toggles that the components apply on their own. minAppVersion is 0.3.0 at the least (older apps do not know kind) and 0.3.5 for a bar that uses the strip or drag to reorder; the floors table lists them.

Three tags are a taskbar

This is the dock's document, with its comments and ids trimmed:

<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <link rel="stylesheet" href="/__sdk/dd-base.css" />
    <link rel="stylesheet" href="style.css" />
  </head>
  <body>
    <dd-bar place="center" reach="35">
      <dd-panel row>
        <dd-bar-button action="start" label="Start"></dd-bar-button>
        <dd-apps settings="pins" indicator="dots" sortable></dd-apps>
      </dd-panel>
      <dd-tray-caret settings="systray" hidden></dd-tray-caret>
    </dd-bar>
    <script src="/__sdk/widget-sdk.js"></script>
  </body>
</html>

Every behavior a taskbar has is in those tags, with no widget code:

  • <dd-bar> is a segment: the part of the window that takes the mouse. Everything outside it clicks through to the desktop or the maximized window beneath. place says where along the edge it sits, reach adds hover headroom above the content.
  • <dd-bar-button action="start"> is the start button. It draws the four-pane logo itself and taps the Windows start menu on a click.
  • <dd-apps> is the app strip: one <dd-app> per row of the host's strip, pins and running apps in the user's order, windows merged per app. A click launches, focuses, minimizes or opens the preview flyout; a right-click opens the host's per-app menu; sortable (app 0.3.5+) makes a press-and-drag reorder the strip. settings="pins" wires the "Show pinned apps" toggle.
  • <dd-tray-caret> is the notification-area caret. It stays hidden until the tray has icons and the setting is on, and a click opens the host-drawn tray list. settings="systray" wires the toggle.

The dock's widget.js exists only for the magnification wave. A bar that wants a different look edits style.css; the components are the box, so size them, animate them and style their parts from widget CSS. The components guide has the general rules and the reference lists every attribute, state and part.

See it

Open the marketplace (the tray icon, or Add widgets... in its menu), switch to Installed, then Taskbars: your folder is listed with a Use button, because one bar runs at a time. Use it, and the Windows taskbar gives way to yours.

Right-click the bar for its menu. Taskbar settings... opens the bar's own settings dialog: the Placement group first (Side, when the manifest lists several edges; Size, 50% to 200%; Auto-hide; Let windows go behind the bar), then your own settings, live-previewed as they change. Change taskbar... goes back to the Taskbars section. After an edit, Reload widgets from the tray reloads the bar too.

Placement is the user's. The manifest tunes what they can pick: edges lists the sides the bar supports, reserve and allowOverlay decide whether windows may fill behind it, and the optional autoHide block sets the feel of auto-hide (the delay, the reveal band on the screen edge, the slide time). The user switches auto-hide on; you say how it behaves. The slide is the host's: your document neither moves nor needs to know, and the reserved strip goes to zero while auto-hide is on.

Lay out along the edge

A bottom bar is the full display width and thickness tall. A left or right bar is the full display height and thickness wide, and every host popup opens to its side instead of above it. Your document learns the edge from ctx.bar.edge in the init context (the same value reaches a bar's pane); a <dd-bar> segment reads it on its own and lays out as a column on a side edge, and dd.bar.store().edge carries it as a signal. The rail mirrors its layout with one attribute on the body:

const ctx = await dd.ready;
document.body.dataset.edge = ctx.bar?.edge ?? "bottom";

Size against the viewport, not in fixed pixels. The user's Size slider (app 0.3.1+) scales the window's thickness, and with it the reserved strip, so a document whose sizes derive from 100vh (a bottom bar) or 100vw (a side bar) scales whole, while literal pixel sizes and theme font-size tokens do not. The bundled bars use one unit:

:root {
  --u: calc(100vh / 128);            /* the manifest thickness, in design px */
  --icon-rest: calc(var(--u) * 52);  /* an icon at rest */
  --pill-h: calc(var(--u) * 72);
}

Treat the manifest thickness as the design size, never as a constant. Colors, radii and type come from the design tokens like everywhere else.

Input: segments, not flags

A bar has no interactive flag. The window takes the mouse natively everywhere: plain CSS :hover and click handlers just work, and for a solid full-width bar that is the whole story.

A bar with empty space (a centered pill, split segments) wraps its visible content in <dd-bar> segments. Each segment's box captures the mouse; everything outside every segment clicks through. Two consequences:

  • Pointer events outside the segments never reach your document, so hang hover waves and leave timers on the segment, not on the body.
  • A reserve smaller than thickness leaves transparent headroom above the strip where maximized windows fill in. Use it only together with segments; without them that band is an invisible click-eater. Give the segment reach so hover animations that rise into the headroom stay hoverable.

The mechanics, and the rules for marking regions after layout, are in the interactivity guide.

Previews, the app menu and the tray are the host's

A stacked app's click opens the preview flyout: window titles and live snapshots, drawn by the host beside the strip. previews="hover" on <dd-apps> opens it after a dwell instead, the native taskbar feel. The look is tunable through a curated style (preview width, radius, gap, surface and border colors, title and close button), set once with dd.bar.store().setFlyoutStyle(style); everything else about the flyout is the host's, including when a card shows the app's icon instead of a snapshot. The flyout reference has the ranges and the fallbacks.

A right-click on an app button opens the host's per-app menu: launch, pin or unpin, close. <dd-app> tags itself so the SDK raises it; a bar that draws its own buttons stamps the same tag by hand (dd.bar.appMenu). Never draw an app menu or keep a pin list of your own: the actions are host privileges, the pins belong to the app config, and a bar is too thin to hold a menu anyway. Untagged space raises the bar's own menu.

The tray caret opens the host's tray list. A bar sees a count and the open state, never the icons, so the caret's pressed look follows the dd.systray stream. Tell your users what the list shows: every app's icon, hidden ones dimmed, and DeskDash's own; the Windows volume, network and battery icons stay with Windows.

A pane of your own

A bar may declare the same popout block as a grid widget, without max, and open it from a click. The host renders the pane document beside the strip, in its own window, sized to your CSS px and capped to the display. The rail's games button is one:

<dd-bar-button action="pane" pane-w="620" pane-h="620" label="Games">
  <dd-icon src="gamepad.svg" tint></dd-icon>
</dd-bar-button>
"popout": { "entry": "games.html" },
"permissions": { "taskbar": true, "games": true }

The pane gets the bar's grants and ctx.bar.edge, closes on Escape, an outside press, a fullscreen app or a taskbar change, and never takes keyboard focus, so design it pointer-only. The taskbar streams reach the pane too; the bar-window-only verbs (the flyout, the app menu, the tray list, the strip move) stay with the bar document. Details in popouts, on a taskbar; the games library behind that pane is dd.games.

Settings without an instance

Bars have no instance, so the active bar has one settings home in the app config, served by dd.settings.get merged over your manifest defaults, and one dialog: the bar's right-click Taskbar settings.... Values live-preview as they change, Save persists, any other close replays the stored values. Everything in the settings guide applies, with two exceptions: the consent-bearing types (secretRef, filePath, instances) are not offered for bars, and the two bar presets, pins and systray, exist for bars only.

Every other grant works

taskbar unlocks the bar-only surface. The rest of the permission set (time, system, media, audio, notifications, http and the others) is declared and gated on a bar exactly as on a grid widget, and the streams reach the bar document on every display the bar runs on. Aurora is the worked example: a clock, a now-playing chip, vitals rings and an audio-reactive surface beside its apps, plus a transport deck in its pane. One note for audio: capture stays demand-driven, the active bar counts as a visible audio widget, and its anyAudio setting is read from the bar's settings.

What stays native, and the limits

The Windows taskbar is hidden, not removed, and Windows keeps reporting it to other apps. Your users will see three things beside a side bar or a short bar: the Start menu opens where Windows puts it, at the bottom edge, left or center per their Windows setting; third-party apps that place their toasts above "the taskbar" (Steam, for one) keep a gap at the bottom; and Explorer re-shows the hidden taskbar on every Start open and work-area change, which the host re-hides at once, so a flash is the most anyone sees. A bar that wants Start beside itself draws its own start pane with the pane mechanism above.

Elevated windows (Task Manager, an elevated terminal, some installers) sit behind a Windows boundary that drops messages from ordinary apps. Focus still works through a fallback; minimize is refused and the click does nothing; the close request from a preview card or the app menu is dropped; the preview shows the icon tile. Previews also fall back to the icon tile for minimized windows and for windows that draw straight to the GPU (games, some browsers and players). None of this is yours to fix, and the streams never desync over it: drive every indicator from dd.windows.onChanged, never from a click.

Publish

Taskbars ship through the same marketplace pipeline as widgets. On the submit page, pick Taskbar at the top and upload the folder as a zip; the manifest must declare kind: "bar" with its bar block and a minAppVersion of at least 0.3.0, and the choice is checked against the zip so a mix-up gets a clear error instead of a mislabeled listing. Taskbars have no store categories; everything else (screenshots, features, the long description, the review) works exactly like a widget submission, see publishing. Published taskbars appear on the store's Taskbars shelf and in the app's marketplace under Taskbars, where installing one offers Use right away.

Where to go next