Structure
A sidebar is composed of the following structure:
SidebarContainer
- Controls the view of the sidebar based on its width.SidebarGroup
(optional) - For building group of menusSidebarGroupLabel
(optional) - The label of the groupSidebarMenu
- A list of menusSidebarMenuItem
- A list item of a menuSidebarMenuButton
- A menu button or a menu linkSidebarIcon
- An icon of the menuSidebarText
- A text of the menu (hidden whenSidebarContainer
is small)
SidebarRail
- An element to collapse the edge sidebar
Building a sidebar
A sidebar requires .jun-sidebarContainer
to function properly:
<div className="jun-sidebarContainer">...sidebar elements</div>
Menu button
For a single menu, wrap button
or a
with a class .jun-sidebarMenuButton
within .jun-sidebarMenu
:
<button className="jun-sidebarMenuButton">
<Icon className="jun-sidebarIcon" />
<span className="jun-sidebarText">A menu text</span>
</button>
💡 Good to know
.jun-sidebarText
will disappear when the sidebar container's width is small.
List of menus
Use .jun-sidebarMenu
to create a list of menus, each menu button should be wrapped with .jun-sidebarMenuItem
:
<ul className="jun-sidebarMenu">
<li className="jun-sidebarMenuItem">
<button className="jun-sidebarMenuButton">
<Icon className="jun-sidebarIcon" />
<span className="jun-sidebarText">A menu text</span>
</button>
</li>
...
<li className="jun-sidebarMenuItem">...</li>
</ul>
Group of menus
Use .jun-sidebarGroup
for a group of menu, each group can have a label using .jun-sidebarGroupLabel
:
<div className="jun-sidebarGroup">
<div className="jun-sidebarGroupLabel">Group 1</div>
<ul className="jun-sidebarMenu">...list of menus</ul>
</div>
<div className="jun-sidebarGroup">
<div className="jun-sidebarGroupLabel">Group 2</div>
<ul className="jun-sidebarMenu">...list of menus</ul>
</div>
💡 Good to know
.jun-sidebarGroupLabel
will disappear when the sidebar container's width is
small.
Group of texts
If the menu has more than one text elements, wrap those texts with .jun-sidebarGroupText
.
The layout will collapse the texts when EdgeSidebar is in collapsed state.
You can control the spacing of the menu button using a modifier .jun-sidebarMenuButton-spacing-[*]
.
<a className="jun-sidebarMenuButton jun-sidebarMenuButton-spacing-3.5">
<svg className="jun-sidebarIcon" />
<div className="jun-sidebarGroupText">
<div>
<span className="jun-sidebarText">Primary text</span>
<span className="jun-sidebarText">Secondary text</span>
<span className="jun-sidebarText">Tertiary text</span>
</div>
</div>
</a>
⚠️ Warning
The extra div
under the .jun-sidebarGroupText
is require to make the
collapse working properly.
Menu action
To add a secondary action to a menu, create a button with .jun-sidebarMenuAction
:
<li className="jun-sidebarMenuItem">
<a className="jun-sidebarMenuButton">
<svg className="jun-sidebarIcon" />
<span className="jun-sidebarText">Dashboard</span>
</a>
<button className="jun-sidebarMenuAction">
<MoreHorizontal />
</button>
</li>
⚠️ Warning
The button must be a direct child of the .jun-sidebarMenuItem
. Do
not put the action inside .jun-sidebarMenuButton
.
Append .jun-sidebarMenuAction-hoverAppear
, if you want the action to appear when users hover on the item.
The action also appears on keyboard focus.
Tooltip
To show a tooltip (using radix or shadcn) only when the permanent EdgeSidebar is collapsed, first wraps the layout with the TooltipProvider
:
<TooltipProvider delayDuration={0}>
<div className="jun-layout">...</div>
</TooltipProvider>
Then, create a TooltipSidebar
that wraps TooltipPrimitive.Portal
and TooltipPrimitive.Content
to custom the container
prop to be the EdgeSidebar element:
import * as TooltipPrimitive from "@radix-ui/react-tooltip";
function TooltipSidebar({ children }) {
const [container, setContainer] = React.useState(null);
React.useEffect(() => {
setContainer(document.querySelector(".jun-edgeSidebar"));
}, []);
return (
<TooltipPrimitive.Portal container={container}>
<TooltipPrimitive.Content
ref={ref}
sideOffset={4}
side="right"
className={cn(
"z-50 overflow-hidden rounded-md bg-primary px-3 py-1.5 text-xs text-primary-foreground animate-in fade-in-0 zoom-in-95",
className
)}
>
{children}
</TooltipPrimitive.Content>
</TooltipPrimitive.Portal>
);
}
Finally, create a tooltip for each menu and apply .jun-sidebarTooltip
to the TooltipContent:
<li className="jun-sidebarMenuItem">
<Tooltip>
<TooltipTrigger>
<a className="jun-sidebarMenuButton">
<svg className="jun-sidebarIcon" />
<span className="jun-sidebarText">Dashboard</span>
</a>
</TooltipTrigger>
<TooltipContent side="right" align="center" className="jun-sidebarTooltip">
<p>Dashboard</p>
</TooltipContent>
</Tooltip>
</li>
Nested menu
The nested menu should be a direct child of .jun-sidebarMenuItem
to follow semantic structure, then append .jun-sidebarMenu-nested
to the menu.
The .jun-sidebarGroupText
is used to hide the nested menu when the sidebar is collapsed.
<ul className="jun-sidebarMenu">
<li key={itemIndex} className="jun-sidebarMenuItem">
<button className="jun-sidebarMenuButton">
<Icon className="jun-sidebarIcon jun-sidebarIcon-shrink-size-5" />
<span className="jun-sidebarText">{item.label}</span>
</button>
<div className="jun-sidebarGroupText">
<div>
<ul className="jun-sidebarMenu jun-sidebarMenu-nested">
<li className="jun-sidebarMenuItem">
<button className="jun-sidebarMenuButton">
<span className="jun-sidebarText">{sub.label}</span>
</button>
</li>
</ul>
</div>
</div>
</li>
</ul>
Collapsible menu
To create a collapsible menu, first change the menu button tag to label
and add a hidden checkbox like this (id
and htmlFor
are required):
- <button className="jun-sidebarMenuButton">
+ <label
+ htmlFor={`menu-${item.label}`}
+ className="jun-sidebarMenuButton jun-collapsibleTrigger"
+ >
<Icon className="jun-sidebarIcon jun-sidebarIcon-shrink-size-5" />
<span className="jun-sidebarText">{item.label}</span>
+ <input
+ type="checkbox"
+ className="sr-only"
+ id={`menu-${item.label}`}
+ />
+ </label>
- </button>
Then, create a div with .jun-collapsibleContent
as a direct child of menu item, add another div as a child and then the nested menu:
<li key={itemIndex} className="jun-sidebarMenuItem">
<label
htmlFor={`menu-${item.label}`}
className="jun-sidebarMenuButton jun-collapsibleTrigger"
>
...
</label>
<div className="jun-collapsibleContent">
<div>
<ul className="jun-sidebarMenu jun-sidebarMenu-nested">
{item.items.map((sub) => (
<li className="jun-sidebarMenuItem">
<button className="jun-sidebarMenuButton">
<span className="jun-sidebarText">{sub.label}</span>
</button>
</li>
))}
</ul>
</div>
</div>
</li>
Warning
The extra div
between .jun-collapsibleContent
and jun-sidebarMenu-nested
is required.
Finally, an optional .jun-collapsibleIcon
can be used as an indicator.
<label
htmlFor={`menu-${item.label}`}
className="jun-sidebarMenuButton jun-collapsibleTrigger"
>
<Icon className="jun-sidebarIcon jun-sidebarIcon-shrink-size-5" />
<span className="jun-sidebarText">{item.label}</span>
<ChevronDown className="size-4 jun-collapsibleIcon jun-collapsibleIcon-rotate-180" />
<input
type="checkbox"
className="sr-only"
id={`menu-${item.label}`}
defaultChecked
/>
</label>
Collapsible menu action
If the menu is an anchor, moved the collapsible trigger to the menu action instead.
The order must strictly follow:
<li className="jun-sidebarMenuItem">
<button className="jun-sidebarMenuButton">...</button>
{/* Collapsible trigger as secondary action */}
<label
htmlFor="menu-orders"
className="jun-sidebarMenuAction jun-collapsibleTrigger"
>
<ChevronDown className="size-4 jun-collapsibleIcon jun-collapsibleIcon-rotate-180" />
<input
type="checkbox"
className="sr-only"
id="menu-orders"
defaultChecked
/>
</label>
{/* Nested menu wrapped in collapsible content */}
<div className="jun-collapsibleContent">...</div>
</li>
Examples
Flat menus
<div className="jun-edgeContent bg-sidebar">
<div className="jun-sidebarContainer">
<ul className="jun-sidebarMenu">
<li className="jun-sidebarMenuItem">
<button className="jun-sidebarMenuButton">
<Home className="jun-sidebarIcon jun-sidebarIcon-shrink-size-5" />
<span className="jun-sidebarText">Home</span>
</button>
</li>
...items
</ul>
</div>
</div>
Group menus
<div className="jun-sidebarContainer">
<div className="jun-sidebarGroup">
<div className="jun-sidebarGroupLabel">Main</div>
<ul className="jun-sidebarMenu">
<li className="jun-sidebarMenuItem">
<button className="jun-sidebarMenuButton">
<Home className="jun-sidebarIcon jun-sidebarIcon-shrink-size-5" />
<span className="jun-sidebarText">Home</span>
</button>
</li>
<li className="jun-sidebarMenuItem">
<button className="jun-sidebarMenuButton">
<Users className="jun-sidebarIcon jun-sidebarIcon-shrink-size-5" />
<span className="jun-sidebarText">Users</span>
</button>
</li>
</ul>
</div>
...groups
</div>
Collapsible menus
<div className="jun-sidebarContainer">
<ul className="jun-sidebarMenu">
<li className="jun-sidebarMenuItem">
<label
htmlFor="menu-home"
className="jun-sidebarMenuButton jun-collapsibleTrigger"
>
<Home className="jun-sidebarIcon jun-sidebarIcon-shrink-size-5" />
<span className="jun-sidebarText">Home</span>
<ChevronDown className="size-4 jun-collapsibleIcon jun-collapsibleIcon-rotate-180" />
<input
type="checkbox"
className="sr-only"
id="menu-home"
defaultChecked
/>
</label>
<div className="jun-collapsibleContent">
<div>
<ul className="jun-sidebarMenu jun-sidebarMenu-nested">
<li className="jun-sidebarMenuItem">
<button className="jun-sidebarMenuButton">
<Users className="jun-sidebarIcon jun-sidebarIcon-shrink-size-5" />
<span className="jun-sidebarText">Users</span>
</button>
</li>
<li className="jun-sidebarMenuItem">
<button className="jun-sidebarMenuButton">
<FileText className="jun-sidebarIcon jun-sidebarIcon-shrink-size-5" />
<span className="jun-sidebarText">Documents</span>
</button>
</li>
<li className="jun-sidebarMenuItem">
<button className="jun-sidebarMenuButton">
<Settings className="jun-sidebarIcon jun-sidebarIcon-shrink-size-5" />
<span className="jun-sidebarText">Settings</span>
</button>
</li>
</ul>
</div>
</div>
</li>
...collapsible items
</ul>
</div>
Collapsible menus (secondary action)
<div className="jun-sidebarContainer">
<ul className="jun-sidebarMenu">
<li className="jun-sidebarMenuItem">
<button className="jun-sidebarMenuButton">
<Home className="jun-sidebarIcon jun-sidebarIcon-shrink-size-5" />
<span className="jun-sidebarText">Home</span>
</button>
<label className="jun-sidebarMenuAction jun-collapsibleTrigger">
<ChevronDown className="size-4 jun-collapsibleIcon jun-collapsibleIcon-rotate-180" />
<input
type="checkbox"
className="sr-only"
id="menu-home"
defaultChecked
/>
</label>
<div className="jun-collapsibleContent">
<div>
<ul className="jun-sidebarMenu jun-sidebarMenu-nested">
<li className="jun-sidebarMenuItem">
<button className="jun-sidebarMenuButton">
<Users className="jun-sidebarIcon jun-sidebarIcon-shrink-size-5" />
<span className="jun-sidebarText">Users</span>
</button>
<button className="jun-sidebarMenuAction jun-sidebarMenuAction-hoverAppear">
<MoreVertical />
</button>
</li>
<li className="jun-sidebarMenuItem">
<button className="jun-sidebarMenuButton">
<FileText className="jun-sidebarIcon jun-sidebarIcon-shrink-size-5" />
<span className="jun-sidebarText">Documents</span>
</button>
<button className="jun-sidebarMenuAction jun-sidebarMenuAction-hoverAppear">
<MoreVertical />
</button>
</li>
<li className="jun-sidebarMenuItem">
<button className="jun-sidebarMenuButton">
<Settings className="jun-sidebarIcon jun-sidebarIcon-shrink-size-5" />
<span className="jun-sidebarText">Settings</span>
</button>
<button className="jun-sidebarMenuAction jun-sidebarMenuAction-hoverAppear">
<MoreVertical />
</button>
</li>
</ul>
</div>
</div>
</li>
</ul>
</div>