Skip to content
Kappa
Select
@dicehub/kappav0.4.2

Select

Chooses one or more values from a known collection while preserving native form semantics.

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

const region = ref(["eu-central"]);
const regions = [
  { label: "EU Central · Frankfurt", value: "eu-central" },
  { label: "EU West · Dublin", value: "eu-west" },
  { label: "US East · Virginia", value: "us-east" },
];
</script>

<template>
  <Select
    v-model="region"
    :items="regions"
    label="Compute region"
    description="Routes the run to the selected cluster."
    name="compute-region"
    placeholder="Select a region"
  />
</template>

Installation

Select is part of the published Kappa package. Use the barrel import for a shared component surface or the granular path for a smaller feature boundary.

Barrel

import { Select, createSelectCollection } from "@dicehub/kappa";

Granular

import {
  Select,
  createSelectCollection,
} from "@dicehub/kappa/components/select";

Usage

Pass items for object records with label, value, and optional disabled. Pass an Ark ListCollection when you need custom mappers, groups, or a hand-authored list. The convenience root renders the trigger, popup, option rows, and one native HiddenSelect.

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

const value = ref(["openfoam"]);
const formats = [
  { label: "OpenFOAM mesh", value: "openfoam" },
  { label: "Binary mesh", value: "binary" },
  { label: "Fluent mesh", value: "fluent" },
  { label: "STAR-CCM+ mesh", value: "star", disabled: true },
];
</script>

<template>
  <Select
    v-model="value"
    :items="formats"
    label="Mesh format"
    name="mesh-format"
    placeholder="Select a format"
  />
</template>

Ark models both single and multiple selection as string[]; single-select state contains zero or one value. For a custom composition, put parts inside the root and do not add a second hidden select. Use RootProvider only when you already own an Ark machine and need to share it across a view.

Composition

Typical part hierarchy
Select.Root
├── Select.Label
├── Select.Control
│   └── Select.Trigger
│       ├── Select.ValueText
│       └── Select.Indicator
├── Select.Positioner + Teleport
│   └── Select.Content
│       └── Select.List
│           ├── Select.Group
│           │   ├── Select.GroupLabel
│           │   └── Select.Option
│           │       ├── Select.ItemText
│           │       └── Select.ItemIndicator
│           └── Select.Separator
└── Select.HiddenSelect (automatic)

Ark UI Select owns the state machine, collection lookup, typeahead, keyboard focus, selection, dismissal, and ARIA attributes. Kappa owns the compound names, semantic tokens, field convenience props, popup teleport, and visual states.

  • Select and Select.Root are the same convenience root.
  • Select.Option is a readable alias for Ark-backed Select.Item.
  • Positioner teleports to body by default; set :teleport="false" for in-place rendering.
  • alignItemWithTrigger is a Kappa placement extension because Ark UI has no matching option. Ark still owns selection and focus; Kappa returns to normal anchored placement when alignment would cross the viewport padding.
  • List renders collection items by default. Set :render-items="false" when grouping or ordering rows yourself.
  • Use as-child on a part only when the replacement element preserves its role, focus, and keyboard contract.
  • The convenience root adds one HiddenSelect for forms. Do not render another one in the same root.
<script setup>
import { ref } from "vue";
import {
  Select,
  createSelectCollection,
} from "@dicehub/kappa/components/select";

const solver = ref(["gamg"]);
const solvers = [
  { label: "GAMG", value: "gamg", group: "Pressure" },
  { label: "PCG", value: "pcg", group: "Pressure" },
  { label: "PBiCGStab", value: "pbicgstab", group: "Momentum" },
  { label: "smoothSolver", value: "smooth", group: "Momentum" },
  { label: "diagonal", value: "diagonal", group: "Direct" },
];
const collection = createSelectCollection({
  items: solvers,
  groupBy: (item) => item.group,
  itemToString: (item) => item.label,
  itemToValue: (item) => item.value,
});
</script>

<template>
  <Select v-model="solver" :collection="collection">
    <template #default="{ collection: visibleCollection }">
      <Select.Label>Linear solver</Select.Label>
      <Select.Control>
        <Select.Trigger>
          <Select.ValueText placeholder="Select a solver" />
          <Select.Indicator />
        </Select.Trigger>
      </Select.Control>
      <Select.Positioner>
        <Select.Content>
          <Select.List :render-items="false">
            <Select.Group v-for="[group, items] in visibleCollection.group()" :key="group">
              <Select.GroupLabel>{{ group }}</Select.GroupLabel>
              <Select.Option v-for="item in items" :key="item.value" :item="item">
                {{ item.label }}
              </Select.Option>
            </Select.Group>
          </Select.List>
        </Select.Content>
      </Select.Positioner>
    </template>
  </Select>
</template>

Examples

Grouped options

Render filtered collection groups while keeping Ark's active option and keyboard state.

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

const solver = ref(["gamg"]);
const solvers = [
  { label: "GAMG", value: "gamg", group: "Pressure" },
  { label: "PCG", value: "pcg", group: "Pressure" },
  { label: "PBiCGStab", value: "pbicgstab", group: "Momentum" },
  { label: "smoothSolver", value: "smooth", group: "Momentum" },
  { label: "diagonal", value: "diagonal", group: "Direct" },
];
const collection = createSelectCollection({
  items: solvers,
  groupBy: (item) => item.group,
  itemToString: (item) => item.label,
  itemToValue: (item) => item.value,
});
</script>

<template>
  <Select v-model="solver" :collection="collection">
    <template #default="{ collection: visibleCollection }">
      <Select.Label>Linear solver</Select.Label>
      <Select.Control>
        <Select.Trigger>
          <Select.ValueText placeholder="Select a solver" />
          <Select.Indicator />
        </Select.Trigger>
      </Select.Control>
      <Select.Positioner>
        <Select.Content>
          <Select.List :render-items="false">
            <Select.Group v-for="[group, items] in visibleCollection.group()" :key="group">
              <Select.GroupLabel>{{ group }}</Select.GroupLabel>
              <Select.Option v-for="item in items" :key="item.value" :item="item">
                {{ item.label }}
              </Select.Option>
            </Select.Group>
          </Select.List>
        </Select.Content>
      </Select.Positioner>
    </template>
  </Select>
</template>

Multiple values

Keep selected rows identifiable with check marks while only the active row receives a highlight.

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

const selectedColumns = ref(["name", "location", "size"]);
const columns = [
  { label: "Name", value: "name" },
  { label: "Location", value: "location" },
  { label: "Size", value: "size" },
  { label: "Read", value: "read" },
  { label: "Write", value: "write" },
  { label: "Created at", value: "created-at" },
];
</script>

<template>
  <Select
    v-model="selectedColumns"
    :items="columns"
    label="Visible columns"
    multiple
    name="visible-columns"
    placeholder="Select columns"
  />
</template>

Placement

Set Ark's placement and gutter through the positioning prop. These examples disable collision flipping so each position stays visible.

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

const planets = [
  { label: "Mercury", value: "mercury" },
  { label: "Venus", value: "venus" },
  { label: "Earth", value: "earth" },
  { label: "Mars", value: "mars" },
];
</script>

<template>
  <Select
    :default-value="['earth']"
    :items="planets"
    :positioning="{ flip: false, placement: 'bottom-start' }"
    label="bottom-start (default)"
  />
  <Select
    :default-value="['earth']"
    :items="planets"
    :positioning="{ flip: false, placement: 'top-start' }"
    label="top-start"
  />
  <Select
    :default-value="['earth']"
    :items="planets"
    :positioning="{ flip: false, placement: 'bottom-end' }"
    label="bottom-end"
  />
  <Select
    :default-value="['earth']"
    :items="planets"
    :positioning="{ flip: false, gutter: 12, placement: 'bottom-start' }"
    label="gutter: 12"
  />
</template>

Aligned to the selected option

Overlay the selected option on the trigger when space permits. Kappa falls back to anchored placement near a viewport edge.

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

const anchoredPlanet = ref(["mars"]);
const alignedPlanet = ref(["mars"]);
const planets = [
  { label: "Mercury", value: "mercury" },
  { label: "Venus", value: "venus" },
  { label: "Earth", value: "earth" },
  { label: "Mars", value: "mars" },
  { label: "Jupiter", value: "jupiter" },
  { label: "Saturn", value: "saturn" },
  { label: "Uranus", value: "uranus" },
  { label: "Neptune", value: "neptune" },
];
</script>

<template>
  <Select
    v-model="anchoredPlanet"
    :items="planets"
    aria-label="Anchored planet"
  />
  <Select
    v-model="alignedPlanet"
    :items="planets"
    align-item-with-trigger
    aria-label="Aligned planet"
  />
</template>

Sizes

Use the compact sizes for dense toolbars and the base size for form fields.

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

const options = [
  { label: "Option A", value: "a" },
  { label: "Option B", value: "b" },
];
</script>

<template>
  <div v-for="size in ['xs', 'sm', 'base', 'lg']" :key="size">
    <span>{{ size }}</span>
    <Select
      :size="size"
      :items="options"
      :aria-label="`Select size ${size}`"
      placeholder="Choose..."
    />
  </div>
</template>

Disabled, read-only, and invalid

Keep unavailable controls explicit and connect errors to the trigger.

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

const formats = [
  { label: "OpenFOAM mesh", value: "openfoam" },
  { label: "Binary mesh", value: "binary" },
  { label: "Fluent mesh", value: "fluent" },
];
</script>

<template>
  <Select :items="formats" disabled label="Source format" placeholder="Unavailable" />
  <Select :items="formats" :default-value="['openfoam']" read-only label="Pinned format" />
  <Select :items="formats" error="Select one supported output format." label="Output format" required />
</template>

Controlled value

Drive the selected value from Vue state and expose it to a live output.

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

const region = ref(["eu-west"]);
const regions = [
  { label: "EU Central · Frankfurt", value: "eu-central" },
  { label: "EU West · Dublin", value: "eu-west" },
  { label: "US East · Virginia", value: "us-east" },
];
</script>

<template>
  <Select v-model="region" :items="regions" label="Failover region" />
  <output aria-live="polite">Controlled value: {{ region[0] }}</output>
</template>

Long list (scrolling test)

Use the bounded popup for long collections. Scrolling stays inside the list without page bounce.

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

const value = ref([]);
const options = Array.from({ length: 50 }, (_, index) => ({
  label: `Option ${String(index + 1).padStart(2, "0")}`,
  value: `option-${index + 1}`,
}));
</script>

<template>
  <Select
    v-model="value"
    :items="options"
    label="Long list select"
    description="Tests scrolling behavior with many options."
    placeholder="Choose an option"
  />
</template>

Right to left

Ark locale direction and Kappa logical geometry support right-to-left interfaces.

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

const regions = [
  { label: "فرانكفورت · أوروبا الوسطى", value: "eu-central" },
  { label: "دبلن · أوروبا الغربية", value: "eu-west" },
  { label: "فرجينيا · شرق الولايات المتحدة", value: "us-east" },
];
</script>

<template>
  <Select dir="rtl" :items="regions" label="منطقة الحساب" placeholder="اختر منطقة" />
</template>

Accessibility

  • Give the root a visible Label or an explicit aria-label.
  • Tab focuses the trigger. Enter, Space, or Arrow Down opens the popup; Arrow Up opens it with the last option highlighted.
  • Arrow keys move through enabled options. Type characters for typeahead, Enter to select, and Escape to close and restore focus.
  • Disabled options remain visible and are skipped by Ark keyboard navigation. Read-only controls stay focusable but do not change value.
  • description and error connect to the trigger through aria-describedby; errors also set aria-invalid.
  • The hidden native select carries name, required, and form reset semantics. Keep option values stable and unique.
  • Direction follows the nearest Ark locale or the root dir. Kappa uses logical CSS and stops popup motion under reduced-motion preferences.

See the official Ark UI Select documentationfor the primitive behavior and accessibility contract.

API Reference

Select.Root

PropTypeDefaultDescription
alignItemWithTriggerbooleanfalseCenter the selected option over the trigger when viewport space permits.
itemsreadonly T[][]Convenience collection. Objects may expose label, value, and disabled.
collectionListCollection<T>generatedArk collection for custom item mapping, grouping, and advanced rendering.
modelValuestring[]undefinedControlled selected values. A single-select value uses a one-item array internally.
defaultValuestring[][]Initial selected values for uncontrolled usage.
multiplebooleanfalseAllow more than one option and keep the popup open after selection by default.
open / defaultOpenbooleanfalseControl or initialize popup visibility.
disabledbooleanfalsePrevent focus, opening, and selection.
readOnlybooleanfalseKeep the value visible while preventing changes.
invalidbooleanfalseMark the control invalid. error also enables this state.
requiredbooleanfalseApply required form semantics to the hidden native select.
label / description / errorstringundefinedConvenience field content with label and description/error relationships.
itemToString / itemToValue / isItemDisabledfunctionobject defaultsMap custom collection records to accessible text, values, and disabled state.
size'xs' | 'sm' | 'base' | 'lg''base'Kappa control geometry.
positioningArk PositioningOptionssame width, bottom-startForwarded Ark floating-position options.
dir'ltr' | 'rtl'inheritedLocale direction used by Ark keyboard and placement behavior.

Select.Item / Select.Option

PropTypeDefaultDescription
itemTundefinedCollection item. Preferred for object collections.
valueTundefinedResolve an item by value from the nearest collection.
persistFocusbooleanfalseKeep focus on the option after selection.
asChildbooleanfalseMerge Ark item behavior into a custom element.

Parts

PartElementDescription
Select.Root / RootProviderdivOwn Ark state, collection, controlled values, and compound context.
Select.LabellabelNames the trigger and links it to the field.
Select.Control / Triggerdiv / buttonThe focusable combobox trigger and its visual control.
Select.ValueText / Indicatorspan / divSelected label, placeholder, and disclosure indicator.
Select.Positioner / ContentdivTeleported floating surface with Ark positioning.
Select.List / OptiondivCollection list and selectable options.
Select.ItemText / ItemIndicatorspanAccessible option text and selected mark.
Select.Group / GroupLabeldivOptional semantic grouping for related options.
Select.SeparatordivOptional semantic divider between option groups.
Select.ClearTriggerbuttonClears selected values when the composition includes it.
Select.HiddenSelectselectNative form bridge. The convenience root adds it automatically.
Select.Context / ItemContextslotExpose Ark state for advanced render logic.

Events

EventPayloadDescription
update:modelValuestring[]Controlled selected values changed.
valueChangeSelectValueChangeDetailsArk selection details.
update:open / openChangeboolean / SelectOpenChangeDetailsPopup visibility changed.
update:highlightedValue / highlightChangestring / SelectHighlightChangeDetailsActive option changed.
selectSelectSelectionDetailsAn option was selected.
focusOutside / interactOutside / pointerDownOutsideArk outside eventThe popup interacted with or lost focus outside.

Exports

ExportDescription
SelectNamespace and convenience root with all compound parts.
SelectRoot / SelectOptionNamed root and option aliases.
createSelectCollectionArk ListCollection factory for custom records and groups.
SelectProps / SelectEmits / SelectSlotsKappa root contracts.
SelectSelectionDetailsTyped payload for the select event, derived from Ark RootEmits.
SelectRootProvider / SelectApiAdvanced shared Ark machine composition.
SELECT_SIZES / SELECT_DEFAULT_SIZESupported Kappa size values.
selectAnatomy / useSelect / useSelectContextArk UI behavior and anatomy exports.