ReferenceAPI

Bar widgets

Reference for the bar-only surface: the taskbar store and the render list, the system tray mirror, the start-menu tap and the per-app context menu, gated by the taskbar permission.

Some methods exist only for a kind: "bar" widget that also holds the taskbar permission. The bridge refuses the whole surface for a regular desktop widget before permissions are even read, so "a desktop widget can never see your open windows" stays a one-line guarantee.

This page covers the taskbar store and the render list, the notification-area mirror, the start-menu tap and the per-app context menu. The same taskbar + bar-kind gate also guards the open-windows and pinned-app surfaces (dd.windows.*, dd.pins.*) and the host-drawn preview flyout (dd.bar.flyout.*), which the store drives for you.

Most bars never call any of it directly: the bar components (<dd-apps>, <dd-bar-button>, <dd-tray-caret>) carry the behavior, and a bar differs in its CSS.

Where the bar docks

A bar's manifest names the edges it can dock to (bar.edges, default ["bottom"]; see the manifest reference). "left" and "right" make a vertical bar: the window is the full display height and bar.thickness wide. A bar that lists several edges gets a Side choice in the bar's own settings dialog (right-click the bar; the Placement group at the top). Your document learns the docked edge from ctx.bar.edge (the same value reaches a bar's pane; a <dd-bar> segment reads it itself, dd.bar.store().edge carries it as a signal) and mirrors its layout from it. Every host popup that hangs off the bar (previews, the tray list, the app menu, a pane) opens beside the strip on a side bar, so anchors carry { x, y, w, h }: pass the element and the SDK measures it.

The same Placement group offers Auto-hide for whichever bar is active: the host reserves no space, slides the bar off its edge while the mouse is away, and brings it back when the cursor touches that edge. The slide is the host's; your document neither moves nor needs to know. The manifest tunes the feel through the optional bar.autoHide block: hideDelay (ms the cursor must stay away, 0 to 2000, default 150), revealBand (logical px on the screen edge, 1 to 8, default 2) and slide (ms, 0 to 600, default 140). The user switches auto-hide on; you set how it behaves.

The same group has a Size slider (50% to 200%, one value per bar): the host scales the window's thickness, and with it the reserved strip, so a document that derives every size from its viewport (100vh on a bottom bar, 100vw on a side bar, or a unit like --u: calc(100vh / 128)) scales whole. Literal pixel sizes and theme font-size tokens do not; treat the manifest thickness as the design size, never as a constant.

What stays native: the Windows taskbar is hidden, not removed, and Windows keeps reporting it to other apps. Beside a side bar the Start menu (dd.bar.openStartMenu) still spawns at the bottom edge, where Windows puts it, and third-party apps that place their toasts above "the taskbar" (Steam, for one) keep a 48 px gap at the bottom. Windows' own notifications use the work area and land right. A bar that wants Start beside itself draws its own start pane with the popout mechanism below.

A pane of your own

A bar may declare the same popout block as a grid widget (entry only, no max) and open it with the same dd.popout.open from a click. The host renders the pane document beside the strip in its own window, with your grants, ctx.popout.side pointing away from the bar and ctx.bar.edge set; size is CSS px capped to the display and prefer is ignored. The pane closes on Escape, a press outside it and its button, a fullscreen app, a reload, a tray click or a taskbar change, and it never takes keyboard focus (pointer-only). dd.popout.send and onMessage work between the two documents. The bar-window-only methods on this page stay with the bar document; the taskbar streams (windows.changed, pins.changed, systray.changed) reach the pane too. The popouts guide has the details.

Every other grant works too

taskbar unlocks the bar-only surface on this page. 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, so a taskbar can carry a clock, a now-playing chip, vitals meters or an audio-reactive surface beside its apps. Audio capture stays demand-driven: the active bar counts as a visible audio widget since app 0.3.0, and its anyAudio setting is read from the bar's settings.

Where bar widgets live

Bar widgets 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. Users pick the active taskbar in the marketplace's Taskbars section (one bar runs at a time, so installed taskbars offer Use instead of Add), and they edit its settings from the bar's own right-click menu.

Publishing taskbars

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 minAppVersion of at least 0.3.0), and the selection 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 hands-on review) works exactly like a widget submission. Published taskbars appear on the store's Taskbars shelf and in the app's marketplace under Taskbars, where installing one offers Use right away.

dd.bar.store

dd.bar.store(): BarStore

The headless taskbar state, one lazy instance per bar document; the first call arms the host streams. Signals (dd.ui.signal, read .value inside an effect, .peek() outside): windows and pins (the host snapshots), entries (the render list below), flyout ({ open, key }, from bar.flyout.changed only), pinsEnabled, lowMotion (low-spec host or reduced motion), edge; per key, icon(iconKey) (the data URI, fetched in batches on first read; a key the host has no icon for stays undefined and is never asked again) and launching(key). ready resolves after the seed, with every listener already registered.

Actions: activate(key, anchorEl) is the click tree (a launcher launches once, behind a lock that holds until its window lands or 6 s; a single window focuses, or minimizes when it is the focused one; a stack toggles the preview flyout anchored on the element), with launch, openFlyout, closeFlyout and toggleFlyout as its pieces; hoverEnter(key, el) and hoverLeave() run the hover dwell when configure({ previews: "hover" }) is set (a sweep opens nothing, a switch while open waits the same dwell, leaving a button closes nothing); pointerEntered() and pointerLeft() are the debounced advisory close for the bar segment; enablePins(bool) is the pins gate (pins.list is never called while off); setFlyoutStyle sets the style every open sends; menuSubject(entry) is the app-menu subject. hooks is the seam the components use to raise their cancelable events; leave it alone.

The <dd-apps> / <dd-app> / <dd-bar-button> components render from it and hold no behavior of their own. A bar that draws its own buttons reads and calls the same thing.

dd.bar.entries

dd.bar.entries(windows: OsWindow[], pins: PinnedApp[]): BarEntry[]

The render list every taskbar wants, pure: pins first in pin order, each merged with its running group when the host stamped the pin's id onto the windows (appKey equality), a launcher otherwise; then the unpinned running groups in the host's first-seen order. Windows without an appKey stay their own entry. A merged entry and its launcher share one key, so one DOM node can carry launcher, running, launcher across a launch and a quit. The store's entries signal is exactly this over its snapshots.

dd.systray

The Windows notification area ("system tray"), mirrored by the host so a bar can offer it back once DeskDash replaces the native taskbar. You get a count, never the icons: the rows, and the clicks that reach those apps, live in a host-drawn window that no widget can read or script.

The easiest way to use it is the <dd-tray-caret> component with the systray setting preset; the methods below are the surface underneath it.

dd.systray.subscribe()

dd.systray.subscribe(): Promise<{ count: number }>

Arms the host reader (the first call only, so a bar that never shows the tray costs nothing) and returns the count of shown icons. Seed a caret's visibility from the count, then follow systray.changed.

dd.systray.open()

dd.systray.open(anchor: Element | { x: number; y?: number; w: number; h?: number }): Promise<void>

Opens the host-drawn tray list, anchored on your caret. Pass the element and the SDK measures it. The host owns the list's size, position, dismissal and every click inside it. Fire and forget: follow systray.changed, not this promise.

dd.systray.close()

dd.systray.close(): Promise<void>

Advisory close (your caret toggled off). The host also closes on its own: an outside click, Escape, a row action, or the bar going away.

dd.systray.onChanged()

dd.systray.onChanged(cb: (p: { count: number; open: boolean }) => void): () => void

Fires on every tray change and every open or close of the list, whoever caused it. This is the correct driver for a caret's pressed look: the host closes the list on its own, so follow this, never your own guess.

dd.bar.openStartMenu

dd.bar.openStartMenu(): Promise<void>

Opens the Windows start menu (the host taps the Windows key). A second call while it is open toggles it closed, the native behavior. There is no documented API for the menu itself, so the tap is a no-op on the secure desktop or while a held modifier turns it into a chord. Fire and forget: there is no menu state to follow.

dd.bar.appMenu

dd.bar.appMenu(opts: {
  appKey?: string;    // exactly one of the three
  windowId?: string;
  pinId?: string;
  anchor: Element | { x: number; w: number };
}): Promise<void>

Opens the host-drawn per-app context menu above the bar for one subject: a merged group by its appKey, a single window by its id (for a window without an appKey), or a pin by its id (for a pin without one). The menu shows the app's name (launches a new instance), Pin to taskbar or Unpin from taskbar, and Close window (or Close all windows for a group with several). Pins live in the DeskDash config, one list for every bar widget (switching bars never resets it), and every change comes back through dd.pins.onChanged.

A pin's appKey is its own id, and the host stamps that same token onto every window the pin claims before it hands you the window list. So a pin and its running windows always share one key, from the first frame either exists, and a group key and a pin id are the same kind of token.

You rarely call this yourself. <dd-app> tags itself; by hand, tag each app button with the subject and the SDK's right-click handler does it for you:

<button data-dd-app="3f9a1c2e">...</button>
<button data-dd-window="131586">...</button>
<button data-dd-pin="9c2f0b4e1d7a6f35">...</button>

Re-stamp the tag on every render (ids move as windows come and go). An element whose own contextmenu handler calls preventDefault() before the SDK's handler keeps the right-click; untagged space raises the bar's own menu (Taskbar settings, Settings, Edit layout, Quit).

Trust model: you only say which app was right-clicked. Launch, pin, unpin and close run host-side from clicks inside the host's own menu window, so the method grants a widget nothing beyond opening the menu, and the taskbar permission stays a read grant. Honored only while the cursor is over the bar and never in edit mode; the host closes the menu on Escape, an outside click, an action, or when the subject disappears. Fire and forget.