Skip to content
Star on GitHub
WorkspaceWorkspace rail
Style
Accent
Sound

Built by DesignMe, free to use under the MIT licence.

Expert design and dev support for startups and enterprises.

Book a call

Workspace rail

A slim app rail that opens into a labelled sidebar. Free and MIT licensed, as HTML or a typed React component, in two styles with light and dark mode.

$npx designme add workspace-rail
01

Install

Add it to your project with the CLI.

HTML, CSS and JavaScript

$npx designme add workspace-rail

React

$npx designme add workspace-rail --react

The CLI copies the source into your project, so you own it and can change anything. The HTML version lands in ./designme with the shared core and a demo.html. The React version lands in src/components/designme and needs React 19; it works in Next.js, Vite and Astro.

02

About

What it is for and what it does.

The main navigation for an agent app: a narrow column of icons that can be pinned open into a sidebar with labels. A marker slides to the open item, icons can carry a count, and agent runs in progress are listed near the bottom so people can see what is still working. The page for the open item sits beside it.

  • As the left-hand navigation of an agent app or workspace.
  • When screen space matters and the sidebar should fold down to icons.
  • When people should see which agent runs are still going from anywhere in the app.
  • When an item needs an unread or pending count on its icon.
03

Usage

Import it and drive it from your own state.

Import

import { WorkspaceRail } from '@/components/designme/workspace-rail/WorkspaceRail';

Example

The demo above as a React component, 30 lines you can copy.

'use client';
// The animated preview from designme.dev, rebuilt from the React component: an example of driving it from your own state.
import { useRef, useState } from 'react';
import { WorkspaceRail, type WorkspaceRailItem } from './WorkspaceRail';
import { useStory } from '../lib/useScript';

/* ---------- Workspace rail: opens out a moment after it appears; opening an item clears its badge ---------- */
const ITEMS: WorkspaceRailItem[] = [
  { key: 'home', label: 'Home', icon: 'home' }, { key: 'agent', label: 'Agent', icon: 'spark' }, { key: 'projects', label: 'Projects', icon: 'layers' },
  { key: 'frames', label: 'Frames', icon: 'figma' }, { key: 'memory', label: 'Memory', icon: 'book' }, { key: 'settings', label: 'Settings', icon: 'gear' }
];

export default function WorkspaceRailDemo() {
  const [active, setActive] = useState('agent');
  const [badges, setBadges] = useState<Record<string, number>>({ agent: 2 });
  const [wide, setWide] = useState(false);
  const ref = useRef<HTMLDivElement>(null);
  // the preview pins the rail open on its own, unless you take over first
  useStory(ref, async ({ wait }) => { await wait(1500); setWide(true); });
  const open = (key: string) => { setActive(key); setBadges(b => ({ ...b, [key]: 0 })); };

  return (
    <div ref={ref} style={{ width: '100%' }}>
      <WorkspaceRail items={ITEMS.map(i => ({ ...i, badge: badges[i.key] }))} value={active} onValueChange={open}
        expanded={wide} onExpandedChange={setWide}
        runs={[{ key: 'audit', label: 'Pricing audit' }, { key: 'tokens', label: 'Token export' }]} />
    </div>
  );
}
04

Props

Everything WorkspaceRail takes.

PropTypeWhat it does
itemsrequiredWorkspaceRailItem[]
valuestringThe open item. Leave it out to let clicks pick it
defaultValuestring
onValueChange(key: string) => void
expandedbooleanWhether the rail shows its labels. Leave it out to let the pin button toggle it
defaultExpandedboolean
onExpandedChange(expanded: boolean) => void
workspace{ name: string; detail?: string; logo?: ReactNode }The workspace at the top. Defaults to DesignMe, Studio
runsWorkspaceRailRun[]Agent runs in progress, listed above the pin button
runsLabelstring
shortcutstringThe shortcut shown in the pin button's tooltip
childrenReactNodeThe page beside the rail. Defaults to the open item's name over placeholder lines
classNamestring
05

Code

The full source, as the CLI installs it.

WorkspaceRail.tsxReact · 119 lines
'use client';
import { useEffect, useRef, useState, type ReactNode } from 'react';
import { cx } from '../lib/cx';
import { useControllable } from '../lib/hooks';
import { Icon, type IconName } from '../lib/icons';
import { Swap, Think, Tip } from '../lib/motion';
import '../designme.css';
import './workspace-rail.css';

export interface WorkspaceRailItem {
  key: string;
  label: string;
  icon: IconName;
  /** A count on the icon, like unread messages. Clear it and it leaves */
  badge?: number;
}

export interface WorkspaceRailRun {
  key: string;
  /** What is running, like "Pricing audit" */
  label: string;
  onClick?: () => void;
}

export interface WorkspaceRailProps {
  items: WorkspaceRailItem[];
  /** The open item. Leave it out to let clicks pick it */
  value?: string;
  defaultValue?: string;
  onValueChange?: (key: string) => void;
  /** Whether the rail shows its labels. Leave it out to let the pin button toggle it */
  expanded?: boolean;
  defaultExpanded?: boolean;
  onExpandedChange?: (expanded: boolean) => void;
  /** The workspace at the top. Defaults to DesignMe, Studio */
  workspace?: { name: string; detail?: string; logo?: ReactNode };
  /** Agent runs in progress, listed above the pin button */
  runs?: WorkspaceRailRun[];
  runsLabel?: string;
  /** The shortcut shown in the pin button's tooltip */
  shortcut?: string;
  /** The page beside the rail. Defaults to the open item's name over placeholder lines */
  children?: ReactNode;
  className?: string;
}

const LOGO = (
  <svg viewBox="-20 -83 799 799" aria-hidden="true" className="dm-logo"><path fill="currentColor" d="M149.497 283.784C149.477 284.047 149.474 284.311 149.487 284.575C156.385 340.034 183.548 391.116 225.9 428.196C267.989 465.044 322.188 485.532 378.414 485.855L379.743 485.859C436.453 485.868 491.188 465.353 533.619 428.212C575.912 391.194 603.062 340.216 610.018 284.856L610.018 281.512C610.008 277.337 608.324 273.315 605.303 270.335C602.277 267.351 598.155 265.656 593.837 265.656L165.647 265.656C161.338 265.656 157.226 267.349 154.21 270.331C151.198 273.309 149.524 277.326 149.523 281.491L149.523 283.437L149.497 283.784ZM114.378 247.395C118.396 247.391 122.254 245.919 125.214 243.29C128.063 240.759 129.887 237.33 130.396 233.629L130.396 233.35L130.437 232.917C136.355 171.634 165.162 114.754 211.202 73.3687C257.239 31.9859 317.206 9.06063 379.393 9.04448L379.396 9.04448C441.593 9.04583 501.574 31.964 547.624 73.3482C593.677 114.735 622.491 171.624 628.411 232.917L628.453 233.35L628.453 233.643C628.953 237.344 630.773 240.774 633.621 243.304C636.58 245.932 640.437 247.399 644.452 247.395L725.644 247.395C728.882 247.404 732.089 248.06 735.071 249.329C738.055 250.598 740.753 252.453 743.001 254.787C745.252 257.123 747.005 259.891 748.151 262.924C749.296 265.954 749.81 269.183 749.662 272.418L749.663 272.418C745.484 366.797 704.617 455.919 635.615 521.211C566.616 586.499 474.809 622.924 379.338 622.924C283.872 622.924 192.071 586.496 123.081 521.206C54.0935 455.917 13.2412 366.804 9.07133 272.438L9.07036 272.438L9.07036 272.436C9.07007 272.43 9.06967 272.423 9.06938 272.417L9.07036 272.417C8.92272 269.182 9.43655 265.953 10.5811 262.924C11.7276 259.89 13.481 257.123 15.7315 254.787C17.9805 252.452 20.6797 250.597 23.6622 249.329C26.6443 248.06 29.8504 247.404 33.0889 247.395L114.378 247.395Z" /></svg>
);

/** A count on an icon. When it is cleared it plays its exit before it goes */
function Badge({ count }: { count?: number }) {
  const [kept, setKept] = useState(count);
  const [leaving, setLeaving] = useState(false);
  useEffect(() => {
    if (count) { setKept(count); setLeaving(false); return; }
    setLeaving(true);
    const t = setTimeout(() => { setKept(undefined); setLeaving(false); }, 300);
    return () => clearTimeout(t);
  }, [count]);
  return kept ? <span className={cx('dm-bd', leaving && 'dm-leave')}>{kept}</span> : null;
}

/** A sidebar that folds to icons, with a sliding marker on the open item and the agent runs in progress. */
export function WorkspaceRail({ items, value, defaultValue, onValueChange, expanded, defaultExpanded = false, onExpandedChange,
  workspace, runs, runsLabel = 'Running', shortcut = '⌘B', children, className }: WorkspaceRailProps) {
  const [active, setActive] = useControllable(value, defaultValue ?? items[0]?.key ?? '', onValueChange);
  const [wide, setWide] = useControllable(expanded, defaultExpanded, onExpandedChange);
  const refs = useRef(new Map<string, HTMLButtonElement>());
  // the marker slides to the open item; it is placed after the first paint, so it slides in from the top as on the site
  const [top, setTop] = useState<number>();
  useEffect(() => { const b = refs.current.get(active); if (b) setTop(b.offsetTop); }, [active, items.length]);
  const ws = workspace ?? { name: 'DesignMe', detail: 'Studio' };
  const current = items.find(i => i.key === active);

  return (
    <div data-dm="" className={cx('dm-wrx dm-panel', wide && 'dm-wide', className)}>
      <nav className="dm-wrx-rail" aria-label="Workspace">
        <div className="dm-wrx-ws">
          <span className="dm-wrx-lg">{ws.logo ?? LOGO}</span>
          <span className="dm-wrx-lb"><b>{ws.name}</b>{ws.detail && <span className="dm-muted">{ws.detail}</span>}</span>
        </div>
        <div className="dm-wrx-nav">
          <span className="dm-wrx-ind" style={top === undefined ? undefined : { top }} />
          {items.map(it => (
            <Tip key={it.key} label={it.label} off={wide}>
              <button type="button" ref={el => { if (el) refs.current.set(it.key, el); else refs.current.delete(it.key); }}
                className={cx('dm-wrx-i', it.key === active && 'dm-on')} aria-label={it.label} aria-current={it.key === active ? 'page' : undefined}
                onClick={() => setActive(it.key)}>
                <Icon name={it.icon} /><Badge count={it.badge} /><span className="dm-wrx-lb">{it.label}</span>
              </button>
            </Tip>
          ))}
        </div>
        <div className="dm-wrx-sp" />
        {runs && runs.length > 0 && (
          <div className="dm-wrx-runs">
            <span className="dm-wrx-lbh">{runsLabel}</span>
            {runs.map(r => (
              <Tip key={r.key} label={r.label} off={wide}>
                <button type="button" className="dm-wrx-run" onClick={r.onClick}><Think /><span className="dm-wrx-lb">{r.label}</span></button>
              </Tip>
            ))}
          </div>
        )}
        <Tip label={wide ? 'Collapse' : 'Pin open'} kbd={shortcut}>
          <button type="button" className="dm-wrx-i dm-wrx-pin" aria-label="Pin sidebar" aria-expanded={wide} onClick={() => setWide(!wide)}>
            <Icon name="panel" /><span className="dm-wrx-lb">Collapse</span>
          </button>
        </Tip>
      </nav>
      <div className="dm-wrx-main">
        <div className="dm-wrx-mh"><Swap>{current?.label ?? ''}</Swap></div>
        {children ?? <div className="dm-wrx-sk"><i style={{ width: '70%' }} /><i style={{ width: '92%' }} /><i style={{ width: '56%' }} /><i className="dm-b" /></div>}
      </div>
    </div>
  );
}
workspace-rail.cssReact · 33 lines
/* Generated from designme.dev by scripts/sync.mjs. Edit the site, then sync. */
/* ---------- Workspace rail ---------- */
.dm-wrx{display:flex;min-height:330px;overflow:hidden}
.dm-wrx-rail{width:56px;flex:none;display:flex;flex-direction:column;gap:2px;padding:10px 9px;border-right:1px solid color-mix(in srgb,var(--dm-line) 50%,transparent);transition:width .5s var(--dm-ease);overflow:hidden}
.dm-wrx.dm-wide .dm-wrx-rail{width:188px}
.dm-wrx-ws{display:flex;align-items:center;gap:10px;padding:2px 2px 10px;white-space:nowrap}
.dm-wrx-lg .dm-logo{width:20px;height:20px}
.dm-wrx-lg{width:34px;height:34px;border-radius:11px;background:var(--dm-ink);color:var(--dm-on-ink);display:grid;place-items:center;font-weight:500;flex:none}
.dm-wrx-lb{white-space:nowrap;opacity:0;transform:translateX(-6px);filter:blur(3px);transition:opacity .3s,transform .45s var(--dm-ease),filter .3s;display:flex;flex-direction:column;font-size:13px;line-height:1.3}
.dm-wrx-lb .dm-muted{font-size:11.5px}
.dm-wrx.dm-wide .dm-wrx-lb{opacity:1;transform:none;filter:none;transition-delay:.08s}
.dm-wrx-nav{display:flex;flex-direction:column;gap:2px;position:relative}
.dm-wrx-ind{position:absolute;left:0;right:0;height:36px;border-radius:11px;background:var(--dm-chip);transition:top .45s var(--dm-ease);top:0}
.dm-wrx-i{position:relative;display:flex;align-items:center;gap:var(--dm-s-3);height:36px;padding:0 10px;border-radius:11px;color:var(--dm-fg2);white-space:nowrap;transition:color .2s}
@media (hover:hover) and (pointer:fine){.dm-wrx-i:hover{color:var(--dm-fg)}}
.dm-wrx-i.dm-on{color:var(--dm-fg)}
.dm-wrx-i .dm-bd{position:absolute;left:19px;top:4px;min-width:15px;height:15px;padding:0 var(--dm-s-1);border-radius:var(--dm-r-sm);background:var(--dm-accent);color:var(--dm-on-accent);font-size:9.5px;font-weight:500;display:grid;place-items:center;box-shadow:0 0 0 2px var(--dm-surface)}
.dm-wrx-i .dm-wrx-lb{flex-direction:row}
.dm-wrx-sp{flex:1}
.dm-wrx-runs{display:flex;flex-direction:column;gap:2px;margin-bottom:6px}
.dm-wrx-lbh{font-size:11px;color:var(--dm-muted);padding:0 10px var(--dm-s-1);opacity:0;height:0;overflow:hidden;transition:opacity .3s}
.dm-wrx.dm-wide .dm-wrx-lbh{opacity:1;height:auto}
.dm-wrx-run{display:flex;align-items:center;gap:var(--dm-s-3);height:32px;padding:0 11px;border-radius:10px;font-size:12.5px;color:var(--dm-fg2);white-space:nowrap}
@media (hover:hover) and (pointer:fine){.dm-wrx-run:hover{background:var(--dm-row)}}
.dm-wrx-run .dm-think{color:var(--dm-accent-fg,var(--dm-accent))}
.dm-wrx-pin svg{transition:transform .5s var(--dm-ease)}
.dm-wrx.dm-wide .dm-wrx-pin svg{transform:scaleX(-1)}
.dm-wrx-main{flex:1;min-width:0;padding:18px;display:flex;flex-direction:column;gap:14px}
.dm-wrx-mh{font-size:14px;font-weight:500}
.dm-wrx-sk{display:flex;flex-direction:column;gap:9px}
.dm-wrx-sk i{display:block;height:9px;border-radius:5px;background:var(--dm-chip)}
.dm-wrx-sk i.dm-b{height:120px;width:100%;border-radius:14px;margin-top:6px}
workspace-rail.htmlHTML · 15 lines
<div class="card" data-c="workspace-rail">
  <div class="stage-in">
    <div class="wrx panel">
      <nav class="wrx-rail" aria-label="Workspace">
        <div class="wrx-ws"><span class="wrx-lg"><svg viewBox="-20 -83 799 799" aria-hidden="true" class="logo"><path d="M149.497 283.784C149.477 284.047 149.474 284.311 149.487 284.575C156.385 340.034 183.548 391.116 225.9 428.196C267.989 465.044 322.188 485.532 378.414 485.855L379.743 485.859C436.453 485.868 491.188 465.353 533.619 428.212C575.912 391.194 603.062 340.216 610.018 284.856L610.018 281.512C610.008 277.337 608.324 273.315 605.303 270.335C602.277 267.351 598.155 265.656 593.837 265.656L165.647 265.656C161.338 265.656 157.226 267.349 154.21 270.331C151.198 273.309 149.524 277.326 149.523 281.491L149.523 283.437L149.497 283.784ZM114.378 247.395C118.396 247.391 122.254 245.919 125.214 243.29C128.063 240.759 129.887 237.33 130.396 233.629L130.396 233.35L130.437 232.917C136.355 171.634 165.162 114.754 211.202 73.3687C257.239 31.9859 317.206 9.06063 379.393 9.04448L379.396 9.04448C441.593 9.04583 501.574 31.964 547.624 73.3482C593.677 114.735 622.491 171.624 628.411 232.917L628.453 233.35L628.453 233.643C628.953 237.344 630.773 240.774 633.621 243.304C636.58 245.932 640.437 247.399 644.452 247.395L725.644 247.395C728.882 247.404 732.089 248.06 735.071 249.329C738.055 250.598 740.753 252.453 743.001 254.787C745.252 257.123 747.005 259.891 748.151 262.924C749.296 265.954 749.81 269.183 749.662 272.418L749.663 272.418C745.484 366.797 704.617 455.919 635.615 521.211C566.616 586.499 474.809 622.924 379.338 622.924C283.872 622.924 192.071 586.496 123.081 521.206C54.0935 455.917 13.2412 366.804 9.07133 272.438L9.07036 272.438L9.07036 272.436C9.07007 272.43 9.06967 272.423 9.06938 272.417L9.07036 272.417C8.92272 269.182 9.43655 265.953 10.5811 262.924C11.7276 259.89 13.481 257.123 15.7315 254.787C17.9805 252.452 20.6797 250.597 23.6622 249.329C26.6443 248.06 29.8504 247.404 33.0889 247.395L114.378 247.395Z" fill="currentColor" /></svg></span><span class="wrx-lb"><b>DesignMe</b><span class="muted">Studio</span></span></div>
        <div class="wrx-nav"><span class="wrx-ind"></span></div>
        <div class="wrx-sp"></div>
        <div class="wrx-runs"><span class="wrx-lbh">Running</span></div>
        <button class="wrx-i wrx-pin" aria-label="Pin sidebar" data-tip="Pin open" data-tip-k="⌘B"><svg class="i s"><use href="#i-panel"/></svg><span class="wrx-lb">Collapse</span></button>
      </nav>
      <div class="wrx-main"><div class="wrx-mh"></div><div class="wrx-sk"><i style="width:70%"></i><i style="width:92%"></i><i style="width:56%"></i><i class="b"></i></div></div>
    </div>
  </div>
</div>
workspace-rail.cssHTML · 32 lines
/* ---------- Workspace rail ---------- */
.wrx{display:flex;min-height:330px;overflow:hidden}
.wrx-rail{width:56px;flex:none;display:flex;flex-direction:column;gap:2px;padding:10px 9px;border-right:1px solid color-mix(in srgb,var(--line) 50%,transparent);transition:width .5s var(--ease);overflow:hidden}
.wrx.wide .wrx-rail{width:188px}
.wrx-ws{display:flex;align-items:center;gap:10px;padding:2px 2px 10px;white-space:nowrap}
.wrx-lg .logo{width:20px;height:20px}
.wrx-lg{width:34px;height:34px;border-radius:11px;background:var(--ink);color:var(--on-ink);display:grid;place-items:center;font-weight:500;flex:none}
.wrx-lb{white-space:nowrap;opacity:0;transform:translateX(-6px);filter:blur(3px);transition:opacity .3s,transform .45s var(--ease),filter .3s;display:flex;flex-direction:column;font-size:13px;line-height:1.3}
.wrx-lb .muted{font-size:11.5px}
.wrx.wide .wrx-lb{opacity:1;transform:none;filter:none;transition-delay:.08s}
.wrx-nav{display:flex;flex-direction:column;gap:2px;position:relative}
.wrx-ind{position:absolute;left:0;right:0;height:36px;border-radius:11px;background:var(--chip);transition:top .45s var(--ease);top:0}
.wrx-i{position:relative;display:flex;align-items:center;gap:var(--s-3);height:36px;padding:0 10px;border-radius:11px;color:var(--fg2);white-space:nowrap;transition:color .2s}
@media (hover:hover) and (pointer:fine){.wrx-i:hover{color:var(--fg)}}
.wrx-i.on{color:var(--fg)}
.wrx-i .bd{position:absolute;left:19px;top:4px;min-width:15px;height:15px;padding:0 var(--s-1);border-radius:var(--r-sm);background:var(--accent);color:var(--on-accent);font-size:9.5px;font-weight:500;display:grid;place-items:center;box-shadow:0 0 0 2px var(--surface)}
.wrx-i .wrx-lb{flex-direction:row}
.wrx-sp{flex:1}
.wrx-runs{display:flex;flex-direction:column;gap:2px;margin-bottom:6px}
.wrx-lbh{font-size:11px;color:var(--muted);padding:0 10px var(--s-1);opacity:0;height:0;overflow:hidden;transition:opacity .3s}
.wrx.wide .wrx-lbh{opacity:1;height:auto}
.wrx-run{display:flex;align-items:center;gap:var(--s-3);height:32px;padding:0 11px;border-radius:10px;font-size:12.5px;color:var(--fg2);white-space:nowrap}
@media (hover:hover) and (pointer:fine){.wrx-run:hover{background:var(--row)}}
.wrx-run .think{color:var(--accent-fg,var(--accent))}
.wrx-pin svg{transition:transform .5s var(--ease)}
.wrx.wide .wrx-pin svg{transform:scaleX(-1)}
.wrx-main{flex:1;min-width:0;padding:18px;display:flex;flex-direction:column;gap:14px}
.wrx-mh{font-size:14px;font-weight:500}
.wrx-sk{display:flex;flex-direction:column;gap:9px}
.wrx-sk i{display:block;height:9px;border-radius:5px;background:var(--chip)}
.wrx-sk i.b{height:120px;width:100%;border-radius:14px;margin-top:6px}
workspace-rail.jsHTML · 16 lines
DM.def('workspace-rail', (r, c) => {
  r.innerHTML = r.innerHTML.replace('{panel}', I('panel'));
  const N = [['home','Home',0],['spark','Agent',2],['layers','Projects',0],['figma','Frames',0],['book','Memory',0],['gear','Settings',0]];
  const nav = $('.wrx-nav', r), ind = $('.wrx-ind', r), root = $('.wrx', r), mh = $('.wrx-mh', r);
  nav.insertAdjacentHTML('beforeend', N.map((n, i) => `<button class="wrx-i${i === 1 ? ' on' : ''}" data-i="${i}" aria-label="${n[1]}" data-tip="${n[1]}">${I(n[0])}${n[2] ? `<span class="bd">${n[2]}</span>` : ''}<span class="wrx-lb">${n[1]}</span></button>`).join(''));
  $('.wrx-runs', r).insertAdjacentHTML('beforeend', ['Pricing audit','Token export'].map(t => `<button class="wrx-run" data-tip="${t}"><span class="think on"><i></i><i></i><i></i><i></i></span><span class="wrx-lb">${t}</span></button>`).join(''));
  const items = $$('.wrx-i[data-i]', nav);
  function go(b){ items.forEach(x => x.classList.toggle('on', x === b)); ind.style.top = b.offsetTop + 'px'; swap(mh, N[+b.dataset.i][1]); const bd = $('.bd', b); if (bd){ bd.classList.add('leave'); c.later(300, () => bd.remove()); } }
  items.forEach(b => b.onclick = () => go(b));
  const tipSync = () => $$('[data-tip]', $('.wrx-rail', r)).forEach(el => { if (!el.dataset.t0) el.dataset.t0 = el.dataset.tip; if (root.classList.contains('wide') && !el.classList.contains('wrx-pin')) el.removeAttribute('data-tip'); else el.dataset.tip = el.dataset.t0; });
  $('.wrx-pin', r).onclick = () => { root.classList.toggle('wide'); const w = root.classList.contains('wide'); $('.wrx-pin', r).dataset.t0 = w ? 'Collapse' : 'Pin open'; tipSync(); };
  mh.textContent = 'Agent'; requestAnimationFrame(() => ind.style.top = items[1].offsetTop + 'px');
  // the preview pins the rail open once on its own, unless you take over first
  c.auto(1500, () => { root.classList.add('wide'); $('.wrx-pin', r).dataset.t0 = 'Collapse'; tipSync(); });
});
06

Questions

What people ask about it.

Does the rail bind the Cmd B shortcut for me?
No. shortcut is only the text shown in the pin button's tooltip. Listen for the key yourself and set expanded through your own state.
How do I clear a badge when someone opens that item?
Handle onValueChange, and in the same update set that item's badge to 0 or leave it out. The badge plays its exit before it goes.
How do I show agent runs that are in progress?
Pass runs with a key and label for each, plus an onClick if a run should open something. The section hides when the list is empty.
Can I put my own page next to the rail?
Yes. Pass it as children. Without children the rail shows the open item's label over placeholder lines, which is useful for prototypes.
07

More in Workspace

Navigation, search, settings, models and automations around the agent.

Automation canvasA trigger, a few agent steps, and a run you can watch flow through.
Canvas toolbarA floating toolbar for a workflow canvas: run, pick a tool, comment, fit and zoom.
Command paletteJump anywhere or hand a task to the agent from one box.
Memory panelWhat the agent remembers about your team, editable and removable.
Model compareRun one prompt on three models side by side and keep the best.
Agent settingsHow much the agent may do alone, with a save bar for changes.
Tools and connectionsThe tools the agent can reach, and how much it may do with each.
Knowledge sourcesThe files, sites and folders the agent knows, and how far each is indexed.
All 11 in Workspace →