Phi
Select
@dicehub/phiv1.0.0-beta.1

Select

Displays a list of options for the user to pick from, triggered by a button.

<script setup>
import { ref } from "vue";
import { Select } from "@dicehub/phi/components/select";

const value = ref("apple");
</script>

<template>
  <Select
    v-model="value"
    label="Favorite Fruit"
    :items="{ apple: 'Apple', banana: 'Banana', cherry: 'Cherry' }"
  />
</template>

Installation

Barrel

import { Select } from "@dicehub/phi";

Granular

import { Select } from "@dicehub/phi/components/select";

Usage

<script setup>
import { ref } from "vue";
import { Select } from "@dicehub/phi/components/select";

const value = ref("apple");
</script>

<template>
  <Select
    v-model="value"
    label="Favorite Fruit"
    :items="{ apple: 'Apple', banana: 'Banana', cherry: 'Cherry' }"
  />
</template>

Examples

Basic

A select with a visible label. Providing label renders the label above the trigger.

<script setup>
import { ref } from "vue";
import { Select } from "@dicehub/phi/components/select";

const value = ref("apple");
</script>

<template>
  <Select
    v-model="value"
    label="Favorite Fruit"
    :items="{ apple: 'Apple', banana: 'Banana', cherry: 'Cherry' }"
  />
</template>

Sizes

Use the size prop to match Input sizing (xs, sm, base, lg).

<script setup>
import { Select } from "@dicehub/phi/components/select";
</script>

<template>
  <Select size="xs" aria-label="Select size xs" placeholder="Choose..." :items="{ a: 'Option A', b: 'Option B' }" />
  <Select size="sm" aria-label="Select size sm" placeholder="Choose..." :items="{ a: 'Option A', b: 'Option B' }" />
  <Select size="base" aria-label="Select size base" placeholder="Choose..." :items="{ a: 'Option A', b: 'Option B' }" />
  <Select size="lg" aria-label="Select size lg" placeholder="Choose..." :items="{ a: 'Option A', b: 'Option B' }" />
</template>

Without Visible Label

When a visible label is not needed, use aria-label for accessibility.

<script setup>
import { ref } from "vue";
import { Select } from "@dicehub/phi/components/select";

const value = ref("apple");
</script>

<template>
  <Select
    v-model="value"
    aria-label="Select a fruit"
    :items="{ apple: 'Apple', banana: 'Banana', cherry: 'Cherry' }"
  />
</template>

With Description

Select shows helper text below the trigger through the description prop.

<script setup>
import { ref } from "vue";
import { Select } from "@dicehub/phi/components/select";

const value = ref(null);
</script>

<template>
  <Select
    v-model="value"
    label="Issue Type"
    description="Choose the category that best describes your issue"
    :items="{ bug: 'Bug', documentation: 'Documentation', feature: 'Feature' }"
  />
</template>

With Error

Pass error to display a validation message. The error replaces description text.

<script setup>
import { Select } from "@dicehub/phi/components/select";
</script>

<template>
  <Select
    label="Issue Type"
    error="Please select an issue type"
    :value="null"
    :items="{ bug: 'Bug', documentation: 'Documentation', feature: 'Feature' }"
  />
</template>

Placeholder

Use placeholder to show text when no value is selected.

<script setup>
import { ref } from "vue";
import { Select } from "@dicehub/phi/components/select";

const value = ref(null);
</script>

<template>
  <Select
    v-model="value"
    label="Category"
    placeholder="Choose a category..."
    :items="{ bug: 'Bug', documentation: 'Documentation', feature: 'Feature' }"
  />
</template>

Label with Tooltip

Add a tooltip icon next to the label with labelTooltip.

<script setup>
import { ref } from "vue";
import { Select } from "@dicehub/phi/components/select";

const value = ref(null);
</script>

<template>
  <Select
    v-model="value"
    label="Priority"
    label-tooltip="Higher priority issues are addressed first"
    placeholder="Select priority"
    :items="{ low: 'Low', medium: 'Medium', high: 'High', critical: 'Critical' }"
  />
</template>

Custom Rendering

Use the value slot or renderValue to customize the selected value display.

<script setup>
import { ref } from "vue";
import { Select } from "@dicehub/phi/components/select";

const languages = [
  { value: "en", label: "English", emoji: "🇬🇧" },
  { value: "fr", label: "French", emoji: "🇫🇷" },
];
const value = ref(languages[0]);
const equalByValue = (item, value) => item?.value === value?.value;
</script>

<template>
  <Select v-model="value" label="Language" :is-item-equal-to-value="equalByValue">
    <template #value="{ value, empty }">
      <span v-if="!empty">{{ value.emoji }} {{ value.label }}</span>
    </template>
    <Select.Option v-for="language in languages" :key="language.value" :value="language">
      {{ language.emoji }} {{ language.label }}
    </Select.Option>
  </Select>
</template>

Loading

The loading prop disables the trigger and swaps the selected value for a skeleton.

<script setup>
import { computed, onMounted, ref } from "vue";
import { Select } from "@dicehub/phi/components/select";

const loading = ref(true);
const value = ref(null);
const items = computed(() => loading.value ? undefined : { Visal: "Visal", John: "John" });

onMounted(() => window.setTimeout(() => { loading.value = false; }, 2000));
</script>

<template>
  <Select aria-label="Loading select" loading />
  <Select v-model="value" label="Assignee" :loading="loading" :items="items" />
</template>

Multiple Selection

Enable multiple selection with multiple. The value becomes an array.

<script setup>
import { ref } from "vue";
import { Select } from "@dicehub/phi/components/select";

const value = ref(["Name", "Location", "Size"]);
const renderValue = (selected) => selected.join(", ");
</script>

<template>
  <Select v-model="value" label="Visible Columns" multiple :render-value="renderValue">
    <Select.Option value="Name">Name</Select.Option>
    <Select.Option value="Location">Location</Select.Option>
    <Select.Option value="Size">Size</Select.Option>
  </Select>
</template>

More Example

Object values can use isItemEqualToValue for value-based comparison.

<script setup>
import { ref } from "vue";
import { Select } from "@dicehub/phi/components/select";

const authors = [
  { id: 1, name: "John Doe", title: "Programmer" },
  { id: 2, name: "Alice Smith", title: "Software Engineer" },
];
const value = ref(null);
const equalById = (item, value) => item?.id === value?.id;
const renderAuthor = (author) => author?.name ?? "";
</script>

<template>
  <Select
    v-model="value"
    label="Author"
    placeholder="Select an author"
    :is-item-equal-to-value="equalById"
    :render-value="renderAuthor"
  >
    <Select.Option v-for="author in authors" :key="author.id" :value="author">
      {{ author.name }} - {{ author.title }}
    </Select.Option>
  </Select>
</template>

Disabled Options

Options can be disabled with the disabled prop.

<script setup>
import { ref } from "vue";
import { Select } from "@dicehub/phi/components/select";

const regions = [
  { value: "us-east", label: "US East" },
  { value: "eu-west", label: "EU West", disabled: true },
];
const value = ref(null);
const equalByValue = (item, value) => item?.value === value?.value;
</script>

<template>
  <Select v-model="value" label="Deployment Region" :is-item-equal-to-value="equalByValue">
    <Select.Option v-for="region in regions" :key="region.value" :value="region" :disabled="region.disabled">
      {{ region.label }}
    </Select.Option>
  </Select>
</template>

Disabled Items (via items prop)

The items object-map accepts descriptor objects with disabled metadata.

<script setup>
import { ref } from "vue";
import { Select } from "@dicehub/phi/components/select";

const value = ref("free");
</script>

<template>
  <Select
    v-model="value"
    label="Plan"
    :items="{
      free: 'Free',
      pro: 'Pro',
      business: { label: 'Business', disabled: true },
      enterprise: { label: 'Enterprise', disabled: true },
    }"
  />
</template>

Grouped Options

Use Select.Group, Select.GroupLabel, and Select.Separator to organize options.

<script setup>
import { ref } from "vue";
import { Select } from "@dicehub/phi/components/select";

const fruits = [{ value: "apple", label: "Apple" }];
const vegetables = [{ value: "carrot", label: "Carrot" }];
const value = ref(null);
const equalByValue = (item, value) => item?.value === value?.value;
</script>

<template>
  <Select v-model="value" label="Food" :is-item-equal-to-value="equalByValue">
    <Select.Group>
      <Select.GroupLabel>Fruits</Select.GroupLabel>
      <Select.Option v-for="food in fruits" :key="food.value" :value="food">{{ food.label }}</Select.Option>
    </Select.Group>
    <Select.Separator />
    <Select.Group>
      <Select.GroupLabel>Vegetables</Select.GroupLabel>
      <Select.Option v-for="food in vegetables" :key="food.value" :value="food">{{ food.label }}</Select.Option>
    </Select.Group>
  </Select>
</template>

Groups with Disabled Options

Combine groups, separators, and disabled options in one dropdown.

<script setup>
import { ref } from "vue";
import { Select } from "@dicehub/phi/components/select";

const available = [{ value: "us-east-1", label: "US East (N. Virginia)" }];
const unavailable = [{ value: "ap-south-1", label: "AP South (Mumbai)" }];
const value = ref(null);
const equalByValue = (item, value) => item?.value === value?.value;
</script>

<template>
  <Select v-model="value" label="Server Region" :is-item-equal-to-value="equalByValue">
    <Select.Group>
      <Select.GroupLabel>Available</Select.GroupLabel>
      <Select.Option v-for="region in available" :key="region.value" :value="region">{{ region.label }}</Select.Option>
    </Select.Group>
    <Select.Separator />
    <Select.Group>
      <Select.GroupLabel>Unavailable</Select.GroupLabel>
      <Select.Option v-for="region in unavailable" :key="region.value" :value="region" disabled>
        {{ region.label }}
      </Select.Option>
    </Select.Group>
  </Select>
</template>

Long List (Scrolling Test)

A long list validates popup scrolling without overscroll bounce.

<script setup>
import { ref } from "vue";
import { Select } from "@dicehub/phi/components/select";

const items = Array.from({ length: 50 }, (_, index) => ({
  value: `item-${index + 1}`,
  label: `Option ${index + 1}`,
}));
const value = ref(null);
const equalByValue = (item, value) => item?.value === value?.value;
</script>

<template>
  <Select v-model="value" label="Long List Select" :is-item-equal-to-value="equalByValue">
    <Select.Option v-for="item in items" :key="item.value" :value="item">
      {{ item.label }}
    </Select.Option>
  </Select>
</template>

API Reference

Select

Root select component with trigger, optional field label, and dropdown content. The trigger uses the control surface, including while open; the popup uses the base surface.

Prop / EventTypeDefaultDescription
default slotunknown-Child Select.Option, Select.Group, Select.GroupLabel, and Select.Separator components. If omitted, options are generated from items.
trigger slot{ label; value; items; empty; open }-Replaces the trigger through Ark UI as-child forwarding. Render exactly one interactive child, such as Toolbar.Button.
value slot{ value: unknown; items: SelectCollectionItem[]; empty: boolean }-Custom Vue slot for rendering the selected value inside the trigger.
classstring-Additional CSS classes applied to the trigger.
size"xs" | "sm" | "base" | "lg""base"Trigger size. Matches the Input component sizes.
labelstring-Visible label rendered above the trigger.
hideLabelbooleanfalseDeprecated hidden-label mode. Prefer aria-label for selects without a visible label.
placeholderstring-Text shown when no value is selected.
loadingbooleanfalseShows a skeleton value and disables the trigger.
disabledbooleanfalseDisables the select trigger and all selection interaction.
requiredboolean-Native required state. When explicitly false, the label shows optional text.
labelTooltipstring-Tooltip content displayed next to the visible label.
descriptionstring-Helper text displayed below the trigger.
errorstring | SelectFieldError-Validation message. When present, it replaces description and marks the trigger invalid.
itemsRecord<string, SelectItemValue> | SelectInputItem[]-Options rendered automatically when no default slot is provided. Object values may be descriptor objects with label and disabled.
modelValue / v-modelunknown | unknown[] | null-Controlled selected value. Multiple mode uses an array.
valueunknown | unknown[] | null-Controlled selected value alias for modelValue.
defaultValueunknown | unknown[] | null-Initial selected value for uncontrolled usage.
multiplebooleanfalseEnables multiple selection and emits arrays from v-model and @value-change.
renderValue(value: unknown) => string-String formatter for the trigger value. Use the value slot for rich Vue markup.
isItemEqualToValue(itemValue: unknown, value: unknown) => booleanObject.isCustom comparison for object values.
openboolean-Controlled open state.
defaultOpenboolean-Initial open state for uncontrolled usage.
positioningobjectbottom-start, 4px gutterArk positioning options for the popup. By default, the popup opens below the trigger and stays collision-aware.
namestring-Native hidden select name for form submission.
@update:model-value(value: unknown) => void-v-model update event with the original option value.
@value-change(value: unknown, details: SelectValueChangeDetails) => void-Emitted when selection changes. Details include selected items and internal value keys.
@open-change(details: SelectOpenChangeDetails) => void-Emitted when the popup opens or closes.
@highlight-change(details: SelectHighlightChangeDetails) => void-Emitted when the highlighted option changes.

Select.Option

Individual option rendered inside a Select list.

Prop / EventTypeDefaultDescription
default slotunknown-Visible option content.
valueunknown-Option value emitted by the parent Select.
labelunknown-Text label used for selection display when slot content is custom.
disabledbooleanfalsePrevents this option from being selected.
classstring-Additional CSS classes applied to the option item.

Select.Group

Groups related options under a shared label.

Prop / EventTypeDefaultDescription
default slotunknown-Grouped options and an optional Select.GroupLabel.
classstring-Additional CSS classes applied to the group wrapper.

Select.GroupLabel

Visible label for a Select.Group.

Prop / EventTypeDefaultDescription
default slotunknown-Group heading content.
classstring-Additional CSS classes applied to the group label.

Select.Separator

Visual divider between option groups.

Prop / EventTypeDefaultDescription
classstring-Additional CSS classes applied to the separator.

Accessibility

Accessible Name

Use a visible label when possible. For compact controls without a visible label, provide aria-label or aria-labelledby.

Keyboard Navigation

Enter, Space, or ArrowDown opens the list. Arrow keys move highlight. Enter selects. Escape closes.

Screen Readers

The component is built on Ark UI Select primitives and preserves combobox/listbox semantics, disabled states, and hidden native form control output.