Skip to Content
Developer GuideDaemon UIWebShell Sidebar — Customization Guide

WebShell Sidebar — Customization Guide

The WebShellSidebar is the session list and navigation panel rendered inside the web-shell App component. This document maps each visual area to its current customization capability and identifies areas with no external injection point.

Enabling the sidebar

The sidebar is disabled by default. Pass the sidebar prop to enable:

import { WebShellWithProviders } from '@qwen-code/web-shell'; <WebShellWithProviders baseUrl="http://localhost:4170" sidebar={true} // simple enable // or with fine-grained options: // sidebar={{ enabled: true, defaultCollapsed: false, ... }} />;

Layout overview

┌─────────────────────────────────────┐ │ ① Branding (topRow) │ ✅ customizable ├─────────────────────────────────────┤ │ ② Primary navigation │ ✅ customizable │ [+ New task] [🧩 Plugins] │ │ [📅 Scheduled] [🎯 Goals] │ │ [custom render...] │ ├─────────────────────────────────────┤ │ ③ Project header │ ✅ show/hide │ 📁 Projects ▼ [🔍] [+] │ │ Session list entries... │ │ 📦 Archived sessions │ ├─────────────────────────────────────┤ │ ④ Footer action bar │ ✅ customizable │ [⚙ Settings] v0.19 [☀] [▦] [◧] │ ├─────────────────────────────────────┤ │ ⑤ Resize handle │ ❌ not customizable └─────────────────────────────────────┘

Customizable areas

① Branding — branding

interface WebShellSidebarBranding { render?: () => ReactNode; // replace the entire branding row hideWhenCompact?: boolean; // hide when sidebar is collapsed (default: true) }
ValueEffect
undefined (default)Resolved brand: brand prop → daemon GET /brand → built-in Qwen logo + “Qwen Code” text
falseBranding row hidden entirely
{ render: () => <MyHeader /> }Full replacement with custom content
{ hideWhenCompact: false }Keep branding visible in collapsed icon-rail mode

The default row is data-driven, not fixed: a daemon that serves a ui.brand configuration renames the text and swaps the mark, and an embedding host can override both with the shell component’s brand prop (onBrandResolved reports the outcome for the host’s own chrome). branding.render stays the highest-precedence override — it wins over the prop and the daemon-resolved value, exactly as before.

sidebar={{ branding: { render: () => ( <div style={{ display: 'flex', gap: 8 }}> <img src="/my-logo.svg" alt="" width={24} /> <span>My App</span> </div> ), }, }}

② Primary Navigation — primaryNav

type WebShellSidebarPrimaryNavItem = | 'newTask' // ✏️ New Task button | 'plugins' // 🧩 Plugins button | 'scheduledTasks' // 📅 Scheduled Tasks button | 'goals'; // 🎯 Goals button interface WebShellSidebarPrimaryNavOptions { items?: readonly WebShellSidebarPrimaryNavItem[]; // which built-in buttons to show (default: all) render?: () => ReactNode; // additional custom content after built-in buttons }

The primary navigation area contains built-in buttons controlled by items:

  • All buttons are shown by default when items is not specified
  • Only the listed buttons are shown when items is provided
  • Custom content can be added via render() after the built-in buttons
ValueEffect
undefined (default)All built-in buttons shown
{ items: ['plugins'] }Only Plugins button
{ items: ['plugins', 'scheduledTasks'] }Plugins + Scheduled Tasks
{ items: [], render: () => ... }Hide all built-in, only custom content
sidebar={{ primaryNav: { items: ['plugins', 'scheduledTasks'], // hide newTask and goals render: () => ( <button onClick={() => console.log('custom action')}> 🔗 Data Sync </button> ), }, }}
type WebShellSidebarFooterItem = | 'settings' // ⚙ Settings panel | 'version' // version label (e.g. "v0.19.10") | 'theme' // ☀/🌙 light/dark toggle | 'sessionsOverview' // ▦ session overview panel | 'splitView' // ◧ split view (large screens only) | 'daemonStatus' // 📊 daemon status panel | 'collapse'; // ◁/▷ collapse/expand toggle interface WebShellSidebarFooterOptions { items?: readonly WebShellSidebarFooterItem[]; // which built-in items to show (default: all) render?: () => ReactNode; // custom content rendered on the left side, before built-in items }
ValueEffect
undefined (default)All items shown
falseFooter hidden; the mobile drawer keeps only its close control
{ items: ['settings', 'theme', 'collapse'] }Only listed items shown; the mobile drawer always keeps its close control

The footer auto-adapts to narrow widths: labels are hidden and version is dropped below certain thresholds.

sidebar={{ footer: { items: ['theme', 'collapse'] }, // minimal footer }}

Custom content via render() appears on the left side of the footer, before the built-in items:

sidebar={{ footer: { items: ['collapse'], render: () => ( <button onClick={() => openHelpCenter()}> ❓ Help </button> ), }, }}

Note: 'scheduledTasks' and 'goals' have been moved to the primary navigation area (②) and are shown by default. They are controlled by primaryNav.items instead of footer.items.

Other top-level options

interface WebShellSidebarOptions { enabled?: boolean; // show/hide sidebar (default: true when passed) defaultCollapsed?: boolean; // initial collapsed state (persisted in localStorage) showCompactToggle?: boolean; // show the collapse button in the chat area (default: true) showSessionSourceSwitch?: boolean; // show the Tasks/Channels switch (default: true) showLive?: boolean; // show daemon-owned Live conversations (default: false) branding?: false | WebShellSidebarBranding; primaryNav?: WebShellSidebarPrimaryNavOptions; hideProjectHeader?: boolean; // hide "Projects" header row (default: false = shown) sessionActions?: WebShellSidebarSessionActionsOptions; footer?: false | WebShellSidebarFooterOptions; }

Session source switch — showSessionSourceSwitch

Set showSessionSourceSwitch to false when an embedded host should show only ordinary task sessions:

sidebar={{ showSessionSourceSwitch: false, }}

This removes the Tasks/Channels switch and fixes every active, archived, primary, and secondary session query to sourceType: "default". Omitting the option keeps the current switch and channel-session access unchanged.

Live conversations — showLive

Live conversations are hidden from embedded hosts by default. Opt in when the host should expose the daemon-owned Live group:

Previous releases displayed this group without an explicit option, so hosts that rely on it must set showLive: true when upgrading.

sidebar={{ showLive: true, }}

③ Project Header — hideProjectHeader

Controls visibility of the “Projects” header row (the row with the collapse toggle, search icon, and add workspace button). Defaults to false (shown).

sidebar={{ hideProjectHeader: true, // hide the "项目 ▼ [🔍] [+]" row }}

When hidden, the session list entries and archived sessions are still shown — the header row with its action buttons and the session search bar are removed.

Session Row Actions — sessionActions

type WebShellSidebarSessionActionItem = | 'details' // 📝 Details (dropdown sub-menu) | 'rename' // ✏️ Rename (dropdown menu) | 'group' // 📁 Group/Move to folder (dropdown menu) | 'export' // 📤 Export chat history (dropdown menu) | 'delete' // 🗑 Delete session (dropdown menu) | 'pin' // 📌 Pin/Unpin (inline button) | 'archive'; // 📦 Archive (dropdown menu) /** Subset with working inline (hover-button) handlers. */ type WebShellSidebarSessionInlineActionItem = | 'pin' | 'rename' | 'export' | 'delete'; interface WebShellSidebarSessionActionsOptions { items?: readonly WebShellSidebarSessionActionItem[]; // which actions to show (default: all) inlineItems?: readonly WebShellSidebarSessionInlineActionItem[]; // which items appear as inline buttons (default: ['pin']) }

Controls which action buttons appear on session rows:

  • items: Master control for all actions (both inline and dropdown). If an item is not in items, it’s hidden everywhere.
  • inlineItems: Controls which items appear as inline buttons (on hover). Defaults to ['pin']. Only items with working inline handlers can be used: 'pin', 'rename', 'export', 'delete'. 'details', 'group', and 'archive' are dropdown-only.

Visibility priority: Both items AND the item’s built-in condition AND inlineItems must all pass for the inline button to show. For example, delete as inline requires items to include 'delete' AND inlineItems to include 'delete'.

ValueEffect
undefined (default)All actions shown, pin as inline
{ inlineItems: ['pin', 'delete'] }Pin + delete as inline buttons
{ inlineItems: [] }No inline buttons at all
{ inlineItems: ['rename', 'export'] }Rename + export as inline buttons

The dropdown trigger (⋮) is automatically hidden when no dropdown items are enabled. Inline buttons are only shown when both their capability condition and items include them. Archive is disabled on the current session and on any session with a running turn, because the daemon closes the live session when it archives.

sidebar={{ sessionActions: { items: ['details', 'rename', 'export', 'delete', 'pin'], // which actions to show (master control) inlineItems: ['pin', 'delete'], // pin + delete as inline buttons }, }}

Non-customizable areas

Projects / Workspaces (inside session list)

When the session list is visible, the following sub-areas are rendered but not individually customizable:

AspectDetail
Data sourceuseSessions() hook → daemon API (/sessions endpoint)
Session list sortingBy creation time, descending
Session row renderingInternal renderSessionRow useCallback — not injectable
Search / filterBuilt-in search bar with client-side text matching
Session groupsSessionGroupSection component with 6 preset colors + custom hex
Workspace sectionsWorkspaceSection per daemon workspace, not replaceable
Add workspace dialogBuilt-in AddWorkspaceDialog

⑤ Resize handle

  • Drag handle on the right edge for resizing sidebar width
  • Width is persisted in localStorage
  • Not configurable

Runtime behavior props

These WebShellProps affect sidebar behavior indirectly:

PropEffect
onNewSessionOverride the new-session handler
onLoadSessionOverride session loading logic
onSessionIdChangeReact to session switches
splitSessionIdsControl split-view sessions externally
theme / onThemeChangeControl / observe theme
language / onLanguageChangeControl / observe UI language

Collapsed and mobile states

StateBehavior
ExpandedFull sidebar with text labels
CollapsedIcon-rail mode (logo, pen icon, action icons only)
MobileDrawer uses 70% of its container, within width limits, with backdrop and footer close controls

Collapse state is persisted in localStorage under the key qwen-code-web-shell-sidebar-collapsed.

The resized desktop width is restored only in expanded layouts. Opening or closing the mobile drawer does not overwrite that width or the persisted desktop collapse preference.

Source locations

ComponentFile
WebShellSidebarpackages/web-shell/client/components/sidebar/WebShellSidebar.tsx
SessionGroupSectionpackages/web-shell/client/components/sidebar/SessionGroupSection.tsx
WorkspaceSectionpackages/web-shell/client/components/sidebar/WorkspaceSection.tsx
Sidebar stylespackages/web-shell/client/components/sidebar/WebShellSidebar.module.css
App integrationpackages/web-shell/client/App.tsx (search WebShellSidebar)
Entry point (dev)packages/web-shell/client/main.tsx (sidebar: { enabled: true, showLive: true })
Last updated on