Skip to content

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>

Demo