Skip to content

Sidebar

Sidebar is the collapsible-rail flagship of the styled layer: a flat ItemsControl of NaviusSidebarSection and/or bare NaviusSidebarItem children (the same manual-composition anatomy NaviusBreadcrumb uses), plus optional header/footer content slots and a built-in collapse toggle. IsCollapsed is registered via DependencyProperty.RegisterAttached (with GetIsCollapsed/SetIsCollapsed accessors) carrying FrameworkPropertyMetadataOptions.Inherits, so every descendant (section headers, item labels, the collapse toggle) reacts to it through a plain template trigger with no manual state push-down. The attached registration is what makes the inheritance real: Register-applied Inherits metadata never leaves the owner type, so other element types would read the false default forever. Roving keyboard focus (ArrowUp/ArrowDown/Home/End) walks the realized visual tree for NaviusSidebarItem instances in visual order, flattening across section boundaries deliberately: the contract is one continuous nav list, and sections are only visual grouping. The index math lives in the SidebarNavigation.MoveFocus/ToggleCollapsed helpers, free of any UI-thread/Application dependency (though MoveFocus still takes a System.Windows.Input.Key, so it is UI-thread/Application-free but not WPF-assembly-free), unit tested directly.

  • Expanded (IsCollapsed = false, the default): the root is ExpandedWidth (240px by default). Each NaviusSidebarItem shows its icon plus label, section headers are visible, and the footer toggle button reads “Collapse” with a left-pointing chevron.
  • Collapsed (IsCollapsed = true): the root animates (or snaps, see below) to CollapsedWidth (64px by default). Item labels and section headers collapse to Visibility.Collapsed via DataTriggers bound to the inherited NaviusSidebar.IsCollapsed value, each item’s icon re-centers, and the footer toggle’s label hides while its chevron flips to point right.
  • The transition between the two is a 150ms CubicEase-out DoubleAnimation on the root part’s Width, started from OnIsCollapsedChanged. When ReducedMotion.AnimationsEnabled is false, or there is no live PresentationSource (design time / not yet shown), the width is set instantly instead of animated. The instant-set path was a confirmed M6 audit fix: AnimateWidth originally ran the animation unconditionally with no reduced-motion guard, unlike the sibling looping animations in Skeleton/Spinner; it now short-circuits through the same ReducedMotion.AnimationsEnabled seam via a using Navius.Wpf.Ui.Internal; import.
xmlns:sidebar="clr-namespace:Navius.Wpf.Ui.Sidebar;assembly=Navius.Wpf.Ui"
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="pack://application:,,,/Navius.Wpf.Ui;component/Themes/Sidebar.xaml" />
</ResourceDictionary.MergedDictionaries>
<sidebar:NaviusSidebar x:Name="Sidebar">
<sidebar:NaviusSidebar.HeaderContent>
<TextBlock Text="Acme" FontWeight="Bold" />
</sidebar:NaviusSidebar.HeaderContent>
<sidebar:NaviusSidebarSection Header="General">
<sidebar:NaviusSidebarItem Content="Dashboard" IsActive="True">
<sidebar:NaviusSidebarItem.Icon>
<Path Data="M0,0 L10,0 L10,10 L0,10 Z" Fill="{DynamicResource Navius.Foreground}" />
</sidebar:NaviusSidebarItem.Icon>
</sidebar:NaviusSidebarItem>
<sidebar:NaviusSidebarItem Content="Analytics" />
</sidebar:NaviusSidebarSection>
</sidebar:NaviusSidebar>
Property Type Default From Description
IsCollapsed bool false NaviusSidebar Two-way bindable by default. Registered via RegisterAttached with Inherits (plus GetIsCollapsed/SetIsCollapsed accessors), so descendants of any element type read the same value through property value inheritance without a manual push-down — Register-applied Inherits never propagates beyond the owner type.
ExpandedWidth double 240 NaviusSidebar Root width while expanded.
CollapsedWidth double 64 NaviusSidebar Root width while collapsed.
HeaderContent object? null NaviusSidebar Content above the item list (e.g. a logo/brand lockup).
FooterContent object? null NaviusSidebar Content below the item list, above the built-in collapse toggle (e.g. a user/account row).
Icon object? null NaviusSidebarItem Icon content, shown at a fixed width whether the sidebar is collapsed or expanded.
IsActive bool false NaviusSidebarItem True for the current page/section; renders the Accent wash and a left-edge indicator bar. Setting it applies (or clears) AutomationProperties.ItemStatus.
Header object? null HeaderedItemsControl (via NaviusSidebarSection) The section label; hidden via template trigger while IsCollapsed.
Command Description
NaviusSidebar.ToggleCollapsedCommand Flips IsCollapsed. Bound to the built-in footer toggle button in Themes/Sidebar.xaml; a consumer can bind any other element to it too.
Key Behavior
Down Moves focus to the next NaviusSidebarItem in visual order (flattened across sections). No wrap: staying on the last item if already there.
Up Moves focus to the previous item. No wrap: staying on the first item if already there.
Home Moves focus to the first item.
End Moves focus to the last item.

NaviusSidebar ships NaviusSidebarAutomationPeer (a FrameworkElementAutomationPeer reporting AutomationControlType.Group, class name NaviusSidebar) rather than relying on the default ItemsControl peer: ItemsControlAutomationPeer’s children are only item peers for the Items collection, which dropped FooterContent descendants and the built-in collapse button from the UIA tree entirely (navius-wpf issue #28). Deriving FrameworkElementAutomationPeer restores the default visual-tree child walk, so footer descendants and the collapse button now appear in the Control view; Group is the closest fit because UIA has no navigation-landmark control type. The collapse button is a regular Button — its peer exposes InvokePattern natively — and carries a state-aware accessible name (“Collapse sidebar” / “Expand sidebar”) via a style DataTrigger on the inherited IsCollapsed value, since its visible “Collapse” label hides while collapsed and would otherwise leave it nameless. Covered by NaviusSidebar_AutomationPeer_ExposesFooterAndCollapseDescendants, NaviusSidebar_CollapseButton_AccessibleNameReflectsState, and NaviusSidebar_IsCollapsed_InheritsToDescendantsAndCollapsesItemLabels in UiSidebarTests.cs. NaviusSidebarItem derives ButtonBase directly rather than the concrete Button type, and now ships its own NaviusSidebarItemAutomationPeer (a FrameworkElementAutomationPeer implementing IInvokeProvider), the same shape as NaviusBreadcrumbItem’s peer: it reports AutomationControlType.Button, throws ElementNotEnabledException from Invoke when disabled, and otherwise queues activation onto the item’s dispatcher (DispatcherPriority.Input) per the UIA Invoke contract’s requirement to return immediately, running it through ButtonBase.OnClick rather than a bare Click raise so a bound Command executes. Another automation touch is imperative: OnIsActiveChanged calls AutomationProperties.SetItemStatus(d, "current") when IsActive becomes true and clears it otherwise, verified by NaviusSidebarItem_IsActive_SetsItemStatusAutomationProperty. The unit suite (UiSidebarTests.cs) also exercises the item peer’s Button control type, the Invoke pattern’s presence, the disabled-throws behavior, and that an enabled Invoke raises Click and executes a bound Command exactly once the dispatcher queue is pumped; it otherwise covers SidebarNavigation.MoveFocus/ToggleCollapsed as pure functions and the NaviusSidebar defaults (IsCollapsed, ExpandedWidth, CollapsedWidth) and ToggleCollapsedCommand round trip.