Appearance
Dropdown Component Demo
This page demonstrates the Dropdown component with various configurations and examples.
Basic Dropdown
A simple dropdown with single selection.
Code Example
vue
<template>
<ODropdown
v-model="selectedValue"
:items="items"
button-text="Choose an option"
@item:select="handleItemSelect"
/>
</template>
<script setup>
import { ref } from 'vue';
const selectedValue = ref(null);
const items = ref([
{ text: 'Option 1', value: 'option1' },
{ text: 'Option 2', value: 'option2' },
{ text: 'Option 3', value: 'option3' },
{ text: 'Option 4', value: 'option4' },
]);
const handleItemSelect = (item) => {
console.log('Selected:', item);
};
</script>Demo
Selected: None
Multiple Selection
Dropdown with multiple selection and chip display.
Code Example
vue
<template>
<ODropdown
v-model="selectedValues"
:items="items"
:multiple="true"
button-text="Select multiple options"
@item:select="handleItemSelect"
@item:unselect="handleItemUnselect"
/>
</template>
<script setup>
import { ref } from 'vue';
const selectedValues = ref([]);
const items = ref([
{ text: 'Apple', value: 'apple' },
{ text: 'Banana', value: 'banana' },
{ text: 'Orange', value: 'orange' },
{ text: 'Grape', value: 'grape' },
{ text: 'Mango', value: 'mango' },
]);
const handleItemSelect = (item) => {
console.log('Selected:', item);
};
const handleItemUnselect = (item) => {
console.log('Unselected:', item);
};
</script>Demo
Selected: None
Selected Items on Top
Keep chosen options at the top of the list to make bulk edits faster.
Code Example
vue
<template>
<ODropdown
v-model="selectedLanguages"
:items="languages"
:multiple="true"
selected-on-top
button-text="Languages"
/>
</template>
<script setup>
import { ref } from 'vue';
const selectedLanguages = ref(['js']);
const languages = ref([
{ text: 'JavaScript', value: 'js' },
{ text: 'TypeScript', value: 'ts' },
{ text: 'Python', value: 'py' },
{ text: 'Java', value: 'java' },
{ text: 'C++', value: 'cpp' },
]);
</script>Demo
Selected: None
Custom Trigger
Dropdown with a custom trigger element.
Code Example
vue
<template>
<ODropdown
v-model="selectedValue"
:items="items"
placement="bottom-end"
>
<template #trigger="{ isOpen, toggleMenu }">
<button
class="custom-trigger"
:class="{ 'is-open': isOpen }"
@click="toggleMenu"
>
<span>Custom Trigger</span>
<svg
class="arrow"
:class="{ 'rotated': isOpen }"
viewBox="0 0 20 20"
>
<path d="M5 7l5 5 5-5" />
</svg>
</button>
</template>
</ODropdown>
</template>
<script setup>
import { ref } from 'vue';
const selectedValue = ref(null);
const items = ref([
{ text: 'Item 1', value: 'item1' },
{ text: 'Item 2', value: 'item2' },
{ text: 'Item 3', value: 'item3' },
]);
</script>
<style scoped>
.custom-trigger {
padding: 0.5rem 1rem;
border: 2px solid #3b82f6;
border-radius: 0.5rem;
background: white;
display: flex;
align-items: center;
gap: 0.5rem;
cursor: pointer;
transition: all 0.2s;
}
.custom-trigger.is-open {
background: #3b82f6;
color: white;
}
.arrow {
width: 1rem;
height: 1rem;
transition: transform 0.2s;
}
.arrow.rotated {
transform: rotate(180deg);
}
</style>Demo
Selected: None
Trigger Content Slot
Use the triggerContent slot to change what shows inside the default trigger button while keeping its layout and icon.
Code Example
vue
<template>
<ODropdown
v-model="selectedStatuses"
:items="statuses"
:multiple="true"
>
<template #triggerContent="{ props }">
<div class="status-trigger">
<span class="status-trigger__label">Statuses</span>
<span class="status-trigger__value">
{{ props.selectedItems.length ? `${props.selectedItems.length} selected` : 'Pick statuses' }}
</span>
</div>
</template>
</ODropdown>
</template>
<script setup>
import { ref } from 'vue';
const selectedStatuses = ref([]);
const statuses = ref([
{ text: 'Pending Review', value: 'pending' },
{ text: 'In Progress', value: 'in-progress' },
{ text: 'Completed', value: 'done' },
]);
</script>Demo
Selected: None
Different Placements
Dropdown with various placement options.
Code Example
vue
<template>
<div class="placement-examples">
<ODropdown
v-model="selectedValue"
:items="items"
placement="bottom-start"
button-text="Bottom Start"
/>
<ODropdown
v-model="selectedValue"
:items="items"
placement="bottom-end"
button-text="Bottom End"
/>
<ODropdown
v-model="selectedValue"
:items="items"
placement="top-start"
button-text="Top Start"
/>
<ODropdown
v-model="selectedValue"
:items="items"
placement="top-end"
button-text="Top End"
/>
</div>
</template>
<script setup>
import { ref } from 'vue';
const selectedValue = ref(null);
const items = ref([
{ text: 'Option 1', value: 'option1' },
{ text: 'Option 2', value: 'option2' },
{ text: 'Option 3', value: 'option3' },
]);
</script>
<style scoped>
.placement-examples {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 1rem;
padding: 2rem;
}
</style>Demo
Disabled State
Dropdown in disabled state.
Code Example
vue
<template>
<ODropdown
v-model="selectedValue"
:items="items"
:disabled="true"
button-text="Disabled Dropdown"
/>
</template>
<script setup>
import { ref } from 'vue';
const selectedValue = ref(null);
const items = ref([
{ text: 'Enabled Option', value: 'enabled' },
{ text: 'Another Option', value: 'another' },
]);
</script>Demo
Selected: None
No Data State
Display a friendly empty state using the no-data slot when items is empty.
Code Example
vue
<template>
<ODropdown v-model="value" :items="[]" button-text="Empty dropdown">
<template #no-data>
<div class="empty-state">
<strong>No results</strong>
<p>Add items to show them here.</p>
</div>
</template>
</ODropdown>
</template>
<script setup>
import { ref } from 'vue';
const value = ref(null);
</script>Demo
Custom Widths
Dropdown with custom width setting.
Code Example
vue
<template>
<div class="width-examples">
<ODropdown
v-model="buttonWidthSelection"
:items="items"
button-text="Button width 220px"
button-width="220px"
/>
<ODropdown
v-model="menuWidthSelection"
:items="items"
:width="320"
button-text="Menu width 320px"
/>
</div>
</template>
<script setup>
import { ref } from 'vue';
const buttonWidthSelection = ref(null);
const menuWidthSelection = ref(null);
const items = ref([
{ text: 'Short', value: 'short' },
{ text: 'Medium length option', value: 'medium' },
{ text: 'Very long option text that might overflow', value: 'long' },
]);
</script>Demo
Button width selection: None | Menu width selection: None
Form Integration
Dropdown integrated into a form with validation.
Code Example
vue
<template>
<form @submit.prevent="submitForm" class="form-example">
<div class="form-group">
<label>Category</label>
<ODropdown
v-model="formData.category"
:items="categories"
button-text="Select category"
/>
<span v-if="formErrors.category" class="error">{{ formErrors.category }}</span>
</div>
<div class="form-group">
<label>Tags</label>
<ODropdown
v-model="formData.tags"
:items="tags"
:multiple="true"
button-text="Select tags"
/>
<span v-if="formErrors.tags" class="error">{{ formErrors.tags }}</span>
</div>
<div class="form-group">
<label>Priority</label>
<ODropdown
v-model="formData.priority"
:items="priorities"
button-text="Select priority"
/>
<span v-if="formErrors.priority" class="error">{{ formErrors.priority }}</span>
</div>
<div class="form-actions">
<button type="submit" class="submit-btn">Submit Form</button>
<button type="button" @click="validateForm" class="validate-btn">Validate Only</button>
</div>
</form>
</template>
<script setup>
import { reactive } from 'vue';
const formData = reactive({
category: null,
tags: [],
priority: null,
});
const formErrors = reactive({
category: '',
tags: '',
priority: '',
});
const categories = ref([
{ text: 'Technology', value: 'tech' },
{ text: 'Design', value: 'design' },
{ text: 'Marketing', value: 'marketing' },
{ text: 'Development', value: 'dev' },
]);
const tags = ref([
{ text: 'Vue.js', value: 'vue' },
{ text: 'React', value: 'react' },
{ text: 'Angular', value: 'angular' },
{ text: 'TypeScript', value: 'ts' },
{ text: 'JavaScript', value: 'js' },
]);
const priorities = ref([
{ text: 'Low', value: 'low' },
{ text: 'Medium', value: 'medium' },
{ text: 'High', value: 'high' },
{ text: 'Critical', value: 'critical' },
]);
const validateForm = () => {
formErrors.category = !formData.category ? 'Category is required' : '';
formErrors.tags = formData.tags.length === 0 ? 'At least one tag is required' : '';
formErrors.priority = !formData.priority ? 'Priority is required' : '';
};
const submitForm = () => {
validateForm();
if (!formErrors.category && !formErrors.tags && !formErrors.priority) {
console.log('Form submitted:', formData);
alert('Form submitted successfully!');
}
};
</script>
<style scoped>
.form-example {
max-width: 400px;
padding: 1rem;
border: 1px solid #e5e7eb;
border-radius: 0.5rem;
}
.form-group {
margin-bottom: 1rem;
}
.form-group label {
display: block;
margin-bottom: 0.5rem;
font-weight: 500;
}
.error {
color: #ef4444;
font-size: 0.875rem;
margin-top: 0.25rem;
display: block;
}
.form-actions {
display: flex;
gap: 0.5rem;
margin-top: 1rem;
}
.submit-btn, .validate-btn {
padding: 0.5rem 1rem;
border: none;
border-radius: 0.25rem;
cursor: pointer;
}
.submit-btn {
background: #3b82f6;
color: white;
}
.validate-btn {
background: #6b7280;
color: white;
}
</style>