VerticalNavigation
Use a vertical layout for global navigation on wide screens. treeview mode provides structured hierarchy but overrides standard keyboard navigation.
The tree defaults to mode="disclosure", which suits most apps. Set
mode="treeview" for arrow-key navigation — it overrides standard keyboard
behavior, so only use it when the hierarchy genuinely needs it.
Usage
HvVerticalNavigationTree renders a data array; HvVerticalNavigationActions pins actions to the bottom. Each child is separated by a divider, so grouping is a matter of how you nest them.
import { useMemo, useRef, useState } from "react"; export default function Demo() { const [selected, setSelected] = useState("analytics-reports"); const [query, setQuery] = useState(""); const [open, setOpen] = useState(true); const searchRef = useRef(null); const sections = useMemo( () => filterNavigation(mainSections, query), [query], ); const treeProps = { selected, onChange: (event, data) => setSelected(data.id), }; // on the collapsed rail the field becomes an icon that reopens the navigation const openSearch = () => { setOpen(true); requestAnimationFrame(() => searchRef.current?.focus()); }; return ( <HvVerticalNavigation open={open} useIcons> <HvVerticalNavigationTree aria-label="Quick access" data={quickAccess} {...treeProps} /> <section className="flex flex-col gap-sm"> {open ? ( <HvSearchInput ref={searchRef} aria-label="Menu search" placeholder="Menu search..." value={query} onChange={(event, value) => setQuery(value)} /> ) : ( <HvIconButton title="Menu search" onClick={openSearch} // minimized tree items center themselves; match them style={{ alignSelf: "center" }} > {/* the button resets the color the nav sets on its children */} <Search color="textLight" /> </HvIconButton> )} <HvVerticalNavigationTree // remounting re-applies defaultExpanded, so matches open on their own key={query} collapsible aria-label="Sections" defaultExpanded={query ? true : ["analytics"]} data={sections} {...treeProps} /> </section> <HvVerticalNavigationActions> <HvVerticalNavigationAction label={open ? "Collapse menu" : "Expand menu"} icon={open ? <Backwards /> : <Forwards />} onClick={() => setOpen((prev) => !prev)} /> </HvVerticalNavigationActions> </HvVerticalNavigation> ); } /** Keeps matching items, and parents that still have a matching child. */ const filterNavigation = (items, query) => { const q = query.trim().toLowerCase(); if (!q) return items; return items.flatMap((item) => { const children = item.data ? filterNavigation(item.data, q) : []; if (children.length > 0) return [{ ...item, data: children }]; if (item.label.toLowerCase().includes(q)) return [{ ...item, data: undefined }]; return []; }); }; const quickAccess = [ { id: "home", label: "Home", icon: <Home /> }, { id: "marketplace", label: "Data Marketplace", icon: <Storage /> }, { id: "favorites", label: "Favorites", icon: <Favorite /> }, ]; const mainSections = [ { id: "explore", label: "Explore", icon: <Map /> }, { id: "transform", label: "Transform", icon: <Machine /> }, { id: "governance", label: "Data Governance", icon: <Lock /> }, { id: "analytics", label: "Business Analytics", icon: <BarChart />, data: [ { id: "analytics-models", label: "Semantic Models", icon: <DataStore /> }, { id: "analytics-reports", label: "Business Reports", icon: <Doc /> }, { id: "analytics-dashboards", label: "Business Dashboards", icon: <LineChart />, }, ], }, { id: "management", label: "Management", icon: <Deploy /> }, { id: "monitoring", label: "Monitoring", icon: <Preview /> }, { id: "settings", label: "Settings", icon: <Settings /> }, ];
Collapsible
Use HvVerticalNavigationHeader with onCollapseButtonClick to add a collapse button, and useIcons to show icons when collapsed. Items without an icon show the first letter of their label.
When collapsed, items with data open their children in a popup: hover to open it, click to keep it open. Items without data are selected on click.
Menu
import { useState } from "react"; export default function Demo() { const [selected, setSelected] = useState("analytics-reports"); const [open, setOpen] = useState(true); const [icons, setIcons] = useState(true); return ( <div className="flex gap-sm"> <HvCheckBox label="Show icons" checked={icons} onChange={() => setIcons((prev) => !prev)} /> <HvVerticalNavigation open={open} useIcons={icons}> <HvVerticalNavigationHeader title="Menu" onCollapseButtonClick={() => setOpen((prev) => !prev)} collapseButtonProps={{ "aria-label": "Toggle menu", "aria-expanded": open, }} /> <HvVerticalNavigationTree collapsible defaultExpanded={["analytics"]} aria-label="Sections" selected={selected} onChange={(event, data) => setSelected(data.id)} data={navigationData} /> </HvVerticalNavigation> </div> ); } const navigationData = [ { id: "explore", label: "Explore", icon: <Map /> }, { id: "analytics", label: "Business Analytics", icon: <BarChart />, data: [ { id: "analytics-models", label: "Semantic Models", icon: <DataStore /> }, { id: "analytics-reports", label: "Business Reports", icon: <Doc /> }, ], }, { id: "management", label: "Management", icon: <Deploy /> }, { id: "settings", label: "Settings" }, ];
Slider
slider replaces the content with an item’s children instead of expanding in place, one level at a time. Use it when the screen is too narrow for a nested tree.
import { useState } from "react"; export default function Demo() { const [selected, setSelected] = useState("explore"); return ( <HvVerticalNavigation open slider useIcons> <HvVerticalNavigationHeader title="Menu" /> <HvVerticalNavigationTree collapsible aria-label="Sections" selected={selected} onChange={(event, data) => setSelected(data.id)} data={navigationData} /> </HvVerticalNavigation> ); } const navigationData = [ { id: "explore", label: "Explore", icon: <Map /> }, { id: "analytics", label: "Business Analytics", icon: <BarChart />, data: [ { id: "analytics-models", label: "Semantic Models", icon: <DataStore />, data: [ { id: "analytics-models-sales", label: "Sales" }, { id: "analytics-models-inventory", label: "Inventory" }, ], }, { id: "analytics-reports", label: "Business Reports", icon: <Doc /> }, ], }, { id: "management", label: "Management", icon: <Deploy /> }, { id: "settings", label: "Settings" }, ];
Custom content
HvVerticalNavigation accepts arbitrary children, so branding or a primary action can sit alongside the tree.
import { useState } from "react"; export default function Demo() { const [collapsed, setCollapsed] = useState(false); return ( <HvVerticalNavigation open={!collapsed} useIcons> <div className="flex flex-col gap-md" style={{ alignItems: collapsed ? "center" : "stretch" }} > {collapsed ? ( <Favorite /> ) : ( <div className="flex items-center"> <Favorite /> <HvTypography variant="label">Custom App</HvTypography> </div> )} <CollapsibleButton collapsed={collapsed} variant="primary" icon={<Add />} label="Create" /> </div> <HvVerticalNavigationTree aria-label="Sections" data={navigationData} /> <HvVerticalNavigationActions> <HvVerticalNavigationAction label={collapsed ? "Expand menu" : "Collapse menu"} icon={collapsed ? <Forwards /> : <Backwards />} onClick={() => setCollapsed((prev) => !prev)} /> </HvVerticalNavigationActions> </HvVerticalNavigation> ); } const CollapsibleButton = ({ collapsed, label, icon, ...others }) => { const props = collapsed ? { "aria-label": String(label), children: icon, icon: true } : { startIcon: icon, children: label }; return <HvButton {...props} {...others} />; }; const navigationData = [ { id: "explore", label: "Explore", icon: <Map /> }, { id: "analytics", label: "Business Analytics", icon: <BarChart />, data: [ { id: "analytics-models", label: "Semantic Models", icon: <DataStore /> }, { id: "analytics-reports", label: "Business Reports", icon: <Doc /> }, ], }, { id: "management", label: "Management", icon: <Deploy /> }, { id: "settings", label: "Settings" }, ];