Skip to content
🌏 Translated with the assistance of DeepSeek and ChatGPT

DropDown

A compact menu displayed via a dropdown.

Basic Usage

Set menu content using the options prop. When an option is a string or an object, its index field will be used as the option's unique identifier — make sure they are not duplicated.

Use the select event to get the triggered option.

options determines whether to render the options as <a> tags or Vue Router RouterLink components based on the href or route field of the child elements.

WARNING

The href and route will be directly rendered into the <a> tag. If values such as javascript:alert(1) or malicious URLs are passed, this may lead to XSS or open redirect vulnerabilities.

<template>
	<px-space>
		<px-drop-down :options="options1" @select="selectHandler">
			<px-button>String Option</px-button>
		</px-drop-down>
		<px-drop-down :options="options2" @select="selectHandler">
			<px-button>Object Option</px-button>
		</px-drop-down>
	</px-space>
</template>
<script setup lang="ts">
import type { DropDownListOption } from '@pixelium/web-vue'

// On-demand import
// import type { DropDownListOption } from '@pixelium/web-vue/es';

import { ref } from 'vue'

const options1 = ref(['Action 1', 'Action 2', 'Action 3'])
const options2 = ref([
	{ label: 'Home', index: 'Home', href: '/pixelium-design/' },
	{
		label: 'Github',
		index: 'Github',
		target: '_blank',
		href: 'https://github.com/shika-works/pixelium-design'
	},
	{ label: 'Action 1', index: 'Action 1' },
	{ label: 'Action 2', index: 'Action 2', disabled: true },
	{ label: 'Action 3', index: 'Action 3' }
])

const selectHandler = (index: string, option: string | DropDownListOption) => {
	$message.info(
		`Selected index: ${index}, option: ${
			typeof option === 'string' ? option : JSON.stringify(option)
		}`
	)
}
</script>

Placement

The DropDown popup supports multiple placements.

The placement prop determines where the popup appears. The value format is [direction]-[alignment], such as 'top', 'right', 'bottom', 'left', 'top-start', 'top-end', 'right-start', 'right-end', 'bottom-start', 'bottom-end', 'left-start', 'left-end'. There are four directions and three alignment variations. Default placement is top.

<template>
	<div class="box">
		<div class="left">
			<px-drop-down
				v-for="item in ['left-start', 'left', 'left-end']"
				:key="item"
				:placement="item"
				:options="getOptions(item)"
			>
				<px-button>{{ getLabel(item) }}</px-button>
			</px-drop-down>
		</div>
		<div class="center">
			<div class="top">
				<px-drop-down
					v-for="item in ['top-start', 'top', 'top-end']"
					:key="item"
					:placement="item"
					:options="getOptions(item)"
				>
					<px-button>{{ getLabel(item) }}</px-button>
				</px-drop-down>
			</div>
			<div class="bottom">
				<px-drop-down
					v-for="item in ['bottom-start', 'bottom', 'bottom-end']"
					:key="item"
					:placement="item"
					:options="getOptions(item)"
				>
					<px-button>{{ getLabel(item) }}</px-button>
				</px-drop-down>
			</div>
		</div>
		<div class="right">
			<px-drop-down
				v-for="item in ['right-start', 'right', 'right-end']"
				:key="item"
				:placement="item"
				:options="getOptions(item)"
			>
				<px-button>{{ getLabel(item) }}</px-button>
			</px-drop-down>
		</div>
	</div>
</template>

<script setup lang="ts">
const getLabel = (placement: string) => {
	return placement.replace(/-/g, ' ').replace(/\b\w/g, (l) => l.toUpperCase())
}

const getOptions = (placement: string) => {
	let contentArr: string[] = []
	if (placement.includes('-')) {
		const [pos, sub] = placement.split('-')
		contentArr = [capitalize(pos), capitalize(sub), 'Popover']
	} else {
		contentArr = [capitalize(placement), 'Popover']
	}

	return contentArr
}

const capitalize = (str: string) => str.charAt(0).toUpperCase() + str.slice(1)
</script>

<style lang="css" scoped>
.box {
	display: flex;
	width: 600px;
	height: 350px;
	margin-left: 100px;
	margin-top: 64px;
	margin-bottom: 64px;
}
.left,
.right {
	flex-shrink: 0;
	display: flex;
	flex-direction: column;
	justify-content: space-around;
}
.left {
	align-items: flex-start;
}
.right {
	align-items: flex-end;
}
.center {
	flex: 1;
	display: flex;
	flex-direction: column;
}
.top,
.bottom {
	display: flex;
	justify-content: space-around;
}
.top {
	flex: 1;
}
</style>

Grouped Menus

Options can be grouped.

<template>
	<px-space>
		<px-drop-down :options="options1">
			<px-button>DropDown Group</px-button>
		</px-drop-down>
		<px-drop-down :options="options2">
			<px-button>DropDown Divider</px-button>
		</px-drop-down>
	</px-space>
</template>
<script setup lang="ts">
import { ref } from 'vue'

const options1 = ref([
	'Action 1',
	'Action 2',
	'Action 3',
	{
		label: 'Action More',
		index: 'Action More',
		children: ['Action More 1', 'Action More 2', 'Action More 3'],
		type: 'group'
	}
])
const options2 = ref([
	'Action 1',
	'Action 2',
	'Action 3',
	{
		label: 'Action More 1',
		index: 'Action More 1',
		divider: true
	},
	'Action More 2',
	'Action More 3'
])
</script>

Split Button

Combine ButtonGroup and Button components so an icon button can trigger the popup.

<template>
	<px-space>
		<px-drop-down :options="options">
			<px-button
				>DropDown <IconChevronDown style="margin-left: 8px"></IconChevronDown
			></px-button>
		</px-drop-down>
		<px-button-group>
			<px-button>DropDown</px-button>

			<px-drop-down :options="options">
				<px-button shape="square"><IconChevronDown /></px-button>
			</px-drop-down>
		</px-button-group>
	</px-space>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import { IconChevronDown } from '@pixelium/web-vue/icon-hn/es'

const options = ref(['Action 1', 'Action 2', 'Action 3'])
</script>

Controlled Mode

Passing visible switches the component to controlled mode. If omitted or set to undefined, the component is uncontrolled and defaultVisible can be used to set the initial state. The component emits update:visible to support v-model.

<template>
	<px-drop-down :options="options" v-model:visible="visible">
		<px-button variant="text">Controlled {{ visible }}</px-button>
	</px-drop-down>
</template>

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

const visible = ref(false)
const options = ref(['Action 1', 'Action 2', 'Action 3'])
</script>

Disabled State

Set disabled to disable the component. Trigger actions will be ignored and the popup will not open.

<template>
	<px-drop-down :options="options" disabled>
		<px-button theme="info">Disabled</px-button>
	</px-drop-down>
</template>

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

const options = ref(['Action 1', 'Action 2', 'Action 3'])
</script>

API

AttributeTypeOptionalDefaultDescriptionVersion
optionsDropDownOption | DropDownGroupOptionTrueDropdown menu contents.0.1.0
visibleboolean | nullTrueWhether it is shown (controlled mode, supports v-model).0.1.0
defaultVisibleboolean | nullTrueDefault visibility in uncontrolled mode.0.1.0
placement'top' | 'right' | 'bottom' | 'left' | 'top-start' | 'top-end' | 'right-start' | 'right-end' | 'bottom-start' | 'bottom-end' | 'left-start' | 'left-end'True'top'Popup placement.0.1.0
trigger'hover' | 'click'True'hover'Trigger action.0.1.0
disabledbooleanTruebooleanWhether the component is disabled.0.1.0
offsetnumberTrue8Popup offset in pixels.0.1.0
variant'dark' | 'light'True'light'Component style variant.0.1.0
arrowbooleanTruetrueWhether to show an arrow.0.1.0
rootHTMLElement | stringTrue'body'Mount element.0.1.0
zIndexnumberTruez-index for the popup.0.1.0
destroyOnHidebooleanTruefalseWhether to destroy content when hidden.0.1.0
popoverPropsOmit<PopoverProps, 'visible' | 'content' | 'defaultVisible'> & EmitEvent<PopoverEvents>TrueProps forwarded to the internal Popover.0.1.0
dividerPropsRestAttrsTrueDOM attributes forwarded to the divider element.0.1.0
EventParameterDescriptionVersion
update:visiblevalue: booleanCallback for updating visible.0.1.0
closeevent: MouseEventFired when the popup closes.0.1.0
openevent: MouseEventFired when the popup opens.0.1.0
selectindex: string | number | symbol, option: DropDownOption | string, event: MouseEventFired when a menu option is selected.0.1.0
SlotParameterDescriptionVersion
defaultTrigger element slot.0.1.0
optionCustom rendering for an option.0.1.0
group-labelCustom rendering for the group header.0.1.0
AttributeTypeOptionalDefaultDescriptionVersion
open() => voidFalseOpens the popup.0.1.0
close() => voidFalseCloses the popup.0.1.0

RestAttrs

ts
import type { StyleValue } from 'vue'

export type VueClassValue = string | Record<string, any> | VueClassValue[]
export type VueStyleValue = StyleValue

export type RestAttrs = {
	style?: VueStyleValue | null
	class?: VueClassValue | null
	[x: string]: any
}

EmitEvent

ts
export type EmitEvent<T extends Record<string, any>> = {
	[K in keyof T as `on${Capitalize<K & string>}`]?: (...args: T[K]) => void
}
ts
export interface DropDownOption extends NavigationOption {
	divider?: boolean
	disabled?: boolean
	href?: string
	route?: string | object
	target?: string
}

export interface DropDownGroupOption extends NavigationOption {
	children: (DropDownOption | string)[]
	type: 'group'
}

export interface NavigationOption {
	index: string | number | symbol
	label?: string
}