Skip to content

QuesoDropdown

A flexible dropdown component with comprehensive accessibility support, keyboard navigation, and focus management. It supports both single and multiple selection modes with customizable options and automatic ARIA attributes.

Basic Usage

vue
<template>
    <queso-dropdown v-model="selectedOptions" :options="options">
        <template #selectorPlaceholder> Choose an option </template>
    </queso-dropdown>
</template>

<script setup>
import { ref } from "vue";
import { QuesoDropdown } from "@allomambo/queso";

const selectedOptions = ref([]);
const options = [
    { value: "option1", label: "Option 1" },
    { value: "option2", label: "Option 2" },
    { value: "option3", label: "Option 3" },
];
</script>

Props

options

  • Type: QuesoDropdownOption<TOptionData>[]
  • Required: true
  • Description: Array of options to display in the dropdown. Each option must have a value and label, with optional data. TOptionData defaults to Record<string, any> and lets you strongly type the shape of data.

multiple

  • Type: boolean
  • Default: false
  • Description: When true, allows multiple options to be selected simultaneously.

stayOpenOnSelection

  • Type: boolean
  • Default: false
  • Description: When true, the dropdown stays open after selecting an option (only applies when multiple is false).

isDisabled

  • Type: boolean
  • Default: false
  • Description: When true, disables the dropdown. The dropdown cannot be focused, opened by click, or opened with the space key. If the dropdown is open when disabled, it will automatically close.

Emits

  • Payload: void
  • Description: Emitted when the dropdown is opened.
  • Payload: void
  • Description: Emitted when the dropdown is closed.

Slots

selector

  • Props: { options: QuesoDropdownOption[], activeOptions: QuesoDropdownOption[], isDropdownOpen: boolean }
  • Description: Completely replaces the entire selector content.

selectorBeforeText

  • Props: { isDropdownOpen: boolean, options: QuesoDropdownOptions, activeOptions: QuesoDropdownOptions }
  • Description: Content to display before the selector text.

selectorPlaceholder

  • Props: { isDropdownOpen: boolean }
  • Description: Text displayed when no options are selected.

selectorActiveOptions

  • Props: { isDropdownOpen: boolean, activeOptions: QuesoDropdownOptions }
  • Description: Content to display when options are selected.

selectorAfterText

  • Props: { isDropdownOpen: boolean, options: QuesoDropdownOptions, activeOptions: QuesoDropdownOptions }
  • Description: Content to display after the selector text.

selectorIcon

  • Props: { isDropdownOpen: boolean }
  • Description: The icon displayed in the selector. Defaults to "↓".

popoverHeader

  • Props: { options: QuesoDropdownOptions, activeOptions: QuesoDropdownOptions }
  • Description: Content to display at the top of the dropdown popover.

popoverItem

  • Props: { index: number, value: string, label: string, data?: TOptionData, isSelected: boolean, openDropdown: () => void, closeDropdown: () => void }
  • Description: Content for each dropdown option.

popoverFooter

  • Props: { options: QuesoDropdownOptions, activeOptions: QuesoDropdownOptions }
  • Description: Content to display at the bottom of the dropdown popover.

afterDropdown

  • Description: Content to display after the dropdown component.

Examples

Basic Single Selection

vue
<template>
    <queso-dropdown :options="options" v-model="selectedOption">
        <template #selectorPlaceholder> Choose a fruit </template>
    </queso-dropdown>
</template>

<script setup>
import { ref } from "vue";

const selectedOption = ref([]);
const options = [
    { value: "apple", label: "Apple" },
    { value: "banana", label: "Banana" },
    { value: "orange", label: "Orange" },
];
</script>
html
<div class="queso-dropdown is-dropdown-close">
    <div
        class="queso-dropdown__selector"
        aria-expanded="false"
        aria-controls="queso-dropdown__abc123"
        tabindex="0"
    >
        <div class="queso-dropdown__selector__text">
            <div class="queso-dropdown__selector__text__placeholder">
                Choose a fruit
            </div>
        </div>
        <div class="queso-dropdown__selector__icon">↓</div>
    </div>
    <div class="queso-dropdown__popover" id="queso-dropdown__abc123">
        <div class="queso-dropdown__popover__scroll">
            <ul class="queso-dropdown__popover__options-list">
                <li
                    class="queso-dropdown__popover__options-list__item"
                    tabindex="-1"
                >
                    Apple
                </li>
                <li
                    class="queso-dropdown__popover__options-list__item"
                    tabindex="-1"
                >
                    Banana
                </li>
                <li
                    class="queso-dropdown__popover__options-list__item"
                    tabindex="-1"
                >
                    Orange
                </li>
            </ul>
        </div>
    </div>
</div>

Multiple Selection

vue
<template>
    <queso-dropdown :options="options" multiple v-model="selectedOptions">
        <template #selectorPlaceholder> Choose multiple fruits </template>
        <template #selectorActiveOptions="{ activeOptions }">
            {{ activeOptions.map((option) => option.label).join(", ") }}
        </template>
    </queso-dropdown>
</template>

<script setup>
import { ref } from "vue";

const selectedOptions = ref([]);
const options = [
    { value: "apple", label: "Apple" },
    { value: "banana", label: "Banana" },
    { value: "orange", label: "Orange" },
];
</script>

Custom Item Template and Selector

vue
<template>
    <queso-dropdown :options="options" v-model="selectedOption">
        <template #selector="{ activeOptions }">
            <div class="custom-selector">
                <span class="icon">🌍</span>
                <span class="text">
                    {{
                        activeOptions.length > 0
                            ? activeOptions[0].label
                            : "Select country"
                    }}
                </span>
                <span class="arrow">▼</span>
            </div>
        </template>
        <template #popoverItem="{ label, data }">
            <div class="country-option">
                <span class="flag">{{ data?.flag }}</span>
                <span class="name">{{ label }}</span>
                <span class="code">{{ data?.code }}</span>
            </div>
        </template>
    </queso-dropdown>
</template>

<script setup>
import { ref } from "vue";

const selectedOption = ref([]);
const options = [
    { value: "us", label: "United States", data: { flag: "🇺🇸", code: "US" } },
    { value: "ca", label: "Canada", data: { flag: "🇨🇦", code: "CA" } },
    { value: "mx", label: "Mexico", data: { flag: "🇲🇽", code: "MX" } },
];
</script>
html
<div class="queso-dropdown is-dropdown-close">
    <div
        class="queso-dropdown__selector"
        aria-expanded="false"
        aria-controls="queso-dropdown__abc123"
        tabindex="0"
    >
        <div class="custom-selector">
            <span class="icon">🌍</span>
            <span class="text">Select country</span>
            <span class="arrow">▼</span>
        </div>
    </div>
    <div class="queso-dropdown__popover" id="queso-dropdown__abc123">
        <div class="queso-dropdown__popover__scroll">
            <ul class="queso-dropdown__popover__options-list">
                <li
                    class="queso-dropdown__popover__options-list__item"
                    tabindex="-1"
                >
                    <div class="country-option">
                        <span class="flag">🇺🇸</span>
                        <span class="name">United States</span>
                        <span class="code">US</span>
                    </div>
                </li>
                <li
                    class="queso-dropdown__popover__options-list__item"
                    tabindex="-1"
                >
                    <div class="country-option">
                        <span class="flag">🇨🇦</span>
                        <span class="name">Canada</span>
                        <span class="code">CA</span>
                    </div>
                </li>
                <li
                    class="queso-dropdown__popover__options-list__item"
                    tabindex="-1"
                >
                    <div class="country-option">
                        <span class="flag">🇲🇽</span>
                        <span class="name">Mexico</span>
                        <span class="code">MX</span>
                    </div>
                </li>
            </ul>
        </div>
    </div>
</div>

With Events

vue
<template>
    <queso-dropdown
        :options="options"
        @dropdown:open="handleOpen"
        @dropdown:close="handleClose"
        v-model="selectedOption"
    >
        <template #selectorPlaceholder> Choose a fruit </template>
    </queso-dropdown>
</template>

<script setup>
import { ref } from "vue";

const selectedOption = ref([]);
const options = [
    { value: "apple", label: "Apple" },
    { value: "banana", label: "Banana" },
    { value: "orange", label: "Orange" },
];

const handleOpen = () => {
    console.log("Dropdown opened");
};

const handleClose = () => {
    console.log("Dropdown closed");
};
</script>

Disabled State

vue
<template>
    <queso-dropdown
        :options="options"
        :isDisabled="isDisabled"
        v-model="selectedOption"
    >
        <template #selectorPlaceholder> Choose a fruit </template>
    </queso-dropdown>
    <button @click="isDisabled = !isDisabled">
        {{ isDisabled ? "Enable" : "Disable" }} Dropdown
    </button>
</template>

<script setup>
import { ref } from "vue";

const selectedOption = ref([]);
const isDisabled = ref(false);
const options = [
    { value: "apple", label: "Apple" },
    { value: "banana", label: "Banana" },
    { value: "orange", label: "Orange" },
];
</script>

Exposed Methods

openDropdown()

Opens the dropdown and focuses the first option.

closeDropdown()

Closes the dropdown and resets focus.

isDropdownOpen

Reactive boolean indicating the current open state.

Behavior

  • Keyboard Navigation: Full keyboard support with arrow keys, Enter, Space, and Escape
  • Focus Management: Automatic focus trapping and management
  • Click Outside: Automatically closes when clicking outside
  • Accessibility: Complete ARIA attributes and screen reader support
  • Scroll Management: Automatic scroll to top when closing
  • Multiple Selection: Toggle selection in multiple mode, replace in single mode

Accessibility

The component automatically handles:

  • aria-expanded attribute for screen readers
  • aria-controls attribute linking to the popover
  • tabindex management for keyboard navigation
  • Focus trapping within the dropdown
  • Proper semantic structure with lists and list items
  • Keyboard shortcuts (Arrow keys, Enter, Space, Escape)

CSS

Classes

The component applies the following CSS classes:

  • .queso-dropdown - Base class
  • .is-dropdown-open - Applied when dropdown is open
  • .is-dropdown-close - Applied when dropdown is closed
  • .is-multiple - Applied when multiple selection is enabled
  • .is-disabled - Applied when dropdown is disabled
  • .queso-dropdown__selector - Selector container
  • .queso-dropdown__popover - Popover container
  • .queso-dropdown__popover__options-list__item - Individual option item
  • .is-option-active - Applied to selected options

Custom Properties

  • --queso-dropdown-selector-align - Selector alignment (default: center)
  • --queso-dropdown-selector-justify - Selector justification (default: flex-start)
  • --queso-dropdown-popover-pos-top - Popover top position (default: 100%)
  • --queso-dropdown-popover-pos-bottom - Popover bottom position (default: auto)
  • --queso-dropdown-popover-pos-left - Popover left position (default: 0)
  • --queso-dropdown-popover-pos-right - Popover right position (default: 0)
  • --queso-dropdown-popover-z - Popover z-index (default: 300)
  • --queso-dropdown-popover-opacity - Popover opacity (default: 0)
  • --queso-dropdown-popover-display - Popover display (default: flex)
  • --queso-dropdown-popover-direction - Popover direction (default: column)
  • --queso-dropdown-popover-align - Popover alignment (default: stretch)
  • --queso-dropdown-popover-justify - Popover justification (default: flex-start)
  • --queso-dropdown-popover-wrap - Popover wrap (default: nowrap)
  • --queso-dropdown-popover-max-height - Popover max height (default: 20rem)

Type Declaration

ts
export type QuesoDropdownOptionValue = string;
export type QuesoDropdownOptionValues = QuesoDropdownOptionValue[];

export interface QuesoDropdownOption<TOptionData = Record<string, any>> {
    value: QuesoDropdownOptionValue;
    label: string;
    data?: TOptionData;
}
export type QuesoDropdownOptions<TOptionData = Record<string, any>> =
    QuesoDropdownOption<TOptionData>[];

export type QuesoDropdownModel = QuesoDropdownOptionValues;

export interface QuesoDropdownProps<TOptionData = Record<string, any>> {
    options: QuesoDropdownOptions<TOptionData>;
    multiple?: boolean;
    stayOpenOnSelection?: boolean;
    isDisabled?: boolean;
}