Sidebar

The Sidebar is twelve squares wide. Each row is one square high, with half a square of indentation per level. Labels use regular text; counts and supporting information use small text. Disclosure arrows follow their labels. Selection fills the whole row with 2px corners, scaled with the surface.

A sidebar is navigation: the places of an application, every row a link or a button of its own and a stop for Tab, the current destination the page being shown. With tree it is a tree instead: the things inside one of those places, which a person opens, closes and chooses from.

An exhibition index

Icons, nested labels and counts share compact navigation rows.

Compact document navigation

Open a section, choose a document, or navigate with the arrow keys.

Set the items property to an array of visible rows. Each row has a unique id and a label; optional fields are depth, href, expanded, current, disabled, icon (a name from the icon library), meta, and action: { id, label }. Omit expanded for a leaf. Collapsed children must be omitted from the array.

The application owns the hierarchy, filtering, selected destination and saved expansion state. The Unit owns the row layout, disclosure controls, focus and keyboard navigation. Supply a label for the navigation landmark and empty for its empty state. The header and footer slots hold optional search and supporting controls. When the host has a constrained height, only the navigation rows scroll; header and footer controls stay visible.

EventDetailApplication response
sidebar-activate{ id }Cancel with preventDefault() to handle routing or guard unsaved changes.
sidebar-toggle{ id, expanded }Update the visible items if expansion is accepted.
sidebar-action{ id, action }Run the independent trailing action.

Links retain their native destinations, context menus and modified clicks. Buttons support Enter and Space. Up and Down move between available rows; Home and End move to the first and last. Right expands a closed section or enters its children; Left collapses an open section or moves to its parent. Tab also reaches disclosure and trailing actions.

A label too long for its row ends in an ellipsis and shows in full in the shared tooltip on hover or focus; a label that fits has none, and no row carries the browser’s title. The Sidebar loads the tooltip module itself.

Call focusItem(id) after updateComplete to focus a row, or scrollToCurrent() to reveal the current destination. --ds-sf scales the width, rows, text, icons and indentation. A containing layout may set the host width to another whole number of squares.

The rows’ fills and the current row’s weight are the shared row tokens (--ds-row-fill-hover, --ds-row-fill-current, --ds-row-current-weight), so a navigation row and a tree’s selected row look the same.

In React, Sidebar from react/sidebar.ts takes items, label, empty, tree, onToggle, onSelect, onActivate and onAction.

Folders and files

Open a folder, choose a file, or move with the arrow keys. Enter or a double click opens a file.

tree makes the sidebar a tree: folders and files, groups and their samples, a page’s parts. Each row is one square high and each level stands one square further in, so a row’s disclosure arrow stands under its parent’s label. The arrow and an optional icon each take a square of their own; a leaf keeps the arrow’s square empty, so labels at one level line up. The rows are the same items, with selected for the row chosen instead of current; labels are shown as written, never translated.

The application owns the hierarchy, the expansion and the selection. The tree owns the rows’ layout, their roles, the focus and the keyboard.

EventDetailApplication response
sidebar-toggle{ id, expanded }Show or hide the row’s children and set its expanded.
sidebar-select{ id }Set selected on that row, and show what it holds.
sidebar-activate{ id }Open the row’s thing: a file in its editor, a sample played.

A click on the arrow opens or closes the row; a click anywhere else selects it, and a double click activates it. itemFromEvent(event) names the row under a context menu or a drag of the application’s own. focusItem(id) moves the focus to a row and scrollToCurrent() brings the selected row into view.

The tree is one stop for Tab: the selected row, or the row last focused. Then, as in the tree pattern: Down and Up move to the next or previous row, skipping unavailable ones; Home and End to the first and last; Right opens a closed row, or moves into an open one; Left closes an open row, or moves to its parent; Space selects; Enter selects and activates; a letter or digit moves to the next row whose label starts with it.

The rows are role="treeitem" inside a role="tree", each with aria-level, aria-setsize and aria-posinset, aria-expanded on a parent and aria-selected on every row. The arrow is drawn, not announced: the row says whether it is open. The pointer over a row fills it as any pressable row, and the selected row is drawn as the current destination. The tree scrolls its rows when its height is constrained.

The native link or button covers the whole navigation row, including its empty space. Its text, indentation and disclosure stay in the same place. A section with no href toggles when pressed anywhere; a linked section still navigates and has a separate disclosure button. Optional actions remain independent of both.

An icon action uses the same action event and keeps its accessible label:

sidebar.items = [
{ id: "pages", label: "Pages", expanded: true,
action: { id: "create", label: "Add page", icon: "plus" } },
{ id: "intro", label: "Introduction", depth: 1, href: "/intro" },
];

Set action.disabled to disable only that action; item.disabled disables all of the row’s controls. No plus button appears unless the application supplies it.

Set sidebar.reorderable = true to enable desktop HTML drag-and-drop in navigation mode. Drop in the top or bottom half of a sibling row to request a move before or after it. Alt+Up and Alt+Down request the same moves from the keyboard. Set an item’s reorderable: false to exclude it. Disabled rows, other parents, external drags and tree mode are excluded. Touch dragging and reparenting are not implemented.

sidebar-move carries { id, targetId, position: "before" | "after" }. The component never changes or saves items: validate permissions and the hierarchy in the application, move the whole subtree when moving a parent, then replace items if the move is accepted. Keep reorderable false while saving. A rejected request leaves the order intact. After accepting a keyboard move, call focusItem(id) after updateComplete to retain focus. Supply translated keyboard guidance alongside any reorderable surface.

The React adapter exposes reorderable, onMove, expandIcon and collapseIcon as props. These options are off unless supplied; the documentation navigation does not enable editing or reordering.