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 needsThe 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.placesays where along the edge it sits,reachadds 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
reservesmaller thanthicknessleaves 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 segmentreachso 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
- Bar widgets reference: the store, the strip, the windows, the pins, the flyout, the tray and the app menu.
- Components: every attribute, state and part of the four bar components.
- A taskbar that feels native: the corner, the order, the motion.