CSC Design System next
VueFlavour Vue React Angular TypeScript PrimaryCSC UI ColorsPrimarySecondaryAccentCustom ColorsRedOrangeGreenBluePurplePink
Guides Getting started Customization Data visualization Migration guide Componentsc-accordionc-alertc-autocompletec-badgec-buttonc-button-groupc-cardc-checkboxc-csc-logoc-data-tablec-dividerc-iconc-icon-buttonc-inputc-linkc-listc-loaderc-login-buttonsc-login-cardc-mainc-menuc-messagec-modalc-navigation-buttonc-otp-inputc-pagec-paginationc-popoverc-progress-barc-progress-circlec-radio-groupc-selectc-side-navigationc-sliderc-spinnerc-statusc-stepsc-switchc-tablec-tabsc-tagsc-text-fieldc-toastsc-toolbarc-tooltip

<c-menu>

Examples

Basic
Vue React Angular TypeScript
<template>
  <div class="example-row">
    <c-menu @select="onSelect">
      <c-button slot="trigger" outlined>
        Device
        <c-icon :path="mdiChevronDown" />
      </c-button>

      <c-menu-label>Type</c-menu-label>

      <c-menu-item value="phone">Phone</c-menu-item>

      <c-menu-item value="tablet">Tablet</c-menu-item>

      <c-menu-item value="desktop">Desktop</c-menu-item>

      <c-divider />

      <c-menu-item value="forget" danger>Forget this device</c-menu-item>
    </c-menu>

    <p>Selected: {{ selected ?? '—' }}</p>
  </div>
</template>

<script setup lang="ts">
import { ref } from 'vue';

import { mdiChevronDown } from '@mdi/js';

const selected = ref<null | string>(null);

const onSelect = (event: Event) => {
  selected.value = (event as CustomEvent<{ value: string }>).detail.value;
};
</script>
Submenu
Vue React Angular TypeScript
<template>
  <div class="example-row">
    <c-menu @select="onSelect">
      <c-button slot="trigger" ghost>
        Export
        <c-icon :path="mdiChevronDown" />
      </c-button>

      <c-menu-item value="documents">
        Documents

        <c-menu-item slot="submenu" value="pdf">
          <c-icon :path="mdiFilePdfBox" />
          PDF
        </c-menu-item>

        <c-menu-item slot="submenu" value="docx">
          <c-icon :path="mdiFileDocument" />
          Word document
        </c-menu-item>
      </c-menu-item>

      <c-menu-item value="images">
        Images

        <c-menu-item slot="submenu" value="png">
          <c-icon :path="mdiFilePngBox" />
          PNG
        </c-menu-item>

        <c-menu-item slot="submenu" value="jpg">
          <c-icon :path="mdiFileJpgBox" />
          JPEG
        </c-menu-item>
      </c-menu-item>

      <c-divider />

      <c-menu-item value="settings">Export settings…</c-menu-item>
    </c-menu>

    <p>Selected: {{ selected ?? '—' }}</p>
  </div>
</template>

<script setup lang="ts">
import { ref } from 'vue';

import {
  mdiChevronDown,
  mdiFileDocument,
  mdiFileJpgBox,
  mdiFilePdfBox,
  mdiFilePngBox,
} from '@mdi/js';

const selected = ref<null | string>(null);

const onSelect = (event: Event) => {
  selected.value = (event as CustomEvent<{ value: string }>).detail.value;
};
</script>
Active items · c-menu-item
Vue React Angular TypeScript
<template>
  <div class="example-row">
    <c-menu @select="onTheme">
      <c-button slot="trigger" text>
        <c-icon :path="mdiThemeLightDark" />
        Theme: {{ theme }}
        <c-icon :path="mdiChevronDown" />
      </c-button>

      <c-menu-label>Theme</c-menu-label>

      <c-menu-item
        :active.prop="theme === 'dark'"
        :icon="mdiWeatherNight"
        value="dark"
      >
        Dark
      </c-menu-item>

      <c-menu-item
        :active.prop="theme === 'light'"
        :icon="mdiWeatherSunny"
        value="light"
      >
        Light
      </c-menu-item>

      <c-menu-item
        :active.prop="theme === 'system'"
        :icon="mdiMonitor"
        value="system"
      >
        System
      </c-menu-item>
    </c-menu>

    <c-menu @select="onSort">
      <c-button slot="trigger" text>
        Sort by: {{ sortBy }}
        <c-icon :path="mdiChevronDown" />
      </c-button>

      <c-menu-item
        v-for="key in sortKeys"
        :key="key"
        :active.prop="sortBy === key"
        :active-icon="mdiRadioboxMarked"
        :value="key"
      >
        {{ key }}
      </c-menu-item>
    </c-menu>
  </div>
</template>

<script setup lang="ts">
import { ref } from 'vue';

import {
  mdiChevronDown,
  mdiMonitor,
  mdiRadioboxMarked,
  mdiThemeLightDark,
  mdiWeatherNight,
  mdiWeatherSunny,
} from '@mdi/js';

const theme = ref('dark');

const sortKeys = ['name', 'size', 'date'];

const sortBy = ref('name');

const onTheme = (event: Event) => {
  theme.value = (event as CustomEvent<{ value: string }>).detail.value;
};

const onSort = (event: Event) => {
  sortBy.value = (event as CustomEvent<{ value: string }>).detail.value;
};
</script>
Basic · c-menu-item
Vue React Angular TypeScript
<template>
  <div class="example-row">
    <c-menu @select="onSelect">
      <c-button slot="trigger" text>
        <c-icon :path="mdiAccount" />
        Account
        <c-icon :path="mdiChevronDown" />
      </c-button>

      <c-menu-item value="profile">View profile</c-menu-item>

      <c-menu-item value="billing" disabled>Billing (unavailable)</c-menu-item>

      <c-menu-item value="invite">Invite teammate</c-menu-item>

      <c-divider />

      <c-menu-item value="delete" danger>Delete account</c-menu-item>
    </c-menu>

    <p>Selected: {{ selected ?? '—' }}</p>
  </div>
</template>

<script setup lang="ts">
import { ref } from 'vue';

import { mdiAccount, mdiChevronDown } from '@mdi/js';

const selected = ref<null | string>(null);

const onSelect = (event: Event) => {
  selected.value = (event as CustomEvent<{ value: string }>).detail.value;
};
</script>
Submenu · c-menu-item
Vue React Angular TypeScript
<template>
  <div class="example-row">
    <c-menu @select="onSelect">
      <c-button slot="trigger" ghost>
        Export
        <c-icon :path="mdiChevronDown" />
      </c-button>

      <c-menu-item value="documents">
        Documents

        <c-menu-item slot="submenu" value="pdf">
          <c-icon :path="mdiFilePdfBox" />
          PDF
        </c-menu-item>

        <c-menu-item slot="submenu" value="docx">
          <c-icon :path="mdiFileDocument" />
          Word document
        </c-menu-item>
      </c-menu-item>

      <c-menu-item value="settings">Export settings…</c-menu-item>
    </c-menu>

    <p>Selected: {{ selected ?? '—' }}</p>
  </div>
</template>

<script setup lang="ts">
import { ref } from 'vue';

import { mdiChevronDown, mdiFileDocument, mdiFilePdfBox } from '@mdi/js';

const selected = ref<null | string>(null);

const onSelect = (event: Event) => {
  selected.value = (event as CustomEvent<{ value: string }>).detail.value;
};
</script>
Basic · c-menu-label
Vue React Angular TypeScript
<template>
  <div class="example-row">
    <c-menu @select="onSelect">
      <c-button slot="trigger" outlined>
        Settings
        <c-icon :path="mdiChevronDown" />
      </c-button>

      <c-menu-label>Account</c-menu-label>

      <c-menu-item value="profile">Profile</c-menu-item>

      <c-menu-item value="notifications">Notifications</c-menu-item>

      <c-divider />

      <c-menu-label>Workspace</c-menu-label>

      <c-menu-item value="members">Members</c-menu-item>

      <c-menu-item value="billing">Billing</c-menu-item>
    </c-menu>

    <p>Selected: {{ selected ?? '—' }}</p>
  </div>
</template>

<script setup lang="ts">
import { ref } from 'vue';

import { mdiChevronDown } from '@mdi/js';

const selected = ref<null | string>(null);

const onSelect = (event: Event) => {
  selected.value = (event as CustomEvent<{ value: string }>).detail.value;
};
</script>

API reference

<c-menu>

Properties

PropertyAttributeTypeDefaultDescription
distancedistancenumber | string0Distance from the trigger to the panel, in pixels. Defaults to `0`.
openopenbooleanfalseWhether the menu is open. Two-way: emits `change:open`.
positionpositionCPlacement'bottom-start'Preferred placement of the panel relative to the trigger.
triggertriggerHTMLElement | stringDesignated trigger: an element elsewhere in the document that opens the menu — its document ID, or the element itself. The same trigger concept as the `trigger` slot, supplied by reference: the menu wires click-to-toggle, arrow-key opening, ARIA and focus return onto it and anchors the panel to it. When both routes are supplied, this prop wins over the slot.

Events

EventDetailDescription
change:openbooleanFired whenever the menu opens or closes, carrying the new open state. Named `change:open`, not `update:open`: Vue's runtime silently drops `onUpdate:*` listeners on custom elements (`isModelListener`), so a template `@update:open` would never be attached.
select{ value: unknown }Fired when a leaf menu item is selected, carrying the item's `value`; bubbles out of the menu so a single listener can handle the whole tree.

Slots

SlotDescription
defaultMenu title / activator element (simple variant)
triggerThe element that opens the menu (e.g. a c-button)

CSS parts

Style from outside with c-menu::part(name) — parts are the library's only styling customization API.

PartDescription
triggerThe inline wrapper around the slotted trigger, serving as the panel's anchor
panelThe floating popover positioned against the trigger
listThe menu list surface inside the panel

Types

Importable from the package root: import type { … } from '@cscfi/csc-ui'

CPlacement shared

Preferred placement of a floating panel relative to its anchor, shared by the anchor-positioned overlay components (`c-menu`, `c-tooltip`, `c-popover`). The side names the panel's position; `-start`/`-end` align the panel's edge with the anchor's on the cross axis.

export type CPlacement =
  | 'bottom-end'
  | 'bottom-start'
  | 'bottom'
  | 'left-end'
  | 'left-start'
  | 'left'
  | 'right-end'
  | 'right-start'
  | 'right'
  | 'top-end'
  | 'top-start'
  | 'top';

<c-menu-item>

A single command in a `c-menu`.

Properties

PropertyAttributeTypeDefaultDescription
activeactivebooleanMarks the item as the currently selected choice: renders the trailing indicator icon and stamps `role="menuitemradio"` + `aria-checked`. Leave unset for regular command items — only a true/false value marks the item as a selectable choice. The state is consumer-owned: the menu emits `select` as usual and never toggles this itself. Distinct from the keyboard highlight (the `data-active` attribute the controlling menu moves between rows).
activeIconactive-iconstring''SVG path for the trailing indicator shown while `active`; defaults to a check mark.
dangerdangerbooleanfalseMarks the action as destructive (renders in the error colour).
disableddisabledbooleanfalseDisables the item — it is skipped by keyboard nav and emits no select.
iconiconstring''SVG path for a leading icon rendered before the item's content.
valuevaluestringValue reported in the menu's `select` event when this item is chosen.

Methods

MethodSignatureDescription
closeSubmenu()
openSubmenu()

Slots

SlotDescription
defaultThe item's content: label text plus optional icons or shortcut hint
submenuc-menu-item components for the nested submenu

CSS parts

Style from outside with c-menu-item::part(name) — parts are the library's only styling customization API.

PartDescription
rootThe item row containing the content and the submenu chevron
contentThe flex wrapper around the default-slot content
iconThe leading icon rendered from the `icon` prop
checkThe trailing indicator icon shown while the item is active
submenu-panelThe floating popover holding the submenu
submenuThe submenu list surface inside the popover

<c-menu-label>

Section label for a group of `c-menu-item`s.

Slots

SlotDescription
defaultThe label text for the menu section

CSS parts

Style from outside with c-menu-label::part(name) — parts are the library's only styling customization API.

PartDescription
rootThe label element inside the menu list