How the sidebar navigates, animates, and responds to the user. Covers every state, transition, and interaction decision.
Terms used throughout this document.
| Term | Meaning |
|---|---|
| L1 | The narrow 64 px navigation column fixed to the left edge of the screen. It holds the logo, section buttons, and the profile button. It is always visible. |
| L2 panel | The 256 px panel that slides out from behind the L1 column when the user hovers or clicks an L1 button. It shows the full menu for the active section. |
| L1 button | One of the icon buttons in the L1 column. Each button corresponds to a section (e.g. Accounting, Banking) and opens that section in the L2 panel. |
| Rail | The vertical chain of keyboard-focusable elements inside the L1 column: the logo, all L1 buttons, and the profile button. Arrow keys move focus along the rail. |
The navigation is split into two layers. A narrow 64 px L1 navigation is always visible on the left edge. When the user engages with it, a 256 px L2 panel slides in and reveals the full menu for that section.
The L2 panel is not a page or modal. It overlays the content area temporarily. Once the user clicks a destination, the L2 closes and the page updates.
The sidebar should feel lightweight and fast. It is always visible and reachable, at minimum in its collapsed state.
The L2 panel opens on hover. When the cursor enters an L1 button, or anywhere in the vertical span between the first button and the last, the L2 panel appears. The relevant section scrolls into position aligned with the nearest button.
If the L2 panel is already open and a different L1 button is hovered, the L2 stays visible and smoothly scrolls to the new section. There is no close-and-reopen flicker.
Clicking an L1 button immediately navigates to the first item of that section. The page title updates and the item becomes selected. It is equivalent to hovering the button and clicking the first menu item in one gesture.
If the L2 panel is already open, switching sections animates the scroll. The L2 does not close and reopen. Fast scanning feels fluid rather than jumpy.
The L2 panel closes as soon as the user moves their cursor away from both the sidebar and the L2 panel. It also closes when a menu item is clicked.
| Action | Result |
|---|---|
| Cursor leaves the L2 panel | L2 panel closes |
| Click a menu item | L2 panel closes, title updates |
| Click the logo | L2 panel closes, title resets to Dashboard |
| Unpin the menu | L2 panel closes |
After closing, the L2 panel's scroll position resets silently after 240ms. This is safely after the closing animation finishes at 150ms. The next time the L2 panel opens, scroll is always at the correct position.
The L2 panel animates on two properties at once: opacity and a horizontal shift. Together they make the L2 panel feel like it emerges from behind the L1 navigation rather than appearing from nowhere.
Opening is intentionally slower than closing. The L2 panel takes its time arriving so it feels considered. It gets out of the way quickly so it never feels like a blocker.
When the operating system has "Reduce Motion" enabled, all L2 panel transitions are disabled. The L2 panel still appears and disappears, just instantly with no animation.
Each section in the L2 panel aligns with its corresponding button. When you hover the Accounting button, the first Accounting item lines up with that button, not the top of the L2 panel.
This creates a spatial relationship between the L1 button and the content. The user's eye does not have to travel. The relevant content is right next to the thing they pointed at.
The alignment is not decorative. It reduces the cognitive gap between "I hovered this button" and "here is what belongs to it." The two things feel like one thing.
When the L2 panel is already open and the user moves to a different section, the scroll animates. Duration scales with distance. Short jumps feel quick and long jumps feel deliberate. It is capped so it never drags.
Sections near the bottom of the sidebar need extra space below them so the L2 panel can scroll them into the aligned position. The L2 panel temporarily extends its own scrollable area to allow this, then resets silently when it closes.
A pin button in the top-right corner of the L2 panel lets users keep the menu permanently visible. Pinning changes both the behaviour and the visual presentation.
L2 panel floats over content. Appears and disappears on hover. Content remains full width behind it.
L2 panel stays visible at all times. Content area shifts right by 256 px. Sidebar and L2 panel share one unified background.
Clicking a menu item commits the user to a destination. The selection is reflected in three places at once: the menu item itself, the L1 button for that section, and the page title.
| Element | Selected state |
|---|---|
| Menu item | Refer to Figma for color values. |
| L1 button | Refer to Figma for color values. |
| Page title | Updates to the selected item's label |
Hovering an L1 button while pinned still navigates to that section and scrolls to highlight it. Once the cursor moves into the L2 panel, the active state is cleared and all sections return to their neutral state. Moving the cursor out of the L2 panel without going toward the sidebar also clears any active state.
The page title updates when the user selects a destination. Hovering alone never changes it. Clicking an L1 button commits to the first item of that section, so the title updates on button click too.
| Action | Title becomes |
|---|---|
| Click the logo | Dashboard |
| Click an L1 button | The first item of that section, e.g. clicking Banking sets the title to Bank transactions |
| Click a menu item in the L2 panel | The item name, e.g. Bank transactions |
Hovering an L1 button opens the L2 panel but does not update the title. Only an explicit click on an L1 button, a menu item, or the logo commits to a page.
The profile dropdown and domain picker use the same opacity and directional shift pattern as the main L2 panel. Each has its own axis, anchor, and trigger.
Opens and closes by clicking the profile avatar. It slides upward as it appears. Clicking anywhere outside closes it.
Opens when the cursor enters the domain item inside the profile dropdown. It slides in from the left as it appears. Moving the cursor between the domain item and the picker keeps it open. There is no accidental dismissal during that transition.
The sidebar, L2 panel, profile dropdown, and domain picker are all fully keyboard-navigable. Tab moves between major interactive elements. Arrow keys move within a component once it is open.
The logo, all L1 buttons, and the profile button form a single vertical navigation chain. ↑ and ↓ move through this chain without opening the L2 panel. The L2 panel only opens on an explicit → or Enter.
| Key | Result |
|---|---|
| Tab | Move focus between the logo, L1 buttons, and the profile button |
| ↓ Arrow Down | Focus next item in the rail (logo → L1 buttons → profile). Stops at the profile button and does not wrap |
| ↑ Arrow Up | Focus previous item in the rail. Stops at the logo and does not wrap |
| → Arrow Right | Open the L2 panel for the focused L1 button; focus moves to the first item in the section |
| Enter / Space | Open the L2 panel for that L1 button; focus moves to the first item in the section |
| Enter / Space (L2 panel already open for same button) | Close the L2 panel |
| Enter / Space (on the logo) | Navigate to Dashboard, same as clicking the logo |
Once the L2 panel is open, arrow keys navigate across the pin button and all menu items, crossing section boundaries freely. The pin button sits at the top of the cycle. As focus moves, the active section updates to match. Navigating with arrows does not close the L2 panel.
| Key | Result |
|---|---|
| ↓ Arrow Down | Focus next item; from the last item wraps up to the pin button |
| ↑ Arrow Up | Focus previous item; from the first item moves up to the pin button |
| ← Arrow Left | Close L2 panel, return focus to the L1 button that opened it |
| Enter | Select item and update the page title. If unpinned, the L2 panel closes and focus returns to the L1 button. If pinned, focus stays on the item. |
| Escape | Close L2 panel, return focus to the L1 button that opened it |
| Tab | Close L2 panel, move focus to the next focusable element in the page |
Pressing any navigation key (arrows, Tab, Enter, Escape, or Space) enters keyboard mode. In keyboard mode, all CSS hover effects are suppressed. Button background highlights and icon colour changes are hidden so the mouse's physical position cannot interfere with keyboard navigation visuals. Keyboard mode ends the moment the mouse moves.
When the L2 panel is pinned, pressing Enter on a menu item keeps focus on that item. Focus only returns to the L1 button when the panel is unpinned.
| Key | Result |
|---|---|
| Enter / Space | Open dropdown; focus moves to first item |
| ↓ / ↑ | Navigate items; wraps at both ends |
| Escape | Close dropdown, return focus to profile button |
| Tab (when open) | Close dropdown, move focus out of the component |
The domain picker is nested inside the profile dropdown. With a mouse, it opens on hover of the domain row. With a keyboard, it is opened deliberately.
| Key | Result |
|---|---|
| Enter / Space / → | Open picker from the domain row; focus moves to the search input |
| ↓ Arrow Down | Move from search input to first domain item; then cycle forward |
| ↑ Arrow Up | Move to previous item; from the first item, returns focus to search input |
| Enter (on item) | Select domain |
| Escape / ← | Close picker, return focus to domain row |
Keyboard focus is always clearly indicated. Display quality adapts to screen hardware.
All interactive elements share the same focus ring: a soft dark shadow with no outline. It appears only on keyboard navigation (:focus-visible) so mouse users never see it.
When the L2 panel is not visible, it carries the inert attribute. This removes all its children, including all menu items, from the tab order and accessibility tree entirely. Tab navigation never silently cycles through hidden items.
| Element | Cursor |
|---|---|
| All interactive elements (buttons, items, profile, dropdowns) | pointer |
| Pin button when L2 panel is pinned (unpin action) | w-resize, communicates "collapse left" |
Antialiasing is applied only on high-DPI displays (at least 2x pixel ratio / 192 dpi). On standard-density screens the browser's default subpixel rendering is preserved. This produces sharper results at lower pixel density.