Skip to main content
A vertical navigation menu for settings and configuration pages. Built as composable components that can be combined to create grouped menu structures with icons, separators, and active states.

Component Location

services/frontend/src/components/ui/settings-menu/
�� SettingsMenu.vue           # Root container
�� SettingsMenuGroup.vue      # Group with optional title
�� SettingsMenuItem.vue       # Clickable menu item (RouterLink)
�� SettingsMenuSeparator.vue  # Horizontal separator
�� index.ts                   # Exports

Components

SettingsMenu

Root container that provides consistent spacing between groups.
<SettingsMenu>
  <!-- SettingsMenuGroup or SettingsMenuSeparator children -->
</SettingsMenu>

SettingsMenuGroup

Groups related menu items together. The title prop is optional.
PropTypeDefaultDescription
titlestringundefinedOptional heading above the group
<!-- With title -->
<SettingsMenuGroup title="Account Settings">
  <SettingsMenuItem to="/settings/profile">Profile</SettingsMenuItem>
</SettingsMenuGroup>

<!-- Without title -->
<SettingsMenuGroup>
  <SettingsMenuItem to="/settings/other">Other</SettingsMenuItem>
</SettingsMenuGroup>

SettingsMenuItem

A RouterLink-based menu item with support for icons.
PropTypeDefaultDescription
tostringrequiredRouterLink destination path
activebooleanfalseWhether the item is currently selected
iconComponentundefinedLucide icon component
iconUrlstringundefinedURL for an image icon
Icon priority: iconUrl takes precedence over icon. If neither is provided, no icon is rendered.
<!-- With URL icon -->
<SettingsMenuItem
  to="/config/vscode"
  icon-url="/icons/vscode.svg"
  :active="currentPath === '/config/vscode'"
>
  VS Code
</SettingsMenuItem>

<!-- With Lucide icon -->
<SettingsMenuItem
  to="/settings/profile"
  :icon="User"
  :active="currentPath === '/settings/profile'"
>
  Profile
</SettingsMenuItem>

<!-- Without icon -->
<SettingsMenuItem to="/settings/other" :active="false">
  Other Settings
</SettingsMenuItem>

SettingsMenuSeparator

A horizontal line to visually separate groups.
<SettingsMenuSeparator />

Usage

Import all components from the settings-menu index:
<script setup lang="ts">
import { useRoute } from 'vue-router'
import { User, Settings, Bell } from 'lucide-vue-next'
import {
  SettingsMenu,
  SettingsMenuGroup,
  SettingsMenuItem,
  SettingsMenuSeparator
} from '@/components/ui/settings-menu'

const route = useRoute()
</script>

<template>
  <aside class="w-1/5 border-r pr-8">
    <SettingsMenu>
      <SettingsMenuGroup title="Account">
        <SettingsMenuItem
          to="/settings/profile"
          :icon="User"
          :active="route.path === '/settings/profile'"
        >
          Profile
        </SettingsMenuItem>
        <SettingsMenuItem
          to="/settings/notifications"
          :icon="Bell"
          :active="route.path === '/settings/notifications'"
        >
          Notifications
        </SettingsMenuItem>
      </SettingsMenuGroup>

      <SettingsMenuSeparator />

      <SettingsMenuGroup title="System">
        <SettingsMenuItem
          to="/settings/preferences"
          :icon="Settings"
          :active="route.path === '/settings/preferences'"
        >
          Preferences
        </SettingsMenuItem>
      </SettingsMenuGroup>
    </SettingsMenu>
  </aside>
</template>

Dynamic Menu from Data

When rendering menus from an array of items:
<script setup lang="ts">
import { ref } from 'vue'
import { useRoute } from 'vue-router'
import {
  SettingsMenu,
  SettingsMenuGroup,
  SettingsMenuItem,
  SettingsMenuSeparator
} from '@/components/ui/settings-menu'

interface MenuItem {
  id: string
  name: string
  iconPath?: string
}

interface MenuCategory {
  id: string
  name: string
  items: MenuItem[]
}

const route = useRoute()
const categories = ref<MenuCategory[]>([])
const selectedId = ref<string>('')
</script>

<template>
  <SettingsMenu>
    <template v-for="(category, index) in categories" :key="category.id">
      <SettingsMenuGroup :title="category.name">
        <SettingsMenuItem
          v-for="item in category.items"
          :key="item.id"
          :to="`/config/${category.id}/${item.id}`"
          :icon-url="item.iconPath"
          :active="selectedId === item.id"
        >
          {{ item.name }}
        </SettingsMenuItem>
      </SettingsMenuGroup>
      <SettingsMenuSeparator v-if="index < categories.length - 1" />
    </template>
  </SettingsMenu>
</template>

Styling

The component uses shadcn-vue design tokens for consistent theming:
  • Active state: bg-secondary text-secondary-foreground font-medium border border-border
  • Inactive state: text-muted-foreground hover:bg-secondary/50 hover:text-foreground
  • Group title: font-semibold text-sm text-muted-foreground
  • Separator: h-px bg-border
  • Icon size: w-5 h-5