The DropdownButton component provides a reusable dropdown action button with support for flat items and one-level nested sub-menus. It dynamically builds UI design props for each menu entry, integrates with scoped translations, and emits a normalized event payload when an action is selected.
- Offers a consistent dropdown action menu across LinPulsar screens
- Supports both direct actions and grouped actions with one nested submenu level
- Integrates with the
lin-pulsar-coredesign system for consistent styling - Uses scoped i18n keys for the button label, parent labels, and child labels
- Emits a normalized action key so parent components can handle all actions in one place
- Provides
data-cyattributes for straightforward E2E targeting
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
uiNamespace |
string |
Yes | - | UI design namespace for custom styling |
i18nScope |
string |
Yes | - | i18n scope for translations |
items |
MenuItem[] |
Yes | - | List of menu items displayed in the dropdown |
MenuItem is provided by @linagora/lin-pulsar-core so any plugin or host application can build typed menus without depending on LinPulsar internals. It is generic over the key type, defaulting to string for open-ended menus.
import type { MenuItem } from '@linagora/lin-pulsar-core';
/**
* Generic shape of a dropdown button menu item.
*/
export interface MenuItem<TKey extends string = string> {
/**
* The unique identifier of the menu item, used as an i18n key and as the base of the emitted action key.
*/
key: TKey;
/**
* Whether the menu item is clickable or not.
*/
clickable: boolean;
/**
* An optional array of child menu item keys, representing a nested menu structure.
*/
children?: TKey[];
}/**
* Defines the shape of the props for a dropdown button component.
*/
export interface DropdownButtonProps extends CommonComponentProps {
/**
* An array of menu items to be displayed in the dropdown.
*/
items: MenuItem[];
}| Event | Payload | Description |
|---|---|---|
itemClick |
{ key: string } |
Emitted when a leaf action is selected |
Important:
- A root item without children emits its own
keyas payload:{ key: 'edit' } - A child item emits a dotted key composed from its parent and child keys:
{ key: 'export.csv' } - Parent items that own children do not emit an event themselves; only their children do
The click payload is DropdownClickPayload from @linagora/lin-pulsar-core, which is generic over the emitted key type and defaults to string.
import type { DropdownClickPayload } from '@linagora/lin-pulsar-core';
/**
* Outputs (events) emitted by the DropdownButton component.
*/
export interface DropdownButtonOutputs {
/**
* Emitted when a menu item is clicked.
*/
itemClick: [DropdownClickPayload];
}The component uses the lin-pulsar-core design system through useUiDesign(). You can customize:
- Dropdown button:
{uiNamespace}.dropdown-button→ applies toq-btn-dropdown - Root list:
{uiNamespace}.dropdown-button→ applies toq-list - Sub-menu:
{uiNamespace}.dropdown-button→ applies toq-menu - Sub-menu trigger icon section:
{uiNamespace}.dropdown-button.menu-trigger→ applies toq-item-section - Sub-menu trigger icon:
{uiNamespace}.dropdown-button.menu-trigger→ applies toq-icon - Root item container:
{uiNamespace}.dropdown-button.items.{itemKey}→ applies toq-item - Root item icon section:
{uiNamespace}.dropdown-button.items.{itemKey}.icon→ applies toq-item-section - Root item icon:
{uiNamespace}.dropdown-button.items.{itemKey}.icon→ applies toq-icon - Root item label section:
{uiNamespace}.dropdown-button.items.{itemKey}.label→ applies toq-item-section - Root item label:
{uiNamespace}.dropdown-button.items.{itemKey}.label→ applies toq-item-label - Child item container:
{uiNamespace}.dropdown-button.items.{itemKey}.children.{childKey}→ applies toq-item - Child item icon section:
{uiNamespace}.dropdown-button.items.{itemKey}.children.{childKey}.icon→ applies toq-item-section - Child item icon:
{uiNamespace}.dropdown-button.items.{itemKey}.children.{childKey}.icon→ applies toq-icon - Child item label section:
{uiNamespace}.dropdown-button.items.{itemKey}.children.{childKey}.label→ applies toq-item-section - Child item label:
{uiNamespace}.dropdown-button.items.{itemKey}.children.{childKey}.label→ applies toq-item-label
Here is a sample JSON configuration for the design system:
{
"catalogPage": {
"dropdown-button": {
"q-btn-dropdown": {
"color": "primary",
"outline": true,
"icon": "more_vert"
},
"q-list": {
"dense": true
},
"q-menu": {
"autoClose": false
},
"menu-trigger": {
"q-item-section": {
"side": true
},
"q-icon": {
"name": "keyboard_arrow_right",
"size": "16px"
}
},
"items": {
"edit": {
"q-item": {
"clickable": true
},
"label": {
"q-item-label": {
"lines": 1
}
}
},
"export": {
"children": {
"csv": {
"q-item": {
"clickable": true
}
},
"pdf": {
"q-item": {
"clickable": true
}
}
}
}
}
}
}
}The component requires an i18n scope (i18nScope) and expects the following keys:
{i18nScope}.DropdownButton.title- Label displayed on the dropdown button{i18nScope}.DropdownButton.{ITEM_KEY}- Label for a root item without children{i18nScope}.DropdownButton.{ITEM_KEY}.title- Label for a root item that opens a submenu{i18nScope}.DropdownButton.{ITEM_KEY}.{CHILD_KEY}- Label for a child item inside a submenu
Example:
{
"moduleUsers.UsersPage": {
"DropdownButton": {
"title": "Actions",
"edit": "Edit user",
"archive": "Archive user",
"export": {
"title": "Export",
"csv": "Export as CSV",
"pdf": "Export as PDF"
}
}
}
}- Consistency: Uniform action dropdown pattern across modules
- Type-safe: Full TypeScript support for props, events, and UI mapping
- Structured: Supports simple actions and grouped actions with one submenu level
- Customizable: Fine-grained UI namespacing for root items and child items
- i18n ready: Fully internationalized with predictable translation keys
- Simple integration: One event channel for all selected actions
- Test-friendly: Includes
data-cyattributes for E2E testing - Reusable: Parent components only provide item definitions and handle emitted action keys
<script setup lang="ts">
import { loadAsyncComponent } from '@linagora/lin-pulsar-core';
import type { MenuItem } from '@linagora/lin-pulsar-core';
const dropdownButton = loadAsyncComponent('LinPulsar/DropdownButton');
const items: MenuItem[] = [
{
key: 'edit',
clickable: true,
},
{
key: 'export',
clickable: true,
children: ['csv', 'pdf'],
},
{
key: 'archive',
clickable: false,
},
];
function onItemClick({ key }: { key: string }) {
if (key === 'edit') {
openEditionDialog();
}
if (key === 'export.csv') {
exportAsCsv();
}
if (key === 'export.pdf') {
exportAsPdf();
}
}
</script>
<template>
<component
:is="dropdownButton"
v-if="dropdownButton"
ui-namespace="catalogPage"
i18n-scope="moduleUsers.UsersPage"
:items="items"
@item-click="onItemClick"
/>
</template><script setup lang="ts">
import { loadAsyncComponent } from '@linagora/lin-pulsar-core';
import type { MenuItem } from '@linagora/lin-pulsar-core';
const dropdownButton = loadAsyncComponent('LinPulsar/DropdownButton');
const items: MenuItem[] = [
{ key: 'open', clickable: true },
{ key: 'duplicate', clickable: true },
{ key: 'export', clickable: true, children: ['csv', 'json'] },
];
const actionHandlers: Record<string, () => void> = {
open: () => openEntity(),
duplicate: () => duplicateEntity(),
'export.csv': () => exportEntity('csv'),
'export.json': () => exportEntity('json'),
};
function onItemClick({ key }: { key: string }) {
actionHandlers[key]?.();
}
</script>
<template>
<component
:is="dropdownButton"
v-if="dropdownButton"
ui-namespace="catalogPage"
i18n-scope="catalogPage"
:items="items"
@item-click="onItemClick"
/>
</template>- One submenu level only: The component supports root items and one child level. Child items cannot themselves define nested children
- Leaf items only emit: Only direct leaf actions emit
itemClick - Parent translation rule: Items with children use the
.titletranslation key for the visible root label - Clickable parent requirement: If an item has children, its submenu is rendered only when
clickableistrue - Disabled leaf behavior: A root item without children can be rendered as non-clickable by setting
clickable: false - Reactive item mapping: Both
uiPropsanditemsWithUIare computed properties, so any runtime change to theitemsprop (e.g. changing available actions based on entity state) is automatically reflected in the rendered menu - UI namespacing: Every root item and child item gets its own namespace for granular customization
- Template coverage: The template contains only presentation and event wiring logic
The component includes data-cy attributes for Cypress testing:
- Main dropdown button:
data-cy="dropdown-button" - Root item:
data-cy="dropdown-button_item_{itemName}" - Child item:
data-cy="dropdown-button_item_{parentName}_{childName}"
The component follows a declarative action-menu architecture:
- Props in: Receives a UI namespace, an i18n scope, and the menu item definitions
- Local namespace resolution: Builds a local component namespace from
uiNamespace - UI props generation: Generates design-system props for the dropdown button, root items, submenu trigger, and child items
- Item enrichment: Creates
itemsWithUIby pairing each item with its generated UI configuration - Rendering: Displays either a direct action item or a parent item with a nested submenu depending on
children - User interaction: Captures clicks on leaf items
- Events out: Emits a normalized
itemClickpayload containing the selected action key
This architecture ensures:
- Separation of concerns: The component handles rendering and event normalization, while the parent handles business actions
- Reusability: The same component can drive many action menus with different item definitions
- Scalability: UI customization remains granular even when menus grow in size
- Testability: The action mapping and emitted payloads are easy to validate
- Predictability: Translation and emitted key formats follow stable naming rules
The component applies the following CSS classes for styling customization:
.dropdown-button- Main dropdown button container.dropdown-button--list- Root or submenu list container.dropdown-button--item- Root or child menu item.dropdown-button--item--icon-section- Icon section inside a menu item.dropdown-button--item--label-section- Label section inside a menu item.dropdown-button--item--label- Label text inside a menu item.dropdown-button--sub-menu- Nested submenu container.dropdown-button--sub-menu--trigger-section- Section containing the submenu trigger icon
These classes can be targeted in your global or scoped styles for additional customization beyond the design system.
The component supports the following item configurations:
| Item Shape | Behavior | Emitted Key |
|---|---|---|
{ key: 'edit', clickable: true } |
Renders a direct action item | edit |
{ key: 'archive', clickable: false } |
Renders a disabled root item | None |
{ key: 'export', clickable: true, children: ['csv', 'pdf'] } |
Renders a parent item with submenu | export.csv, export.pdf |
{ key: 'export', clickable: false, children: ['csv'] } |
Renders the parent row without an active submenu | None |
This makes the component suitable for compact action menus where some commands are immediate and others must be grouped under a common parent action.