120k

Sidebar

A composable, themeable and customizable sidebar component.

Sidebars are one of the most complex components to build. They are central to any application and often contain a lot of moving parts.

We now have a solid foundation to build on top of. Composable. Themeable. Customizable.

Installation

pnpm dlx shadcn@latest add @force-ui-ember/sidebar

Usage

app/templates/application.hbs
import {
  SidebarProvider,
  SidebarTrigger,
} from '@/ember-ui/sidebar';
import { AppSidebar } from '@/components/app-sidebar';
 
<template>
  <SidebarProvider>
    <AppSidebar />
    <main>
      <SidebarTrigger />
      {{yield}}
    </main>
  </SidebarProvider>
</template>
app/components/app-sidebar.gts
import {
  Sidebar,
  SidebarContent,
  SidebarFooter,
  SidebarGroup,
  SidebarHeader,
} from '@/ember-ui/sidebar';
 
<template>
  <Sidebar>
    <SidebarHeader />
    <SidebarContent>
      <SidebarGroup />
      <SidebarGroup />
    </SidebarContent>
    <SidebarFooter />
  </Sidebar>
</template>

Composition

Use the following composition to build a Sidebar layout:

SidebarProvider
├── Sidebar
│   ├── SidebarHeader
│   ├── SidebarContent
│   │   ├── SidebarGroup
│   │   │   ├── SidebarGroupLabel
│   │   │   ├── SidebarGroupAction
│   │   │   ├── SidebarGroupContent
│   │   │   └── SidebarMenu
│   │   │       ├── SidebarMenuItem
│   │   │       │   ├── SidebarMenuButton
│   │   │       │   ├── SidebarMenuAction
│   │   │       │   └── SidebarMenuBadge
│   │   │       └── SidebarMenuItem
│   │   │           ├── SidebarMenuButton
│   │   │           └── SidebarMenuSub
│   │   │               ├── SidebarMenuSubItem
│   │   │               └── SidebarMenuSubItem
│   │   └── SidebarGroup
│   │       └── SidebarMenu
│   │           ├── SidebarMenuItem
│   │           └── SidebarMenuItem
│   ├── SidebarFooter
│   └── SidebarRail
├── SidebarInset
└── SidebarTrigger

Structure

  • SidebarProvider — Handles collapsible state and provides sidebar context to child components.
  • Sidebar — The main collapsible sidebar panel.
  • SidebarHeader — Sticky at the top; use for branding, titles, or workspace switchers.
  • SidebarFooter — Sticky at the bottom; use for user menus, settings, or actions.
  • SidebarContent — Scrollable region between the header and footer.
  • SidebarGroup — Groups related navigation with optional label, action, and content areas.
  • SidebarMenu / SidebarMenuItem — Menu structure for links, badges, actions, and nested submenus.
  • SidebarRail — Resize handle for adjusting sidebar width when applicable.
  • SidebarInset — Wraps main content when using the inset variant.
  • SidebarTrigger — Control that toggles the sidebar open or collapsed.

SidebarProvider

The SidebarProvider component is used to provide the sidebar context to the Sidebar component. You should always wrap your application in a SidebarProvider component.

Arguments

NameTypeDescription
@defaultOpenbooleanDefault open state of the sidebar.
@openbooleanOpen state of the sidebar (controlled).
@onOpenChange(open: boolean) => voidSets open state of the sidebar (controlled).

Width

If you have a single sidebar in your application, you can use the SIDEBAR_WIDTH and SIDEBAR_WIDTH_MOBILE variables in sidebar.gts to set the width of the sidebar.

const SIDEBAR_WIDTH = "16rem"
const SIDEBAR_WIDTH_MOBILE = "18rem"

For multiple sidebars in your application, you can use the @style argument to set the width of the sidebar.

<SidebarProvider @style="--sidebar-width: 20rem; --sidebar-width-mobile: 20rem">
  <Sidebar />
</SidebarProvider>

Keyboard Shortcut

To trigger the sidebar, you use the cmd+b keyboard shortcut on Mac and ctrl+b on Windows.

const SIDEBAR_KEYBOARD_SHORTCUT = "b"

The main Sidebar component used to render a collapsible sidebar.

Arguments

PropertyTypeDescription
@sideleft or rightThe side of the sidebar.
@variantsidebar, floating, or insetThe variant of the sidebar.
@collapsibleoffcanvas, icon, or noneCollapsible state of the sidebar.
PropDescription
offcanvasA collapsible sidebar that slides in from the left or right.
iconA sidebar that collapses to icons.
noneA non-collapsible sidebar.
<SidebarProvider>
  <Sidebar @variant="inset" />
  <SidebarInset>
    <main>{{yield}}</main>
  </SidebarInset>
</SidebarProvider>

useSidebar

The useSidebar hook is used to control the sidebar. Since this is Ember, you need to use the context via ember-provide-consume-context:

import { consume } from 'ember-provide-consume-context';
 
const SidebarContext = 'sidebar-context';
 
class MyComponent extends Component {
  @consume(SidebarContext) sidebar;
 
  handleToggle = () => {
    this.sidebar.toggleSidebar();
  };
}
PropertyTypeDescription
stateexpanded or collapsedThe current state of the sidebar.
openbooleanWhether the sidebar is open.
setOpen(open: boolean) => voidSets the open state of the sidebar.
openMobilebooleanWhether the sidebar is open on mobile.
setOpenMobile(open: boolean) => voidSets the open state of the sidebar on mobile.
isMobilebooleanWhether the sidebar is on mobile.
toggleSidebar() => voidToggles the sidebar. Desktop and mobile.

SidebarHeader

Use the SidebarHeader component to add a sticky header to the sidebar.

import {
  DropdownMenu,
  DropdownMenuContent,
  DropdownMenuItem,
  DropdownMenuTrigger,
} from '@/ember-ui/dropdown-menu';
import {
  Sidebar,
  SidebarHeader,
  SidebarMenu,
  SidebarMenuButton,
  SidebarMenuItem,
} from '@/ember-ui/sidebar';
 
import ChevronDown from '~icons/ms/keyboard_arrow_down';
 
<template>
  <Sidebar>
    <SidebarHeader>
      <SidebarMenu>
        <SidebarMenuItem>
          <DropdownMenu>
            <DropdownMenuTrigger>
              <SidebarMenuButton>
                Select Workspace
                <ChevronDown class="ml-auto" />
              </SidebarMenuButton>
            </DropdownMenuTrigger>
            <DropdownMenuContent class="w-[--radix-popper-anchor-width]">
              <DropdownMenuItem>
                <span>Acme Inc</span>
              </DropdownMenuItem>
            </DropdownMenuContent>
          </DropdownMenu>
        </SidebarMenuItem>
      </SidebarMenu>
    </SidebarHeader>
  </Sidebar>
</template>

SidebarFooter

Use the SidebarFooter component to add a sticky footer to the sidebar.

import {
  DropdownMenu,
  DropdownMenuContent,
  DropdownMenuItem,
  DropdownMenuTrigger,
} from '@/ember-ui/dropdown-menu';
import {
  Sidebar,
  SidebarContent,
  SidebarFooter,
  SidebarHeader,
  SidebarMenu,
  SidebarMenuButton,
  SidebarMenuItem,
} from '@/ember-ui/sidebar';
 
import ChevronUp from '~icons/ms/keyboard_arrow_up';
import User2 from '~icons/ms/person';
 
<template>
  <Sidebar>
    <SidebarHeader />
    <SidebarContent />
    <SidebarFooter>
      <SidebarMenu>
        <SidebarMenuItem>
          <DropdownMenu>
            <DropdownMenuTrigger>
              <SidebarMenuButton>
                <User2 /> Username
                <ChevronUp class="ml-auto" />
              </SidebarMenuButton>
            </DropdownMenuTrigger>
            <DropdownMenuContent @side="top" class="w-[--radix-popper-anchor-width]">
              <DropdownMenuItem>
                <span>Account</span>
              </DropdownMenuItem>
              <DropdownMenuItem>
                <span>Billing</span>
              </DropdownMenuItem>
              <DropdownMenuItem>
                <span>Sign out</span>
              </DropdownMenuItem>
            </DropdownMenuContent>
          </DropdownMenu>
        </SidebarMenuItem>
      </SidebarMenu>
    </SidebarFooter>
  </Sidebar>
</template>

SidebarContent

The SidebarContent component is used to wrap the content of the sidebar. This is where you add your SidebarGroup components. It is scrollable.

import { Sidebar, SidebarContent, SidebarGroup } from '@/ember-ui/sidebar';
 
<template>
  <Sidebar>
    <SidebarContent>
      <SidebarGroup />
      <SidebarGroup />
    </SidebarContent>
  </Sidebar>
</template>

SidebarGroup

Use the SidebarGroup component to create a section within the sidebar.

A SidebarGroup has a SidebarGroupLabel, a SidebarGroupContent and an optional SidebarGroupAction.

import {
  Sidebar,
  SidebarContent,
  SidebarGroup,
  SidebarGroupAction,
  SidebarGroupContent,
  SidebarGroupLabel,
} from '@/ember-ui/sidebar';
 
import Plus from '~icons/ms/add';
 
<template>
  <Sidebar>
    <SidebarContent>
      <SidebarGroup>
        <SidebarGroupLabel>Application</SidebarGroupLabel>
        <SidebarGroupAction title="Add Project">
          <Plus />
          <span class="sr-only">Add Project</span>
        </SidebarGroupAction>
        <SidebarGroupContent />
      </SidebarGroup>
    </SidebarContent>
  </Sidebar>
</template>

To make a SidebarGroup collapsible, wrap it in a Collapsible.

import {
  Collapsible,
  CollapsibleContent,
  CollapsibleTrigger,
} from '@/ember-ui/collapsible';
import {
  SidebarGroup,
  SidebarGroupContent,
  SidebarGroupLabel,
} from '@/ember-ui/sidebar';
 
import ChevronDown from '~icons/ms/keyboard_arrow_down';
 
<template>
  <Collapsible @defaultOpen={{true}} class="group/collapsible">
    <SidebarGroup>
      <SidebarGroupLabel @asChild={{true}}>
        <CollapsibleTrigger>
          Help
          <ChevronDown
            class="ml-auto transition-transform group-data-[state=open]/collapsible:rotate-180"
          />
        </CollapsibleTrigger>
      </SidebarGroupLabel>
      <CollapsibleContent>
        <SidebarGroupContent />
      </CollapsibleContent>
    </SidebarGroup>
  </Collapsible>
</template>

SidebarGroupAction

Use the SidebarGroupAction component to add an action button to the SidebarGroup.

import {
  SidebarGroup,
  SidebarGroupAction,
  SidebarGroupContent,
  SidebarGroupLabel,
} from '@/ember-ui/sidebar';
 
import Plus from '~icons/ms/add';
 
<template>
  <SidebarGroup>
    <SidebarGroupLabel>Projects</SidebarGroupLabel>
    <SidebarGroupAction title="Add Project">
      <Plus />
      <span class="sr-only">Add Project</span>
    </SidebarGroupAction>
    <SidebarGroupContent />
  </SidebarGroup>
</template>

SidebarMenu

The SidebarMenu component is used for building a menu within a SidebarGroup.

A SidebarMenu component is composed of SidebarMenuItem, SidebarMenuButton, SidebarMenuAction and SidebarMenuSub components.

import {
  Sidebar,
  SidebarContent,
  SidebarGroup,
  SidebarGroupContent,
  SidebarGroupLabel,
  SidebarMenu,
  SidebarMenuButton,
  SidebarMenuItem,
} from '@/ember-ui/sidebar';
 
const projects = [
  { name: 'Design Engineering', url: '#', icon: Frame },
  { name: 'Sales & Marketing', url: '#', icon: PieChart },
  { name: 'Travel', url: '#', icon: Map },
];
 
<template>
  <Sidebar>
    <SidebarContent>
      <SidebarGroup>
        <SidebarGroupLabel>Projects</SidebarGroupLabel>
        <SidebarGroupContent>
          <SidebarMenu>
            {{#each projects as |project|}}
              <SidebarMenuItem>
                <SidebarMenuButton>
                  <a href={{project.url}}>
                    <project.icon />
                    <span>{{project.name}}</span>
                  </a>
                </SidebarMenuButton>
              </SidebarMenuItem>
            {{/each}}
          </SidebarMenu>
        </SidebarGroupContent>
      </SidebarGroup>
    </SidebarContent>
  </Sidebar>
</template>

SidebarMenuButton

The SidebarMenuButton component is used to render a menu button within a SidebarMenuItem.

By default, the SidebarMenuButton renders a button but you should yield to it to render a different element such as a LinkTo or an a tag.

Use the @isActive argument to mark a menu item as active.

<SidebarMenuButton @isActive={{true}}>
  <a href="#">Home</a>
</SidebarMenuButton>

SidebarMenuAction

The SidebarMenuAction component is used to render a menu action within a SidebarMenuItem.

import {
  DropdownMenu,
  DropdownMenuContent,
  DropdownMenuItem,
  DropdownMenuTrigger,
} from '@/ember-ui/dropdown-menu';
import {
  SidebarMenuAction,
  SidebarMenuButton,
  SidebarMenuItem,
} from '@/ember-ui/sidebar';
 
import Home from '~icons/ms/home';
import MoreHorizontal from '~icons/ms/more_horiz';
 
<template>
  <SidebarMenuItem>
    <SidebarMenuButton>
      <a href="#">
        <Home />
        <span>Home</span>
      </a>
    </SidebarMenuButton>
    <DropdownMenu>
      <DropdownMenuTrigger>
        <SidebarMenuAction>
          <MoreHorizontal />
        </SidebarMenuAction>
      </DropdownMenuTrigger>
      <DropdownMenuContent @side="right" @align="start">
        <DropdownMenuItem>
          <span>Edit Project</span>
        </DropdownMenuItem>
        <DropdownMenuItem>
          <span>Delete Project</span>
        </DropdownMenuItem>
      </DropdownMenuContent>
    </DropdownMenu>
  </SidebarMenuItem>
</template>

SidebarMenuSub

The SidebarMenuSub component is used to render a submenu within a SidebarMenu.

<SidebarMenuItem>
  <SidebarMenuButton />
  <SidebarMenuSub>
    <SidebarMenuSubItem>
      <SidebarMenuSubButton />
    </SidebarMenuSubItem>
    <SidebarMenuSubItem>
      <SidebarMenuSubButton />
    </SidebarMenuSubItem>
  </SidebarMenuSub>
</SidebarMenuItem>

Collapsible SidebarMenu

To make a SidebarMenu component collapsible, wrap it and the SidebarMenuSub components in a Collapsible.

import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '@/ember-ui/collapsible';
import {
  SidebarMenu,
  SidebarMenuButton,
  SidebarMenuItem,
  SidebarMenuSub,
  SidebarMenuSubItem,
} from '@/ember-ui/sidebar';
 
<template>
  <SidebarMenu>
    <Collapsible @defaultOpen={{true}} class="group/collapsible">
      <SidebarMenuItem>
        <CollapsibleTrigger>
          <SidebarMenuButton />
        </CollapsibleTrigger>
        <CollapsibleContent>
          <SidebarMenuSub>
            <SidebarMenuSubItem />
          </SidebarMenuSub>
        </CollapsibleContent>
      </SidebarMenuItem>
    </Collapsible>
  </SidebarMenu>
</template>

SidebarMenuBadge

The SidebarMenuBadge component is used to render a badge within a SidebarMenuItem.

<SidebarMenuItem>
  <SidebarMenuButton />
  <SidebarMenuBadge>24</SidebarMenuBadge>
</SidebarMenuItem>

SidebarMenuSkeleton

The SidebarMenuSkeleton component is used to render a skeleton for a SidebarMenu. You can use this to show a loading state.

import {
  SidebarMenu,
  SidebarMenuItem,
  SidebarMenuSkeleton,
} from '@/ember-ui/sidebar';
 
<template>
  <SidebarMenu>
    {{#each (array 1 2 3 4 5) as |_|}}
      <SidebarMenuItem>
        <SidebarMenuSkeleton />
      </SidebarMenuItem>
    {{/each}}
  </SidebarMenu>
</template>

SidebarTrigger

Use the SidebarTrigger component to render a button that toggles the sidebar.

The SidebarTrigger component must be used within a SidebarProvider.

<SidebarProvider>
  <Sidebar />
  <main>
    <SidebarTrigger />
  </main>
</SidebarProvider>

SidebarRail

The SidebarRail component is used to render a rail within a Sidebar. This rail can be used to toggle the sidebar.

<Sidebar>
  <SidebarHeader />
  <SidebarContent>
    <SidebarGroup />
  </SidebarContent>
  <SidebarFooter />
  <SidebarRail />
</Sidebar>

Controlled Sidebar

Use the @open and @onOpenChange arguments to control the sidebar.

import { tracked } from '@glimmer/tracking';
import Component from '@glimmer/component';
import { Sidebar, SidebarProvider } from '@/ember-ui/sidebar';
 
export default class extends Component {
  @tracked open = false;
 
  setOpen = (value) => {
    this.open = value;
  };
 
  <template>
    <SidebarProvider @open={{this.open}} @onOpenChange={{this.setOpen}}>
      <Sidebar />
    </SidebarProvider>
  </template>
}

Theming

We use the following CSS variables to theme the sidebar. Add them to your app.css:

@layer base {
  :root {
    --sidebar: oklch(0.985 0 0);
    --sidebar-foreground: oklch(0.145 0 0);
    --sidebar-primary: oklch(0.205 0 0);
    --sidebar-primary-foreground: oklch(0.985 0 0);
    --sidebar-accent: oklch(0.97 0 0);
    --sidebar-accent-foreground: oklch(0.205 0 0);
    --sidebar-border: oklch(0.922 0 0);
    --sidebar-ring: oklch(0.708 0 0);
  }
 
  .dark {
    --sidebar: oklch(0.205 0 0);
    --sidebar-foreground: oklch(0.985 0 0);
    --sidebar-primary: oklch(0.488 0.243 264.376);
    --sidebar-primary-foreground: oklch(0.985 0 0);
    --sidebar-accent: oklch(0.269 0 0);
    --sidebar-accent-foreground: oklch(0.985 0 0);
    --sidebar-border: oklch(1 0 0 / 10%);
    --sidebar-ring: oklch(0.439 0 0);
  }
}

Styling

Here are some tips for styling the sidebar based on different states.

<Sidebar @collapsible="icon">
  <SidebarContent>
    <SidebarGroup class="group-data-[collapsible=icon]:hidden" />
  </SidebarContent>
</Sidebar>
<SidebarMenuItem>
  <SidebarMenuButton />
  <SidebarMenuAction class="peer-data-[active=true]/menu-button:opacity-100" />
</SidebarMenuItem>

API Reference

SidebarProvider

ArgumentTypeDescription
@defaultOpenbooleanDefault open state of the sidebar.
@openbooleanOpen state of the sidebar (controlled).
@onOpenChange(open: boolean) => voidSets open state of the sidebar (controlled).
ArgumentTypeDescription
@sideleft or rightThe side of the sidebar.
@variantsidebar, floating, or insetThe variant of the sidebar.
@collapsibleoffcanvas, icon, or noneCollapsible state of the sidebar.

SidebarMenuButton

ArgumentTypeDescription
@isActivebooleanWhether the menu item is active.
@sizedefault or sm or lgThe size of the button.
@tooltipstringTooltip shown when sidebar is collapsed.