Rendered from the shared Frontfriend stories with React Base, the same
preview Studio shows. Styling comes from your Design System; the code below
is native to Reka UI.
Installation
Complete project setup first. Your configuration
must select vue/reka and include your Design System ID.
pnpm exec frontfriend download sidebar -f vue --primitive reka
Review dependencies reported by the downloader and keep your source
changes in version control.
Usage
<script setup lang="ts">
import { defineComponent } from "vue";
import { SidebarInset } from "@/components/ui/sidebar";
import { SidebarProvider } from "@/components/ui/sidebar";
import { SidebarTrigger } from "@/components/ui/sidebar";
import { Sidebar } from "@/components/ui/sidebar";
import { SidebarContent } from "@/components/ui/sidebar";
import { SidebarFooter } from "@/components/ui/sidebar";
import { SidebarHeader } from "@/components/ui/sidebar";
import { SidebarRail } from "@/components/ui/sidebar";
import { useSidebar } from "@/components/ui/sidebar";
import { Icon } from "@/components/ui/icon";
import { Collapsible } from "@/components/ui/collapsible";
import { CollapsibleContent } from "@/components/ui/collapsible";
import { CollapsibleTrigger } from "@/components/ui/collapsible";
import { SidebarGroup } from "@/components/ui/sidebar";
import { SidebarGroupLabel } from "@/components/ui/sidebar";
import { SidebarMenu } from "@/components/ui/sidebar";
import { SidebarMenuButton } from "@/components/ui/sidebar";
import { SidebarMenuItem } from "@/components/ui/sidebar";
import { SidebarMenuSub } from "@/components/ui/sidebar";
import { SidebarMenuSubButton } from "@/components/ui/sidebar";
import { SidebarMenuSubItem } from "@/components/ui/sidebar";
import { DropdownMenu } from "@/components/ui/dropdown-menu";
import { DropdownMenuContent } from "@/components/ui/dropdown-menu";
import { DropdownMenuItem } from "@/components/ui/dropdown-menu";
import { DropdownMenuSeparator } from "@/components/ui/dropdown-menu";
import { DropdownMenuTrigger } from "@/components/ui/dropdown-menu";
import { SidebarMenuAction } from "@/components/ui/sidebar";
import { Avatar } from "@/components/ui/avatar";
import { AvatarFallback } from "@/components/ui/avatar";
import { AvatarImage } from "@/components/ui/avatar";
import { DropdownMenuGroup } from "@/components/ui/dropdown-menu";
import { DropdownMenuLabel } from "@/components/ui/dropdown-menu";
import { DropdownMenuShortcut } from "@/components/ui/dropdown-menu";
import { ref } from "vue";
import { watch } from "vue";
const NavMain = defineComponent({
components: {
Icon,
Collapsible,
CollapsibleContent,
CollapsibleTrigger,
SidebarGroup,
SidebarGroupLabel,
SidebarMenu,
SidebarMenuButton,
SidebarMenuItem,
SidebarMenuSub,
SidebarMenuSubButton,
SidebarMenuSubItem,
},
props: {
items: {
type: Array,
required: true,
},
},
template: `
<SidebarGroup>
<SidebarGroupLabel>Platform</SidebarGroupLabel>
<SidebarMenu>
<Collapsible
v-for="item in items" :key="item.title" as-child :default-open="item.isActive"
class="group/collapsible"
>
<SidebarMenuItem>
<CollapsibleTrigger as-child>
<SidebarMenuButton :tooltip="item.title">
<Icon :name="item.icon" v-if="item.icon" />
<span>{{ item.title }}</span>
<Icon
name="ChevronRight"
class="ml-auto transition-transform duration-200 group-data-[state=open]/collapsible:rotate-90"
/>
</SidebarMenuButton>
</CollapsibleTrigger>
<CollapsibleContent>
<SidebarMenuSub>
<SidebarMenuSubItem v-for="subItem in item.items" :key="subItem.title">
<SidebarMenuSubButton as-child>
<a :href="subItem.url">
<span>{{ subItem.title }}</span>
</a>
</SidebarMenuSubButton>
</SidebarMenuSubItem>
</SidebarMenuSub>
</CollapsibleContent>
</SidebarMenuItem>
</Collapsible>
</SidebarMenu>
</SidebarGroup>
`,
});
const NavProjects = defineComponent({
components: {
Icon,
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuSeparator,
DropdownMenuTrigger,
SidebarGroup,
SidebarGroupLabel,
SidebarMenu,
SidebarMenuAction,
SidebarMenuButton,
SidebarMenuItem,
useSidebar,
},
props: {
projects: {
type: Array,
required: true,
},
},
setup() {
const { isMobile } = useSidebar();
return { isMobile };
},
template: `
<SidebarGroup class="group-data-[collapsible=icon]:hidden">
<SidebarGroupLabel>Projects</SidebarGroupLabel>
<SidebarMenu>
<SidebarMenuItem v-for="item in projects" :key="item.name">
<SidebarMenuButton as-child>
<a :href="item.url">
<Icon :name="item.icon" />
<span>{{ item.name }}</span>
</a>
</SidebarMenuButton>
<DropdownMenu>
<DropdownMenuTrigger as-child>
<SidebarMenuAction show-on-hover>
<Icon name="EllipsisVertical" />
<span class="sr-only">More</span>
</SidebarMenuAction>
</DropdownMenuTrigger>
<DropdownMenuContent
class="w-48 rounded-lg z-[51]" :side="isMobile ? 'bottom' : 'right'"
:align="isMobile ? 'end' : 'start'"
>
<DropdownMenuItem>
<Icon name="Folder" class="text-neutral-subtle" />
<span>View Project</span>
</DropdownMenuItem>
<DropdownMenuItem>
<Icon name="Forward" class="text-neutral-subtle" />
<span>Share Project</span>
</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem>
<Icon name="Trash2" class="text-neutral-subtle" />
<span>Delete Project</span>
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</SidebarMenuItem>
</SidebarMenu>
</SidebarGroup>
`,
});
const NavUser = defineComponent({
components: {
Avatar,
AvatarFallback,
AvatarImage,
DropdownMenu,
DropdownMenuContent,
DropdownMenuGroup,
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuTrigger,
SidebarMenu,
SidebarMenuButton,
SidebarMenuItem,
useSidebar,
Icon,
},
props: {
user: {
type: Object,
required: true,
},
},
setup() {
const { isMobile } = useSidebar();
return { isMobile };
},
template: `
<SidebarMenu>
<SidebarMenuItem>
<DropdownMenu>
<DropdownMenuTrigger as-child>
<SidebarMenuButton
size="lg"
class="data-[state=open]:bg-neutral-strong data-[state=open]:text-neutral-subtle"
>
<Avatar class="h-8 w-8">
<AvatarImage :src="user.avatar" :alt="user.name" />
<AvatarFallback name="CN" class="" />
</Avatar>
<div class="grid flex-1 text-left text-sm leading-tight">
<span class="truncate font-semibold">{{ user.name }}</span>
<span class="truncate text-xs">{{ user.email }}</span>
</div>
<Icon name="ChevronDown" class="ml-auto size-4" />
</SidebarMenuButton>
</DropdownMenuTrigger>
<DropdownMenuContent
class="w-full min-w-56 rounded-lg z-[51]"
:side="isMobile ? 'bottom' : 'right'"
align="end"
:side-offset="4"
>
<DropdownMenuLabel class="p-0 font-normal">
<div class="flex items-center gap-2 px-1 py-1.5 text-left text-sm">
<Avatar class="h-8 w-8">
<AvatarImage :src="user.avatar" :alt="user.name" />
<AvatarFallback name="CN" />
</Avatar>
<div class="grid flex-1 text-left text-sm leading-tight">
<span class="truncate font-semibold">{{ user.name }}</span>
<span class="truncate text-xs">{{ user.email }}</span>
</div>
</div>
</DropdownMenuLabel>
<DropdownMenuSeparator />
<DropdownMenuGroup>
<DropdownMenuItem>
<Icon name="Sparkles" />
Upgrade to Pro
</DropdownMenuItem>
</DropdownMenuGroup>
<DropdownMenuSeparator />
<DropdownMenuGroup>
<DropdownMenuItem>
<Icon name="CloudCheck" />
Account
</DropdownMenuItem>
<DropdownMenuItem>
<Icon name="CreditCard" />
Billing
</DropdownMenuItem>
<DropdownMenuItem>
<Icon name="Bell" />
Notifications
</DropdownMenuItem>
</DropdownMenuGroup>
<DropdownMenuSeparator />
<DropdownMenuItem>
<Icon name="LogOut" />
Log out
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</SidebarMenuItem>
</SidebarMenu>
`,
});
const TeamSwitcher = defineComponent({
components: {
Icon,
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuShortcut,
DropdownMenuTrigger,
SidebarMenu,
SidebarMenuButton,
SidebarMenuItem,
useSidebar,
},
props: {
teams: {
type: Array,
required: true,
},
},
setup(props) {
const { isMobile } = useSidebar();
const activeTeam = ref(props.teams[0]);
return { isMobile, activeTeam };
},
template: `
<SidebarMenu class="py-0 px-0">
<SidebarMenuItem>
<DropdownMenu>
<DropdownMenuTrigger as-child>
<SidebarMenuButton size="lg">
<div class="rounded-lg p-2 bg-brand-strong"><Icon :name="activeTeam.logo" class="text-white"/></div>
<div class="grid flex-1 text-left text-sm leading-tight">
<span class="truncate font-semibold">
{{ activeTeam.name }}
</span>
<span class="truncate text-xs">{{ activeTeam.plan }}</span>
</div>
<Icon name="ChevronDown" class="ml-auto" />
</SidebarMenuButton>
</DropdownMenuTrigger>
<DropdownMenuContent
class="w-[--reka-dropdownmenu-trigger-width] min-w-56 rounded-lg z-[51]"
align="start"
:side="isMobile ? 'bottom' : 'right'"
:side-offset="4"
>
<DropdownMenuLabel class="text-xs text-neutral-subtle">
Teams
</DropdownMenuLabel>
<DropdownMenuItem
v-for="(team, index) in teams"
:key="team.name"
class="gap-2 p-2"
@click="activeTeam = team"
>
<div class="flex size-6 items-center justify-center rounded-sm border">
<Icon :name="team.logo" class="size-4 shrink-0" />
</div>
{{ team.name }}
<DropdownMenuShortcut>⌘{{ index + 1 }}</DropdownMenuShortcut>
</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem class="gap-2 p-2">
<div class="flex size-6 items-center justify-center rounded-md border bg-background">
<Icon name="Plus" class="size-4" />
</div>
<div class="font-medium text-neutral-subtle">
Add team
</div>
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</SidebarMenuItem>
</SidebarMenu>
`,
});
const AppSidebar = defineComponent({
components: {
Sidebar,
SidebarContent,
SidebarFooter,
SidebarHeader,
SidebarRail,
useSidebar,
NavMain,
NavProjects,
NavUser,
TeamSwitcher,
},
props: {
open: Boolean,
collapsible: {
type: String,
default: "icon",
},
},
setup(props) {
const { setOpen } = useSidebar();
watch(
() => props.open,
(newValue) => {
if (newValue !== undefined) {
setOpen(newValue);
}
},
{ immediate: true },
);
const data = {
user: {
name: "Gustavo",
email: "g.miro@example.com",
avatar:
"data:image/svg+xml,%3Csvg xmlns=%22http://www.w3.org/2000/svg%22 width=%2248%22 height=%2248%22%3E%3Crect width=%2248%22 height=%2248%22 rx=%2224%22 fill=%22%236366f1%22/%3E%3C/svg%3E",
},
teams: [
{
name: "Acme Inc",
logo: "LayoutGrid",
plan: "Startup",
},
{
name: "Acme Corp.",
logo: "LayoutGrid",
plan: "Startup",
},
{
name: "Evil Corp.",
logo: "Command",
plan: "Free",
},
],
navMain: [
{
title: "Playground",
icon: "Terminal",
isActive: true,
items: [
{
title: "Menu Item",
},
{
title: "Menu Item",
},
{
title: "Menu Item",
},
{
title: "Menu Item",
},
],
},
{
title: "Models",
icon: "Bot",
items: [
{
title: "Menu Item",
},
{
title: "Menu Item",
},
{
title: "Menu Item",
},
{
title: "Menu Item",
},
],
},
{
title: "Documentation",
icon: "Book",
items: [
{
title: "Menu Item",
},
{
title: "Menu Item",
},
{
title: "Menu Item",
},
{
title: "Menu Item",
},
],
},
{
title: "Settings",
icon: "Settings",
items: [
{
title: "Menu Item",
},
{
title: "Menu Item",
},
{
title: "Menu Item",
},
{
title: "Menu Item",
},
],
},
],
projects: [
{
name: "Design Engineering",
icon: "Frame",
},
{
name: "Sales & Marketing",
icon: "ChartPie",
},
{
name: "Development",
icon: "Code",
},
{
name: "Advertisement",
icon: "Radio",
},
],
};
return { data, props };
},
template: `
<Sidebar v-bind="props" class="h-full min-h-0">
<SidebarHeader>
<TeamSwitcher :teams="data.teams" />
</SidebarHeader>
<SidebarContent>
<NavMain :items="data.navMain" />
<NavProjects :projects="data.projects" />
</SidebarContent>
<SidebarFooter>
<NavUser :user="data.user" />
</SidebarFooter>
<SidebarRail />
</Sidebar>
`,
});
const SidebarExample = defineComponent({
components: {
SidebarInset,
SidebarProvider,
SidebarTrigger,
AppSidebar,
},
props: {
open: Boolean,
},
template: `
<div class="h-[560px] w-[900px] max-w-full relative overflow-hidden [transform:translateZ(0)]">
<SidebarProvider class="min-h-0 h-full">
<AppSidebar :open="open" />
<SidebarInset class="min-h-0 h-full">
<header
class="flex h-16 shrink-0 items-center gap-2 transition-[width,height] ease-linear group-has-[[data-collapsible=icon]]/sidebar-wrapper:h-12"
>
<div class="flex items-center gap-2 px-4">
<SidebarTrigger class="-ml-1" />
</div>
</header>
</SidebarInset>
</SidebarProvider>
</div>
`,
});
const args = {
"open": true,
};
</script>
<template>
<SidebarExample :open="args.open" />
</template>
Examples
Main
<script setup lang="ts">
import { defineComponent } from "vue";
import { SidebarInset } from "@/components/ui/sidebar";
import { SidebarProvider } from "@/components/ui/sidebar";
import { SidebarTrigger } from "@/components/ui/sidebar";
import { Sidebar } from "@/components/ui/sidebar";
import { SidebarContent } from "@/components/ui/sidebar";
import { SidebarFooter } from "@/components/ui/sidebar";
import { SidebarHeader } from "@/components/ui/sidebar";
import { SidebarRail } from "@/components/ui/sidebar";
import { useSidebar } from "@/components/ui/sidebar";
import { Icon } from "@/components/ui/icon";
import { Collapsible } from "@/components/ui/collapsible";
import { CollapsibleContent } from "@/components/ui/collapsible";
import { CollapsibleTrigger } from "@/components/ui/collapsible";
import { SidebarGroup } from "@/components/ui/sidebar";
import { SidebarGroupLabel } from "@/components/ui/sidebar";
import { SidebarMenu } from "@/components/ui/sidebar";
import { SidebarMenuButton } from "@/components/ui/sidebar";
import { SidebarMenuItem } from "@/components/ui/sidebar";
import { SidebarMenuSub } from "@/components/ui/sidebar";
import { SidebarMenuSubButton } from "@/components/ui/sidebar";
import { SidebarMenuSubItem } from "@/components/ui/sidebar";
import { DropdownMenu } from "@/components/ui/dropdown-menu";
import { DropdownMenuContent } from "@/components/ui/dropdown-menu";
import { DropdownMenuItem } from "@/components/ui/dropdown-menu";
import { DropdownMenuSeparator } from "@/components/ui/dropdown-menu";
import { DropdownMenuTrigger } from "@/components/ui/dropdown-menu";
import { SidebarMenuAction } from "@/components/ui/sidebar";
import { Avatar } from "@/components/ui/avatar";
import { AvatarFallback } from "@/components/ui/avatar";
import { AvatarImage } from "@/components/ui/avatar";
import { DropdownMenuGroup } from "@/components/ui/dropdown-menu";
import { DropdownMenuLabel } from "@/components/ui/dropdown-menu";
import { DropdownMenuShortcut } from "@/components/ui/dropdown-menu";
import { ref } from "vue";
import { watch } from "vue";
const NavMain = defineComponent({
components: {
Icon,
Collapsible,
CollapsibleContent,
CollapsibleTrigger,
SidebarGroup,
SidebarGroupLabel,
SidebarMenu,
SidebarMenuButton,
SidebarMenuItem,
SidebarMenuSub,
SidebarMenuSubButton,
SidebarMenuSubItem,
},
props: {
items: {
type: Array,
required: true,
},
},
template: `
<SidebarGroup>
<SidebarGroupLabel>Platform</SidebarGroupLabel>
<SidebarMenu>
<Collapsible
v-for="item in items" :key="item.title" as-child :default-open="item.isActive"
class="group/collapsible"
>
<SidebarMenuItem>
<CollapsibleTrigger as-child>
<SidebarMenuButton :tooltip="item.title">
<Icon :name="item.icon" v-if="item.icon" />
<span>{{ item.title }}</span>
<Icon
name="ChevronRight"
class="ml-auto transition-transform duration-200 group-data-[state=open]/collapsible:rotate-90"
/>
</SidebarMenuButton>
</CollapsibleTrigger>
<CollapsibleContent>
<SidebarMenuSub>
<SidebarMenuSubItem v-for="subItem in item.items" :key="subItem.title">
<SidebarMenuSubButton as-child>
<a :href="subItem.url">
<span>{{ subItem.title }}</span>
</a>
</SidebarMenuSubButton>
</SidebarMenuSubItem>
</SidebarMenuSub>
</CollapsibleContent>
</SidebarMenuItem>
</Collapsible>
</SidebarMenu>
</SidebarGroup>
`,
});
const NavProjects = defineComponent({
components: {
Icon,
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuSeparator,
DropdownMenuTrigger,
SidebarGroup,
SidebarGroupLabel,
SidebarMenu,
SidebarMenuAction,
SidebarMenuButton,
SidebarMenuItem,
useSidebar,
},
props: {
projects: {
type: Array,
required: true,
},
},
setup() {
const { isMobile } = useSidebar();
return { isMobile };
},
template: `
<SidebarGroup class="group-data-[collapsible=icon]:hidden">
<SidebarGroupLabel>Projects</SidebarGroupLabel>
<SidebarMenu>
<SidebarMenuItem v-for="item in projects" :key="item.name">
<SidebarMenuButton as-child>
<a :href="item.url">
<Icon :name="item.icon" />
<span>{{ item.name }}</span>
</a>
</SidebarMenuButton>
<DropdownMenu>
<DropdownMenuTrigger as-child>
<SidebarMenuAction show-on-hover>
<Icon name="EllipsisVertical" />
<span class="sr-only">More</span>
</SidebarMenuAction>
</DropdownMenuTrigger>
<DropdownMenuContent
class="w-48 rounded-lg z-[51]" :side="isMobile ? 'bottom' : 'right'"
:align="isMobile ? 'end' : 'start'"
>
<DropdownMenuItem>
<Icon name="Folder" class="text-neutral-subtle" />
<span>View Project</span>
</DropdownMenuItem>
<DropdownMenuItem>
<Icon name="Forward" class="text-neutral-subtle" />
<span>Share Project</span>
</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem>
<Icon name="Trash2" class="text-neutral-subtle" />
<span>Delete Project</span>
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</SidebarMenuItem>
</SidebarMenu>
</SidebarGroup>
`,
});
const NavUser = defineComponent({
components: {
Avatar,
AvatarFallback,
AvatarImage,
DropdownMenu,
DropdownMenuContent,
DropdownMenuGroup,
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuTrigger,
SidebarMenu,
SidebarMenuButton,
SidebarMenuItem,
useSidebar,
Icon,
},
props: {
user: {
type: Object,
required: true,
},
},
setup() {
const { isMobile } = useSidebar();
return { isMobile };
},
template: `
<SidebarMenu>
<SidebarMenuItem>
<DropdownMenu>
<DropdownMenuTrigger as-child>
<SidebarMenuButton
size="lg"
class="data-[state=open]:bg-neutral-strong data-[state=open]:text-neutral-subtle"
>
<Avatar class="h-8 w-8">
<AvatarImage :src="user.avatar" :alt="user.name" />
<AvatarFallback name="CN" class="" />
</Avatar>
<div class="grid flex-1 text-left text-sm leading-tight">
<span class="truncate font-semibold">{{ user.name }}</span>
<span class="truncate text-xs">{{ user.email }}</span>
</div>
<Icon name="ChevronDown" class="ml-auto size-4" />
</SidebarMenuButton>
</DropdownMenuTrigger>
<DropdownMenuContent
class="w-full min-w-56 rounded-lg z-[51]"
:side="isMobile ? 'bottom' : 'right'"
align="end"
:side-offset="4"
>
<DropdownMenuLabel class="p-0 font-normal">
<div class="flex items-center gap-2 px-1 py-1.5 text-left text-sm">
<Avatar class="h-8 w-8">
<AvatarImage :src="user.avatar" :alt="user.name" />
<AvatarFallback name="CN" />
</Avatar>
<div class="grid flex-1 text-left text-sm leading-tight">
<span class="truncate font-semibold">{{ user.name }}</span>
<span class="truncate text-xs">{{ user.email }}</span>
</div>
</div>
</DropdownMenuLabel>
<DropdownMenuSeparator />
<DropdownMenuGroup>
<DropdownMenuItem>
<Icon name="Sparkles" />
Upgrade to Pro
</DropdownMenuItem>
</DropdownMenuGroup>
<DropdownMenuSeparator />
<DropdownMenuGroup>
<DropdownMenuItem>
<Icon name="CloudCheck" />
Account
</DropdownMenuItem>
<DropdownMenuItem>
<Icon name="CreditCard" />
Billing
</DropdownMenuItem>
<DropdownMenuItem>
<Icon name="Bell" />
Notifications
</DropdownMenuItem>
</DropdownMenuGroup>
<DropdownMenuSeparator />
<DropdownMenuItem>
<Icon name="LogOut" />
Log out
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</SidebarMenuItem>
</SidebarMenu>
`,
});
const TeamSwitcher = defineComponent({
components: {
Icon,
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuShortcut,
DropdownMenuTrigger,
SidebarMenu,
SidebarMenuButton,
SidebarMenuItem,
useSidebar,
},
props: {
teams: {
type: Array,
required: true,
},
},
setup(props) {
const { isMobile } = useSidebar();
const activeTeam = ref(props.teams[0]);
return { isMobile, activeTeam };
},
template: `
<SidebarMenu class="py-0 px-0">
<SidebarMenuItem>
<DropdownMenu>
<DropdownMenuTrigger as-child>
<SidebarMenuButton size="lg">
<div class="rounded-lg p-2 bg-brand-strong"><Icon :name="activeTeam.logo" class="text-white"/></div>
<div class="grid flex-1 text-left text-sm leading-tight">
<span class="truncate font-semibold">
{{ activeTeam.name }}
</span>
<span class="truncate text-xs">{{ activeTeam.plan }}</span>
</div>
<Icon name="ChevronDown" class="ml-auto" />
</SidebarMenuButton>
</DropdownMenuTrigger>
<DropdownMenuContent
class="w-[--reka-dropdownmenu-trigger-width] min-w-56 rounded-lg z-[51]"
align="start"
:side="isMobile ? 'bottom' : 'right'"
:side-offset="4"
>
<DropdownMenuLabel class="text-xs text-neutral-subtle">
Teams
</DropdownMenuLabel>
<DropdownMenuItem
v-for="(team, index) in teams"
:key="team.name"
class="gap-2 p-2"
@click="activeTeam = team"
>
<div class="flex size-6 items-center justify-center rounded-sm border">
<Icon :name="team.logo" class="size-4 shrink-0" />
</div>
{{ team.name }}
<DropdownMenuShortcut>⌘{{ index + 1 }}</DropdownMenuShortcut>
</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem class="gap-2 p-2">
<div class="flex size-6 items-center justify-center rounded-md border bg-background">
<Icon name="Plus" class="size-4" />
</div>
<div class="font-medium text-neutral-subtle">
Add team
</div>
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</SidebarMenuItem>
</SidebarMenu>
`,
});
const AppSidebar = defineComponent({
components: {
Sidebar,
SidebarContent,
SidebarFooter,
SidebarHeader,
SidebarRail,
useSidebar,
NavMain,
NavProjects,
NavUser,
TeamSwitcher,
},
props: {
open: Boolean,
collapsible: {
type: String,
default: "icon",
},
},
setup(props) {
const { setOpen } = useSidebar();
watch(
() => props.open,
(newValue) => {
if (newValue !== undefined) {
setOpen(newValue);
}
},
{ immediate: true },
);
const data = {
user: {
name: "Gustavo",
email: "g.miro@example.com",
avatar:
"data:image/svg+xml,%3Csvg xmlns=%22http://www.w3.org/2000/svg%22 width=%2248%22 height=%2248%22%3E%3Crect width=%2248%22 height=%2248%22 rx=%2224%22 fill=%22%236366f1%22/%3E%3C/svg%3E",
},
teams: [
{
name: "Acme Inc",
logo: "LayoutGrid",
plan: "Startup",
},
{
name: "Acme Corp.",
logo: "LayoutGrid",
plan: "Startup",
},
{
name: "Evil Corp.",
logo: "Command",
plan: "Free",
},
],
navMain: [
{
title: "Playground",
icon: "Terminal",
isActive: true,
items: [
{
title: "Menu Item",
},
{
title: "Menu Item",
},
{
title: "Menu Item",
},
{
title: "Menu Item",
},
],
},
{
title: "Models",
icon: "Bot",
items: [
{
title: "Menu Item",
},
{
title: "Menu Item",
},
{
title: "Menu Item",
},
{
title: "Menu Item",
},
],
},
{
title: "Documentation",
icon: "Book",
items: [
{
title: "Menu Item",
},
{
title: "Menu Item",
},
{
title: "Menu Item",
},
{
title: "Menu Item",
},
],
},
{
title: "Settings",
icon: "Settings",
items: [
{
title: "Menu Item",
},
{
title: "Menu Item",
},
{
title: "Menu Item",
},
{
title: "Menu Item",
},
],
},
],
projects: [
{
name: "Design Engineering",
icon: "Frame",
},
{
name: "Sales & Marketing",
icon: "ChartPie",
},
{
name: "Development",
icon: "Code",
},
{
name: "Advertisement",
icon: "Radio",
},
],
};
return { data, props };
},
template: `
<Sidebar v-bind="props" class="h-full min-h-0">
<SidebarHeader>
<TeamSwitcher :teams="data.teams" />
</SidebarHeader>
<SidebarContent>
<NavMain :items="data.navMain" />
<NavProjects :projects="data.projects" />
</SidebarContent>
<SidebarFooter>
<NavUser :user="data.user" />
</SidebarFooter>
<SidebarRail />
</Sidebar>
`,
});
const SidebarExample = defineComponent({
components: {
SidebarInset,
SidebarProvider,
SidebarTrigger,
AppSidebar,
},
props: {
open: Boolean,
},
template: `
<div class="h-[560px] w-[900px] max-w-full relative overflow-hidden [transform:translateZ(0)]">
<SidebarProvider class="min-h-0 h-full">
<AppSidebar :open="open" />
<SidebarInset class="min-h-0 h-full">
<header
class="flex h-16 shrink-0 items-center gap-2 transition-[width,height] ease-linear group-has-[[data-collapsible=icon]]/sidebar-wrapper:h-12"
>
<div class="flex items-center gap-2 px-4">
<SidebarTrigger class="-ml-1" />
</div>
</header>
</SidebarInset>
</SidebarProvider>
</div>
`,
});
const args = {
"open": true,
};
</script>
<template>
<div class="h-[560px]">
<SidebarExample :open="args.open" />
</div>
</template>
Collapsed
<script setup lang="ts">
import { defineComponent } from "vue";
import { SidebarInset } from "@/components/ui/sidebar";
import { SidebarProvider } from "@/components/ui/sidebar";
import { SidebarTrigger } from "@/components/ui/sidebar";
import { Sidebar } from "@/components/ui/sidebar";
import { SidebarContent } from "@/components/ui/sidebar";
import { SidebarFooter } from "@/components/ui/sidebar";
import { SidebarHeader } from "@/components/ui/sidebar";
import { SidebarRail } from "@/components/ui/sidebar";
import { useSidebar } from "@/components/ui/sidebar";
import { Icon } from "@/components/ui/icon";
import { Collapsible } from "@/components/ui/collapsible";
import { CollapsibleContent } from "@/components/ui/collapsible";
import { CollapsibleTrigger } from "@/components/ui/collapsible";
import { SidebarGroup } from "@/components/ui/sidebar";
import { SidebarGroupLabel } from "@/components/ui/sidebar";
import { SidebarMenu } from "@/components/ui/sidebar";
import { SidebarMenuButton } from "@/components/ui/sidebar";
import { SidebarMenuItem } from "@/components/ui/sidebar";
import { SidebarMenuSub } from "@/components/ui/sidebar";
import { SidebarMenuSubButton } from "@/components/ui/sidebar";
import { SidebarMenuSubItem } from "@/components/ui/sidebar";
import { DropdownMenu } from "@/components/ui/dropdown-menu";
import { DropdownMenuContent } from "@/components/ui/dropdown-menu";
import { DropdownMenuItem } from "@/components/ui/dropdown-menu";
import { DropdownMenuSeparator } from "@/components/ui/dropdown-menu";
import { DropdownMenuTrigger } from "@/components/ui/dropdown-menu";
import { SidebarMenuAction } from "@/components/ui/sidebar";
import { Avatar } from "@/components/ui/avatar";
import { AvatarFallback } from "@/components/ui/avatar";
import { AvatarImage } from "@/components/ui/avatar";
import { DropdownMenuGroup } from "@/components/ui/dropdown-menu";
import { DropdownMenuLabel } from "@/components/ui/dropdown-menu";
import { DropdownMenuShortcut } from "@/components/ui/dropdown-menu";
import { ref } from "vue";
import { watch } from "vue";
const NavMain = defineComponent({
components: {
Icon,
Collapsible,
CollapsibleContent,
CollapsibleTrigger,
SidebarGroup,
SidebarGroupLabel,
SidebarMenu,
SidebarMenuButton,
SidebarMenuItem,
SidebarMenuSub,
SidebarMenuSubButton,
SidebarMenuSubItem,
},
props: {
items: {
type: Array,
required: true,
},
},
template: `
<SidebarGroup>
<SidebarGroupLabel>Platform</SidebarGroupLabel>
<SidebarMenu>
<Collapsible
v-for="item in items" :key="item.title" as-child :default-open="item.isActive"
class="group/collapsible"
>
<SidebarMenuItem>
<CollapsibleTrigger as-child>
<SidebarMenuButton :tooltip="item.title">
<Icon :name="item.icon" v-if="item.icon" />
<span>{{ item.title }}</span>
<Icon
name="ChevronRight"
class="ml-auto transition-transform duration-200 group-data-[state=open]/collapsible:rotate-90"
/>
</SidebarMenuButton>
</CollapsibleTrigger>
<CollapsibleContent>
<SidebarMenuSub>
<SidebarMenuSubItem v-for="subItem in item.items" :key="subItem.title">
<SidebarMenuSubButton as-child>
<a :href="subItem.url">
<span>{{ subItem.title }}</span>
</a>
</SidebarMenuSubButton>
</SidebarMenuSubItem>
</SidebarMenuSub>
</CollapsibleContent>
</SidebarMenuItem>
</Collapsible>
</SidebarMenu>
</SidebarGroup>
`,
});
const NavProjects = defineComponent({
components: {
Icon,
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuSeparator,
DropdownMenuTrigger,
SidebarGroup,
SidebarGroupLabel,
SidebarMenu,
SidebarMenuAction,
SidebarMenuButton,
SidebarMenuItem,
useSidebar,
},
props: {
projects: {
type: Array,
required: true,
},
},
setup() {
const { isMobile } = useSidebar();
return { isMobile };
},
template: `
<SidebarGroup class="group-data-[collapsible=icon]:hidden">
<SidebarGroupLabel>Projects</SidebarGroupLabel>
<SidebarMenu>
<SidebarMenuItem v-for="item in projects" :key="item.name">
<SidebarMenuButton as-child>
<a :href="item.url">
<Icon :name="item.icon" />
<span>{{ item.name }}</span>
</a>
</SidebarMenuButton>
<DropdownMenu>
<DropdownMenuTrigger as-child>
<SidebarMenuAction show-on-hover>
<Icon name="EllipsisVertical" />
<span class="sr-only">More</span>
</SidebarMenuAction>
</DropdownMenuTrigger>
<DropdownMenuContent
class="w-48 rounded-lg z-[51]" :side="isMobile ? 'bottom' : 'right'"
:align="isMobile ? 'end' : 'start'"
>
<DropdownMenuItem>
<Icon name="Folder" class="text-neutral-subtle" />
<span>View Project</span>
</DropdownMenuItem>
<DropdownMenuItem>
<Icon name="Forward" class="text-neutral-subtle" />
<span>Share Project</span>
</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem>
<Icon name="Trash2" class="text-neutral-subtle" />
<span>Delete Project</span>
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</SidebarMenuItem>
</SidebarMenu>
</SidebarGroup>
`,
});
const NavUser = defineComponent({
components: {
Avatar,
AvatarFallback,
AvatarImage,
DropdownMenu,
DropdownMenuContent,
DropdownMenuGroup,
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuTrigger,
SidebarMenu,
SidebarMenuButton,
SidebarMenuItem,
useSidebar,
Icon,
},
props: {
user: {
type: Object,
required: true,
},
},
setup() {
const { isMobile } = useSidebar();
return { isMobile };
},
template: `
<SidebarMenu>
<SidebarMenuItem>
<DropdownMenu>
<DropdownMenuTrigger as-child>
<SidebarMenuButton
size="lg"
class="data-[state=open]:bg-neutral-strong data-[state=open]:text-neutral-subtle"
>
<Avatar class="h-8 w-8">
<AvatarImage :src="user.avatar" :alt="user.name" />
<AvatarFallback name="CN" class="" />
</Avatar>
<div class="grid flex-1 text-left text-sm leading-tight">
<span class="truncate font-semibold">{{ user.name }}</span>
<span class="truncate text-xs">{{ user.email }}</span>
</div>
<Icon name="ChevronDown" class="ml-auto size-4" />
</SidebarMenuButton>
</DropdownMenuTrigger>
<DropdownMenuContent
class="w-full min-w-56 rounded-lg z-[51]"
:side="isMobile ? 'bottom' : 'right'"
align="end"
:side-offset="4"
>
<DropdownMenuLabel class="p-0 font-normal">
<div class="flex items-center gap-2 px-1 py-1.5 text-left text-sm">
<Avatar class="h-8 w-8">
<AvatarImage :src="user.avatar" :alt="user.name" />
<AvatarFallback name="CN" />
</Avatar>
<div class="grid flex-1 text-left text-sm leading-tight">
<span class="truncate font-semibold">{{ user.name }}</span>
<span class="truncate text-xs">{{ user.email }}</span>
</div>
</div>
</DropdownMenuLabel>
<DropdownMenuSeparator />
<DropdownMenuGroup>
<DropdownMenuItem>
<Icon name="Sparkles" />
Upgrade to Pro
</DropdownMenuItem>
</DropdownMenuGroup>
<DropdownMenuSeparator />
<DropdownMenuGroup>
<DropdownMenuItem>
<Icon name="CloudCheck" />
Account
</DropdownMenuItem>
<DropdownMenuItem>
<Icon name="CreditCard" />
Billing
</DropdownMenuItem>
<DropdownMenuItem>
<Icon name="Bell" />
Notifications
</DropdownMenuItem>
</DropdownMenuGroup>
<DropdownMenuSeparator />
<DropdownMenuItem>
<Icon name="LogOut" />
Log out
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</SidebarMenuItem>
</SidebarMenu>
`,
});
const TeamSwitcher = defineComponent({
components: {
Icon,
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuShortcut,
DropdownMenuTrigger,
SidebarMenu,
SidebarMenuButton,
SidebarMenuItem,
useSidebar,
},
props: {
teams: {
type: Array,
required: true,
},
},
setup(props) {
const { isMobile } = useSidebar();
const activeTeam = ref(props.teams[0]);
return { isMobile, activeTeam };
},
template: `
<SidebarMenu class="py-0 px-0">
<SidebarMenuItem>
<DropdownMenu>
<DropdownMenuTrigger as-child>
<SidebarMenuButton size="lg">
<div class="rounded-lg p-2 bg-brand-strong"><Icon :name="activeTeam.logo" class="text-white"/></div>
<div class="grid flex-1 text-left text-sm leading-tight">
<span class="truncate font-semibold">
{{ activeTeam.name }}
</span>
<span class="truncate text-xs">{{ activeTeam.plan }}</span>
</div>
<Icon name="ChevronDown" class="ml-auto" />
</SidebarMenuButton>
</DropdownMenuTrigger>
<DropdownMenuContent
class="w-[--reka-dropdownmenu-trigger-width] min-w-56 rounded-lg z-[51]"
align="start"
:side="isMobile ? 'bottom' : 'right'"
:side-offset="4"
>
<DropdownMenuLabel class="text-xs text-neutral-subtle">
Teams
</DropdownMenuLabel>
<DropdownMenuItem
v-for="(team, index) in teams"
:key="team.name"
class="gap-2 p-2"
@click="activeTeam = team"
>
<div class="flex size-6 items-center justify-center rounded-sm border">
<Icon :name="team.logo" class="size-4 shrink-0" />
</div>
{{ team.name }}
<DropdownMenuShortcut>⌘{{ index + 1 }}</DropdownMenuShortcut>
</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem class="gap-2 p-2">
<div class="flex size-6 items-center justify-center rounded-md border bg-background">
<Icon name="Plus" class="size-4" />
</div>
<div class="font-medium text-neutral-subtle">
Add team
</div>
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</SidebarMenuItem>
</SidebarMenu>
`,
});
const AppSidebar = defineComponent({
components: {
Sidebar,
SidebarContent,
SidebarFooter,
SidebarHeader,
SidebarRail,
useSidebar,
NavMain,
NavProjects,
NavUser,
TeamSwitcher,
},
props: {
open: Boolean,
collapsible: {
type: String,
default: "icon",
},
},
setup(props) {
const { setOpen } = useSidebar();
watch(
() => props.open,
(newValue) => {
if (newValue !== undefined) {
setOpen(newValue);
}
},
{ immediate: true },
);
const data = {
user: {
name: "Gustavo",
email: "g.miro@example.com",
avatar:
"data:image/svg+xml,%3Csvg xmlns=%22http://www.w3.org/2000/svg%22 width=%2248%22 height=%2248%22%3E%3Crect width=%2248%22 height=%2248%22 rx=%2224%22 fill=%22%236366f1%22/%3E%3C/svg%3E",
},
teams: [
{
name: "Acme Inc",
logo: "LayoutGrid",
plan: "Startup",
},
{
name: "Acme Corp.",
logo: "LayoutGrid",
plan: "Startup",
},
{
name: "Evil Corp.",
logo: "Command",
plan: "Free",
},
],
navMain: [
{
title: "Playground",
icon: "Terminal",
isActive: true,
items: [
{
title: "Menu Item",
},
{
title: "Menu Item",
},
{
title: "Menu Item",
},
{
title: "Menu Item",
},
],
},
{
title: "Models",
icon: "Bot",
items: [
{
title: "Menu Item",
},
{
title: "Menu Item",
},
{
title: "Menu Item",
},
{
title: "Menu Item",
},
],
},
{
title: "Documentation",
icon: "Book",
items: [
{
title: "Menu Item",
},
{
title: "Menu Item",
},
{
title: "Menu Item",
},
{
title: "Menu Item",
},
],
},
{
title: "Settings",
icon: "Settings",
items: [
{
title: "Menu Item",
},
{
title: "Menu Item",
},
{
title: "Menu Item",
},
{
title: "Menu Item",
},
],
},
],
projects: [
{
name: "Design Engineering",
icon: "Frame",
},
{
name: "Sales & Marketing",
icon: "ChartPie",
},
{
name: "Development",
icon: "Code",
},
{
name: "Advertisement",
icon: "Radio",
},
],
};
return { data, props };
},
template: `
<Sidebar v-bind="props" class="h-full min-h-0">
<SidebarHeader>
<TeamSwitcher :teams="data.teams" />
</SidebarHeader>
<SidebarContent>
<NavMain :items="data.navMain" />
<NavProjects :projects="data.projects" />
</SidebarContent>
<SidebarFooter>
<NavUser :user="data.user" />
</SidebarFooter>
<SidebarRail />
</Sidebar>
`,
});
const SidebarExample = defineComponent({
components: {
SidebarInset,
SidebarProvider,
SidebarTrigger,
AppSidebar,
},
props: {
open: Boolean,
},
template: `
<div class="h-[560px] w-[900px] max-w-full relative overflow-hidden [transform:translateZ(0)]">
<SidebarProvider class="min-h-0 h-full">
<AppSidebar :open="open" />
<SidebarInset class="min-h-0 h-full">
<header
class="flex h-16 shrink-0 items-center gap-2 transition-[width,height] ease-linear group-has-[[data-collapsible=icon]]/sidebar-wrapper:h-12"
>
<div class="flex items-center gap-2 px-4">
<SidebarTrigger class="-ml-1" />
</div>
</header>
</SidebarInset>
</SidebarProvider>
</div>
`,
});
const args = {
"open": false,
};
</script>
<template>
<div class="h-[560px]">
<SidebarExample :open="args.open" />
</div>
</template>
API reference
Frontfriend-owned props. Native HTML and inherited primitive props
remain documented by Reka UI.
SidebarContent
| Prop | Type | Default | Description |
class | HTMLAttributes["class"] | — | |
SidebarFooter
| Prop | Type | Default | Description |
class | HTMLAttributes["class"] | — | |
SidebarGroup
| Prop | Type | Default | Description |
class | HTMLAttributes["class"] | — | |
SidebarGroupAction
| Prop | Type | Default | Description |
class | HTMLAttributes["class"] | — | |
SidebarGroupContent
| Prop | Type | Default | Description |
class | HTMLAttributes["class"] | — | |
SidebarGroupLabel
| Prop | Type | Default | Description |
class | HTMLAttributes["class"] | — | |
SidebarHeader
| Prop | Type | Default | Description |
class | HTMLAttributes["class"] | — | |
SidebarInput
| Prop | Type | Default | Description |
class | HTMLAttributes["class"] | — | |
SidebarInset
| Prop | Type | Default | Description |
class | HTMLAttributes["class"] | — | |
SidebarMenu
| Prop | Type | Default | Description |
class | HTMLAttributes["class"] | — | |
SidebarMenuAction
| Prop | Type | Default | Description |
class | HTMLAttributes["class"] | — | |
showOnHover | boolean | — | |
SidebarMenuBadge
| Prop | Type | Default | Description |
class | HTMLAttributes["class"] | — | |
SidebarMenuButton
| Prop | Type | Default | Description |
tooltip | string | Component | — | |
SidebarMenuItem
| Prop | Type | Default | Description |
class | HTMLAttributes["class"] | — | |
SidebarMenuSkeleton
| Prop | Type | Default | Description |
class | HTMLAttributes["class"] | — | |
showIcon | boolean | — | |
SidebarMenuSub
| Prop | Type | Default | Description |
class | HTMLAttributes["class"] | — | |
SidebarMenuSubButton
| Prop | Type | Default | Description |
class | HTMLAttributes["class"] | — | |
isActive | boolean | — | |
size | "sm" | "md" | — | |
SidebarMenuSubItem
| Prop | Type | Default | Description |
class | HTMLAttributes["class"] | — | |
SidebarProvider
| Prop | Type | Default | Description |
class | HTMLAttributes["class"] | — | |
defaultOpen | boolean | — | |
open | boolean | — | |
SidebarRail
| Prop | Type | Default | Description |
class | HTMLAttributes["class"] | — | |
SidebarSeparator
| Prop | Type | Default | Description |
class | HTMLAttributes["class"] | — | |
SidebarTrigger
| Prop | Type | Default | Description |
class | string | — | |
icon | IconName | — | |
Configuration determines the actual classes and available styling hooks.
See Theming & component configuration before introducing
a repeated local override.