25 October, 2023

Nuxt UI multi-select dropdown filter

The front page of the Nuxt UI docs has a Labels multi-select menu with status colours. Inspired by that element, I wanted to create a filter for a data table that lets you select multiple labels/statuses/whatever, and shows the currently selected items as actionable badges. Here's how.

This is what we're going to build.

Approved
Processing

First up, this is the dummy data we're working with in our <script setup>.

const statuses = [{
    label: 'Pending',
    color: 'amber'
},{
    label: 'Approved',
    color: 'lime',
},{
    label: 'Cancelled',
    color: 'rose'
},{
    label: 'Processing',
    color: 'purple'
},{
    label: 'Shipped',
    color: 'emerald'
}]
    
//default selection
const selectedStatuses = ref([
    {label: 'Approved', color: 'lime'}, 
    {label: 'Processing', color: 'purple'}
])

Now, let's start off building the customised multi-select menu. We're looking to add in some coloured dots/markers in the main menu to indicate which statuses are currently selected, as well as the relevant dot next to each status in the drop-down menu.

<UFormGroup label="Filter by status" name="filter:status" class="min-w-[12rem]">
    <USelectMenu v-model="selectedStatuses" :options="statuses" by="label" multiple>
        
        <template #label>
            <span v-if="!selectedStatuses.length">Showing all</span>
            <span v-else class="pl-1 flex items-center">
                <span 
                    v-for="status in selectedStatuses" 
                    :key="status.label"
                    :class="[`bg-${status.color}-500`, 'inline-block h-3 w-3 flex-shrink-0 rounded-full -ml-1 border-2 border-white dark:border-gray-900']" 
                    aria-hidden="true" />
                <span class="ml-1">{{ selectedStatuses.length }} selected</span>
            </span>
        </template>
        
        <template #option="{option:status}">
            <span :class="[`bg-${status.color}-500`, 'inline-block h-2 w-2 flex-shrink-0 rounded-full']" aria-hidden="true" />
            <span>{{status.label}}</span>
        </template>
        
    </USelectMenu>
</UFormGroup>

We set the multiple property on a <USelectMenu/> component, then use the #label slot to add the coloured dots to the main menu. The dots have a negative left margin and white border applied so they overlap neatly.

We also use the #option slot to add the relevant coloured dot next to each status in the drop-down menu.


Next up, we can add in the badges that show which filters are currently being applied. These will appear below the select menu and allow the user to quickly remove a specific status from the selection.

Approved
Processing
<div class="space-x-2 space-y-2 mt-4">
    <UButtonGroup 
        size="xs"
        v-for="status in selectedStatuses"
        :key="status.label"
        :ui="{ rounded: 'rounded-full' }">
      <UBadge 
          color="white" 
          class="capitalize ps-3 hover:cursor-default hover:bg-white dark:hover:bg-white-950">
              <span :class="[`bg-${status.color}-500`, 'inline-block h-2 w-2 rounded-full']" />
              <span class="mx-2">{{ status.label }}</span>
      </UBadge>
      <UButton 
        @click="selectedStatuses.splice(selectedStatuses.indexOf(status), 1)" 
          icon="i-heroicons-x-mark" 
          color="white"
          class="pe-2" 
          :aria-label="`Remove ${status.label} filter`"
          :title="`Remove ${status.label} filter`" />
    </UButtonGroup>
</div>

We loop through the selected statuses with a <ButtonGroup/> component, which wraps the label badge with an actionable remove button at the end.

For simplicity I've put the logic to remove the status inline, but it's probably better to put that in a function inside your <script setup>.


And there you have it – a good-looking, customisable way of adding multi-select status filters to a data table, using Nuxt UI.

You can find the full example to play around with in this Stackblitz .