Skip to content

Select

Experimental Alpha

This library is a work-in-progress. We are releasing it early to gather feedback, but it is not ready for production.

The Select component is a lightly styled native HTML select element that provides consistent styling across browsers while maintaining native functionality and accessibility.

Examples

Vue
Lit
React
Live Preview

Basic Select

A standard select dropdown with custom styling.

Size Variants

The Select component comes in three sizes: small, default, and large.

Disabled State

Selects can be disabled to prevent user interaction.

Multiple Selection

Enable multiple selection mode with the multiple prop. Use Ctrl/Cmd + Click to select multiple options.

With Change Handler

Listen to selection changes with the @change event.

With v-model

Use v-model:value for two-way data binding.

Multiple Selection with v-model

Use v-model:value with multiple to manage an array of selected values.

Event Handling

The Select component fires multiple events: @change, @focus, @blur, and @click.

External Label Support

The Select component supports external labels with helper text, required fields, and validation states.

Label Positioning

Control label position with label-position: 'top' (default), 'start', 'end', or 'bottom'. Note: 'bottom' is not recommended as the dropdown will cover the label.

CSS Shadow Parts Customization

Use CSS Shadow Parts to customize the select's appearance.

View Vue Code
<template>
  <section>
    <div class="mbe4">
      <h2>Basic Select</h2>
      <p class="mbs2 mbe3">
        A standard select dropdown with custom styling.
      </p>
    </div>
    <div class="mbe4" style="max-width: 400px;">
      <VueSelect
        label="Greatest Tennis Player"
        name="tennis"
      >
        <option value="">- Select a player -</option>
        <option value="andre">Andre Agassi</option>
        <option value="serena">Serena Williams</option>
        <option value="roger">Roger Federer</option>
        <option value="mac">John McEnroe</option>
        <option value="martina">Martina Navratilova</option>
        <option value="rafa">Rafael Nadal</option>
        <option value="borg">Bjorn Borg</option>
        <option value="althea">Althea Gibson</option>
      </VueSelect>
    </div>

    <div class="mbe4">
      <h2>Size Variants</h2>
      <p class="mbs2 mbe3">
        The Select component comes in three sizes: small, default, and large.
      </p>
    </div>
    <div
      class="mbe4"
      style="max-width: 400px;"
    >
      <div class="mbe3">
        <VueSelect
          label="Small Select"
          size="small"
          name="small"
        >
          <option value="">Select an option</option>
          <option value="1">Option 1</option>
          <option value="2">Option 2</option>
          <option value="3">Option 3</option>
        </VueSelect>
      </div>

      <div class="mbe3">
        <VueSelect
          label="Default Select"
          name="default"
        >
          <option value="">Select an option</option>
          <option value="1">Option 1</option>
          <option value="2">Option 2</option>
          <option value="3">Option 3</option>
        </VueSelect>
      </div>

      <div class="mbe3">
        <VueSelect
          label="Large Select"
          size="large"
          name="large"
        >
          <option value="">Select an option</option>
          <option value="1">Option 1</option>
          <option value="2">Option 2</option>
          <option value="3">Option 3</option>
        </VueSelect>
      </div>
    </div>

    <div class="mbe4">
      <h2>Disabled State</h2>
      <p class="mbs2 mbe3">
        Selects can be disabled to prevent user interaction.
      </p>
    </div>
    <div
      class="mbe4"
      style="max-width: 400px;"
    >
      <VueSelect
        label="Disabled Select"
        :disabled="true"
        name="disabled"
      >
        <option value="">Cannot select</option>
        <option value="1">Option 1</option>
        <option value="2">Option 2</option>
        <option value="3">Option 3</option>
      </VueSelect>
    </div>

    <div class="mbe4">
      <h2>Multiple Selection</h2>
      <p class="mbs2 mbe3">
        Enable multiple selection mode with the <code>multiple</code> prop. Use Ctrl/Cmd + Click to select multiple options.
      </p>
    </div>
    <div
      class="mbe4"
      style="max-width: 400px;"
    >
      <VueSelect
        label="Favorite Tennis Players (Select Multiple)"
        :multiple="true"
        :multiple-size="6"
        name="multiple"
      >
        <option value="andre">Andre Agassi</option>
        <option value="serena">Serena Williams</option>
        <option value="roger">Roger Federer</option>
        <option value="mac">John McEnroe</option>
        <option value="martina">Martina Navratilova</option>
        <option value="rafa">Rafael Nadal</option>
        <option value="borg">Bjorn Borg</option>
        <option value="althea">Althea Gibson</option>
      </VueSelect>
    </div>

    <div class="mbe4">
      <h2>With Change Handler</h2>
      <p class="mbs2 mbe3">
        Listen to selection changes with the <code>@change</code> event.
      </p>
    </div>
    <div
      class="mbe4"
      style="max-width: 400px;"
    >
      <VueSelect
        label="Select a Fruit"
        name="fruit"
        @change="handleChange"
      >
        <option value="">Choose a fruit</option>
        <option value="apple">Apple</option>
        <option value="banana">Banana</option>
        <option value="orange">Orange</option>
        <option value="grape">Grape</option>
      </VueSelect>
      <p
        v-if="selectedFruit"
        style="margin-top: 0.75rem; padding: 0.75rem; background: var(--ag-background-secondary); border-radius: var(--ag-radius-md);"
      >
        You selected: <strong>{{ selectedFruit }}</strong>
      </p>
    </div>

    <div class="mbe4">
      <h2>With v-model</h2>
      <p class="mbs2 mbe3">
        Use <code>v-model:value</code> for two-way data binding.
      </p>
    </div>
    <div
      class="mbe4"
      style="max-width: 400px;"
    >
      <VueSelect
        label="Choose Your Favorite Color"
        name="color"
        v-model:value="selectedColor"
      >
        <option value="">Choose a color</option>
        <option value="red">Red</option>
        <option value="blue">Blue</option>
        <option value="green">Green</option>
        <option value="purple">Purple</option>
      </VueSelect>
      <p
        v-if="selectedColor"
        style="margin-top: 0.75rem; padding: 0.75rem; background: var(--ag-background-secondary); border-radius: var(--ag-radius-md);"
      >
        Your favorite color is: <strong>{{ selectedColor }}</strong>
      </p>
    </div>

    <div class="mbe4">
      <h2>Multiple Selection with v-model</h2>
      <p class="mbs2 mbe3">
        Use <code>v-model:value</code> with <code>multiple</code> to manage an array of selected values.
      </p>
    </div>
    <div
      class="mbe4"
      style="max-width: 400px;"
    >
      <VueSelect
        label="Select Your Favorite Sports (Ctrl/Cmd + Click)"
        name="sports"
        v-model:value="selectedSports"
        :multiple="true"
        :multiple-size="5"
      >
        <option value="tennis">Tennis</option>
        <option value="football">Football</option>
        <option value="basketball">Basketball</option>
        <option value="baseball">Baseball</option>
        <option value="soccer">Soccer</option>
      </VueSelect>
      <p
        v-if="selectedSports.length > 0"
        style="margin-top: 0.75rem; padding: 0.75rem; background: var(--ag-background-secondary); border-radius: var(--ag-radius-md);"
      >
        You selected <strong>{{ selectedSports.length }}</strong> sport(s): {{ selectedSports.join(', ') }}
      </p>
    </div>

    <div class="mbe4">
      <h2>Event Handling</h2>
      <p class="mbs2 mbe3">
        The Select component fires multiple events: <code>@change</code>, <code>@focus</code>, <code>@blur</code>, and <code>@click</code>.
      </p>
    </div>
    <div
      class="mbe4"
      style="max-width: 600px;"
    >
      <VueSelect
        label="Interact with this select to see events"
        name="events"
        @change="handleEventChange"
        @focus="handleFocus"
        @blur="handleBlur"
        @click="handleClick"
      >
        <option value="">Select an option</option>
        <option value="1">Option 1</option>
        <option value="2">Option 2</option>
        <option value="3">Option 3</option>
      </VueSelect>
      <div
        v-if="eventLog.length > 0"
        style="margin-top: 0.75rem; padding: 0.75rem; background: var(--ag-background-secondary); border-radius: var(--ag-radius-md);"
      >
        <strong>Event Log (most recent first):</strong>
        <div style="margin-top: 0.5rem; font-family: monospace; font-size: 0.875rem; line-height: 1.6;">
          <div
            v-for="(event, index) in eventLog"
            :key="index"
          >{{ event }}</div>
        </div>
      </div>
    </div>

    <div class="mbe4">
      <h2>External Label Support</h2>
      <p class="mbs2 mbe3">
        The Select component supports external labels with helper text, required fields, and validation states.
      </p>
    </div>
    <div
      class="mbe4"
      style="max-width: 400px;"
    >
      <div class="mbe3">
        <VueSelect
          label="Favorite Framework"
          name="framework"
        >
          <option value="">Choose a framework</option>
          <option value="react">React</option>
          <option value="vue">Vue</option>
          <option value="angular">Angular</option>
          <option value="svelte">Svelte</option>
        </VueSelect>
      </div>

      <div class="mbe3">
        <VueSelect
          label="Country"
          help-text="Select your country of residence"
          name="country"
        >
          <option value="">Select a country</option>
          <option value="us">United States</option>
          <option value="uk">United Kingdom</option>
          <option value="ca">Canada</option>
          <option value="au">Australia</option>
        </VueSelect>
      </div>

      <div class="mbe3">
        <VueSelect
          label="Preferred Language"
          :required="true"
          help-text="This field is required"
          name="language"
        >
          <option value="">Select a language</option>
          <option value="en">English</option>
          <option value="es">Spanish</option>
          <option value="fr">French</option>
          <option value="de">German</option>
        </VueSelect>
      </div>

      <div class="mbe3">
        <VueSelect
          label="Payment Method"
          :required="true"
          :invalid="true"
          error-message="Please select a payment method to continue"
          name="payment"
        >
          <option value="">Select payment method</option>
          <option value="credit">Credit Card</option>
          <option value="debit">Debit Card</option>
          <option value="paypal">PayPal</option>
        </VueSelect>
      </div>
    </div>

    <div class="mbe4">
      <h2>Label Positioning</h2>
      <p class="mbs2 mbe3">
        Control label position with <code>label-position</code>: 'top' (default), 'start', 'end', or 'bottom'.
        <strong>Note:</strong> 'bottom' is not recommended as the dropdown will cover the label.
      </p>
    </div>
    <div
      class="mbe4"
      style="max-width: 600px;"
    >
      <div class="mbe3">
        <VueSelect
          label="Top Label (Default)"
          label-position="top"
          name="pos-top"
        >
          <option value="">Select option</option>
          <option value="1">Option 1</option>
          <option value="2">Option 2</option>
          <option value="3">Option 3</option>
        </VueSelect>
      </div>

      <div class="mbe3">
        <VueSelect
          label="Start Position"
          label-position="start"
          name="pos-start"
        >
          <option value="">Select option</option>
          <option value="1">Option 1</option>
          <option value="2">Option 2</option>
          <option value="3">Option 3</option>
        </VueSelect>
      </div>

      <div class="mbe3">
        <VueSelect
          label="End Position"
          label-position="end"
          name="pos-end"
        >
          <option value="">Select option</option>
          <option value="1">Option 1</option>
          <option value="2">Option 2</option>
          <option value="3">Option 3</option>
        </VueSelect>
      </div>

      <div class="mbe3">
        <VueSelect
          label="Bottom Position (Not Recommended)"
          label-position="bottom"
          name="pos-bottom"
          help-text="The dropdown will cover this label when opened"
        >
          <option value="">Select option</option>
          <option value="1">Option 1</option>
          <option value="2">Option 2</option>
          <option value="3">Option 3</option>
        </VueSelect>
      </div>
    </div>

    <div class="mbe4">
      <h2>CSS Shadow Parts Customization</h2>
      <p class="mbs2 mbe3">
        Use CSS Shadow Parts to customize the select's appearance.
      </p>
    </div>
    <div
      class="mbe4"
      style="max-width: 400px;"
    >
      <VueSelect
        label="Custom Styled Select"
        name="custom"
        class="custom-select"
      >
        <option value="">Select a color</option>
        <option value="red">Red</option>
        <option value="blue">Blue</option>
        <option value="green">Green</option>
        <option value="purple">Purple</option>
      </VueSelect>
    </div>
  </section>
</template>

<script lang="ts">
import { defineComponent, ref } from "vue";
import { VueSelect } from "agnosticui-core/select/vue";

export default defineComponent({
  name: "SelectExamples",
  components: {
    VueSelect,
  },
  setup() {
    // Basic @change example
    const selectedFruit = ref("");

    // Vue wrapper emits detail payload directly, not full CustomEvent
    const handleChange = (detail: { value: string | string[] }) => {
      selectedFruit.value = detail.value as string;
    };

    // v-model examples
    const selectedColor = ref("");
    const selectedSports = ref<string[]>([]);

    // Event logging example
    const eventLog = ref<string[]>([]);

    const logEvent = (message: string) => {
      eventLog.value.unshift(message);
      if (eventLog.value.length > 8) eventLog.value.pop();
    };

    const handleEventChange = (detail: { value: string | string[] }) => {
      logEvent(`@change - value: ${detail.value}`);
    };

    const handleFocus = () => {
      logEvent("@focus");
    };

    const handleBlur = () => {
      logEvent("@blur");
    };

    const handleClick = () => {
      logEvent("@click");
    };

    return {
      selectedFruit,
      handleChange,
      selectedColor,
      selectedSports,
      eventLog,
      handleEventChange,
      handleFocus,
      handleBlur,
      handleClick,
    };
  },
});
</script>

<style scoped>
.custom-select::part(ag-select) {
  border: 2px solid var(--ag-primary);
  border-radius: var(--ag-radius-lg);
  font-weight: 500;
  background-color: var(--ag-background-secondary);
}

.custom-select::part(ag-select):focus {
  border-color: var(--ag-primary-dark);
  box-shadow: 0 0 0 3px var(--ag-blue-100);
}
</style>
Live Preview
View Lit / Web Component Code

import { LitElement, html, css } from 'lit';
import 'agnosticui-core/select';

export class SelectLitExamples extends LitElement {
  static properties = {
    selectedFruit: { type: String },
    selectedColor: { type: String },
    selectedSports: { type: Array },
    eventLog: { type: Array }
  };

  constructor() {
    super();
    this.selectedFruit = '';
    this.selectedColor = '';
    this.selectedSports = [];
    this.eventLog = [];
  }

  createRenderRoot() {
    return this;
  }

  handleChange(e) {
    this.selectedFruit = e.detail.value;
  }

  handleColorChange(e) {
    this.selectedColor = e.detail.value;
  }

  handleSportsChange(e) {
    this.selectedSports = Array.from(e.target.shadowRoot.querySelector('select').selectedOptions, option => option.value);
  }

  logEvent(message) {
    this.eventLog = [message, ...this.eventLog].slice(0, 8);
    this.requestUpdate();
  }

  handleEventChange(e) {
    this.logEvent(`@change - value: ${e.detail.value}`);
  }

  render() {
    return html`
      <section>
        <div class="mbe4">
          <h2>Basic Select</h2>
          <p class="mbs2 mbe3">
            A standard select dropdown with custom styling.
          </p>
        </div>
        <div class="mbe4" style="max-width: 400px;">
          <ag-select
            label="Greatest Tennis Player"
            name="tennis"
          >
            <option value="">- Select a player -</option>
            <option value="andre">Andre Agassi</option>
            <option value="serena">Serena Williams</option>
            <option value="roger">Roger Federer</option>
            <option value="mac">John McEnroe</option>
            <option value="martina">Martina Navratilova</option>
            <option value="rafa">Rafael Nadal</option>
            <option value="borg">Bjorn Borg</option>
            <option value="althea">Althea Gibson</option>
          </ag-select>
        </div>

        <div class="mbe4">
          <h2>Size Variants</h2>
          <p class="mbs2 mbe3">
            The Select component comes in three sizes: small, default, and large.
          </p>
        </div>
        <div
          class="mbe4"
          style="max-width: 400px;"
        >
          <div class="mbe3">
            <ag-select
              label="Small Select"
              size="small"
              name="small"
            >
              <option value="">Select an option</option>
              <option value="1">Option 1</option>
              <option value="2">Option 2</option>
              <option value="3">Option 3</option>
            </ag-select>
          </div>

          <div class="mbe3">
            <ag-select
              label="Default Select"
              name="default"
            >
              <option value="">Select an option</option>
              <option value="1">Option 1</option>
              <option value="2">Option 2</option>
              <option value="3">Option 3</option>
            </ag-select>
          </div>

          <div class="mbe3">
            <ag-select
              label="Large Select"
              size="large"
              name="large"
            >
              <option value="">Select an option</option>
              <option value="1">Option 1</option>
              <option value="2">Option 2</option>
              <option value="3">Option 3</option>
            </ag-select>
          </div>
        </div>

        <div class="mbe4">
          <h2>Disabled State</h2>
          <p class="mbs2 mbe3">
            Selects can be disabled to prevent user interaction.
          </p>
        </div>
        <div
          class="mbe4"
          style="max-width: 400px;"
        >
          <ag-select
            label="Disabled Select"
            disabled
            name="disabled"
          >
            <option value="">Cannot select</option>
            <option value="1">Option 1</option>
          </ag-select>
        </div>

        <div class="mbe4">
          <h2>Multiple Selection</h2>
          <p class="mbs2 mbe3">
            Enable multiple selection mode with the <code>multiple</code> prop. Use Ctrl/Cmd + Click to select multiple options.
          </p>
        </div>
        <div
          class="mbe4"
          style="max-width: 400px;"
        >
          <ag-select
            label="Favorite Tennis Players (Select Multiple)"
            multiple
            multiple-size="6"
            name="multiple"
            @change="${this.handleSportsChange}"
          >
            <option value="andre">Andre Agassi</option>
            <option value="serena">Serena Williams</option>
            <option value="roger">Roger Federer</option>
            <option value="mac">John McEnroe</option>
            <option value="martina">Martina Navratilova</option>
            <option value="rafa">Rafael Nadal</option>
            <option value="borg">Bjorn Borg</option>
            <option value="althea">Althea Gibson</option>
          </ag-select>
          ${this.selectedSports.length > 0 ? html`
            <p
              style="margin-top: 0.75rem; padding: 0.75rem; background: var(--ag-background-secondary); border-radius: var(--ag-radius-md);"
            >
              You selected <strong>${this.selectedSports.length}</strong> sport(s): ${this.selectedSports.join(', ')}
            </p>
          ` : ''}
        </div>

        <div class="mbe4">
          <h2>With Change Handler</h2>
          <p class="mbs2 mbe3">
            Listen to selection changes with the <code>@change</code> event.
          </p>
        </div>
        <div
          class="mbe4"
          style="max-width: 400px;"
        >
          <ag-select
            label="Select a Fruit"
            name="fruit"
            @change="${this.handleChange}"
          >
            <option value="">Choose a fruit</option>
            <option value="apple">Apple</option>
            <option value="banana">Banana</option>
            <option value="orange">Orange</option>
            <option value="grape">Grape</option>
          </ag-select>
          ${this.selectedFruit ? html`
            <p
              style="margin-top: 0.75rem; padding: 0.75rem; background: var(--ag-background-secondary); border-radius: var(--ag-radius-md);"
            >
              You selected: <strong>${this.selectedFruit}</strong>
            </p>
          ` : ''}
        </div>

        <div class="mbe4">
          <h2>Event Handling</h2>
          <p class="mbs2 mbe3">
            The Select component fires multiple events: <code>@change</code>, <code>@focus</code>, <code>@blur</code>, and <code>@click</code>.
          </p>
        </div>
        <div
          class="mbe4"
          style="max-width: 600px;"
        >
          <ag-select
            label="Interact with this select to see events"
            name="events"
            @change="${this.handleEventChange}"
            @focus="${() => this.logEvent('@focus')}"
            @blur="${() => this.logEvent('@blur')}"
            @click="${() => this.logEvent('@click')}"
          >
            <option value="">Select an option</option>
            <option value="1">Option 1</option>
            <option value="2">Option 2</option>
            <option value="3">Option 3</option>
          </ag-select>
          ${this.eventLog.length > 0 ? html`
            <div
              style="margin-top: 0.75rem; padding: 0.75rem; background: var(--ag-background-secondary); border-radius: var(--ag-radius-md);"
            >
              <strong>Event Log (most recent first):</strong>
              <div style="margin-top: 0.5rem; font-family: monospace; font-size: 0.875rem; line-height: 1.6;">
                ${this.eventLog.map(event => html`<div>${event}</div>`)}
              </div>
            </div>
          ` : ''}
        </div>

        <div class="mbe4">
          <h2>External Label Support</h2>
          <p class="mbs2 mbe3">
            The Select component supports external labels with helper text, required fields, and validation states.
          </p>
        </div>
        <div
          class="mbe4"
          style="max-width: 400px;"
        >
          <div class="mbe3">
            <ag-select
              label="Favorite Framework"
              name="framework"
            >
              <option value="">Choose a framework</option>
              <option value="react">React</option>
              <option value="vue">Vue</option>
              <option value="angular">Angular</option>
              <option value="svelte">Svelte</option>
            </ag-select>
          </div>

          <div class="mbe3">
            <ag-select
              label="Country"
              help-text="Select your country of residence"
              name="country"
            >
              <option value="">Select a country</option>
              <option value="us">United States</option>
              <option value="uk">United Kingdom</option>
              <option value="ca">Canada</option>
              <option value="au">Australia</option>
            </ag-select>
          </div>

          <div class="mbe3">
            <ag-select
              label="Preferred Language"
              required
              help-text="This field is required"
              name="language"
            >
              <option value="">Select a language</option>
              <option value="en">English</option>
              <option value="es">Spanish</option>
              <option value="fr">French</option>
              <option value="de">German</option>
            </ag-select>
          </div>

          <div class="mbe3">
            <ag-select
              label="Payment Method"
              required
              invalid
              error-message="Please select a payment method to continue"
              name="payment"
            >
              <option value="">Select payment method</option>
              <option value="credit">Credit Card</option>
              <option value="debit">Debit Card</option>
              <option value="paypal">PayPal</option>
            </ag-select>
          </div>
        </div>

        <div class="mbe4">
          <h2>Label Positioning</h2>
          <p class="mbs2 mbe3">
            Control label position with <code>label-position</code>: 'top' (default), 'start', 'end', or 'bottom'.
            <strong>Note:</strong> 'bottom' is not recommended as the dropdown will cover the label.
          </p>
        </div>
        <div
          class="mbe4"
          style="max-width: 600px;"
        >
          <div class="mbe3">
            <ag-select
              label="Top Label (Default)"
              label-position="top"
              name="pos-top"
            >
              <option value="">Select option</option>
              <option value="1">Option 1</option>
              <option value="2">Option 2</option>
              <option value="3">Option 3</option>
            </ag-select>
          </div>

          <div class="mbe3">
            <ag-select
              label="Start Position"
              label-position="start"
              name="pos-start"
            >
              <option value="">Select option</option>
              <option value="1">Option 1</option>
              <option value="2">Option 2</option>
              <option value="3">Option 3</option>
            </ag-select>
          </div>

          <div class="mbe3">
            <ag-select
              label="End Position"
              label-position="end"
              name="pos-end"
            >
              <option value="">Select option</option>
              <option value="1">Option 1</option>
              <option value="2">Option 2</option>
              <option value="3">Option 3</option>
            </ag-select>
          </div>

          <div class="mbe3">
            <ag-select
              label="Bottom Position (Not Recommended)"
              label-position="bottom"
              name="pos-bottom"
              help-text="The dropdown will cover this label when opened"
            >
              <option value="">Select option</option>
              <option value="1">Option 1</option>
              <option value="2">Option 2</option>
              <option value="3">Option 3</option>
            </ag-select>
          </div>
        </div>
      </section>
    `;
  }
}

customElements.define('select-lit-examples', SelectLitExamples);

Interactive Preview: Click the "Open in StackBlitz" button below to see this example running live in an interactive playground.

View React Code

import { ReactSelect } from "agnosticui-core/select/react";
import { useState } from "react";

export default function SelectReactExamples() {
  // Basic example
  const [selectedFruit, setSelectedFruit] = useState("");

  // Multiple example
  const [selectedSports, setSelectedSports] = useState([]);

  // Event logging
  const [eventLog, setEventLog] = useState([]);

  const logEvent = (message) => {
    setEventLog((prev) => [message, ...prev].slice(0, 8));
  };

  const handleEventChange = (e) => {
    logEvent(`onChange - value: ${e.detail.value}`);
  };

  const handleMultipleChange = (e) => {
    setSelectedSports(e.detail.value);
  };

  return (
    <section>
      <div className="mbe4">
        <h2>Basic Select</h2>
        <p className="mbs2 mbe3">
          A standard select dropdown with custom styling.
        </p>
      </div>
      <div className="mbe4" style={{ maxWidth: "400px" }}>
        <ReactSelect
          label="Greatest Tennis Player"
          name="tennis"
        >
          <option value="">- Select a player -</option>
          <option value="andre">Andre Agassi</option>
          <option value="serena">Serena Williams</option>
          <option value="roger">Roger Federer</option>
          <option value="mac">John McEnroe</option>
          <option value="martina">Martina Navratilova</option>
          <option value="rafa">Rafael Nadal</option>
          <option value="borg">Bjorn Borg</option>
          <option value="althea">Althea Gibson</option>
        </ReactSelect>
      </div>

      <div className="mbe4">
        <h2>Size Variants</h2>
        <p className="mbs2 mbe3">
          The Select component comes in three sizes: small, default, and large.
        </p>
      </div>
      <div className="mbe4" style={{ maxWidth: "400px" }}>
        <div className="mbe3">
          <ReactSelect
            label="Small Select"
            size="small"
            name="small"
          >
            <option value="">Select an option</option>
            <option value="1">Option 1</option>
            <option value="2">Option 2</option>
            <option value="3">Option 3</option>
          </ReactSelect>
        </div>

        <div className="mbe3">
          <ReactSelect
            label="Default Select"
            name="default"
          >
            <option value="">Select an option</option>
            <option value="1">Option 1</option>
            <option value="2">Option 2</option>
            <option value="3">Option 3</option>
          </ReactSelect>
        </div>

        <div className="mbe3">
          <ReactSelect
            label="Large Select"
            size="large"
            name="large"
          >
            <option value="">Select an option</option>
            <option value="1">Option 1</option>
            <option value="2">Option 2</option>
            <option value="3">Option 3</option>
          </ReactSelect>
        </div>
      </div>

      <div className="mbe4">
        <h2>Disabled State</h2>
        <p className="mbs2 mbe3">
          Selects can be disabled to prevent user interaction.
        </p>
      </div>
      <div className="mbe4" style={{ maxWidth: "400px" }}>
        <ReactSelect
          label="Disabled Select"
          disabled
          name="disabled"
        >
          <option value="">Cannot select</option>
          <option value="1">Option 1</option>
          <option value="2">Option 2</option>
          <option value="3">Option 3</option>
        </ReactSelect>
      </div>

      <div className="mbe4">
        <h2>Multiple Selection</h2>
        <p className="mbs2 mbe3">
          Enable multiple selection mode with the <code>multiple</code> prop. Use Ctrl/Cmd + Click to select multiple options.
        </p>
      </div>
      <div className="mbe4" style={{ maxWidth: "400px" }}>
        <ReactSelect
          label="Favorite Tennis Players (Select Multiple)"
          multiple
          multipleSize={6}
          name="multiple"
          onChange={handleMultipleChange}
        >
          <option value="andre">Andre Agassi</option>
          <option value="serena">Serena Williams</option>
          <option value="roger">Roger Federer</option>
          <option value="mac">John McEnroe</option>
          <option value="martina">Martina Navratilova</option>
          <option value="rafa">Rafael Nadal</option>
          <option value="borg">Bjorn Borg</option>
          <option value="althea">Althea Gibson</option>
        </ReactSelect>
        {selectedSports.length > 0 && (
          <p style={{ marginTop: "0.75rem", padding: "0.75rem", background: "var(--ag-background-secondary)", borderRadius: "var(--ag-radius-md)" }}>
            You selected <strong>{selectedSports.length}</strong> sport(s): {selectedSports.join(', ')}
          </p>
        )}
      </div>

      <div className="mbe4">
        <h2>With Change Handler</h2>
        <p className="mbs2 mbe3">
          Listen to selection changes with the <code>onChange</code> event.
        </p>
      </div>
      <div className="mbe4" style={{ maxWidth: "400px" }}>
        <ReactSelect
          label="Select a Fruit"
          name="fruit"
          onChange={(e) => setSelectedFruit(e.detail.value)}
        >
          <option value="">Choose a fruit</option>
          <option value="apple">Apple</option>
          <option value="banana">Banana</option>
          <option value="orange">Orange</option>
          <option value="grape">Grape</option>
        </ReactSelect>
        {selectedFruit && (
          <p style={{ marginTop: "0.75rem", padding: "0.75rem", background: "var(--ag-background-secondary)", borderRadius: "var(--ag-radius-md)" }}>
            You selected: <strong>{selectedFruit}</strong>
          </p>
        )}
      </div>

      <div className="mbe4">
        <h2>Event Handling</h2>
        <p className="mbs2 mbe3">
          The Select component fires multiple events: <code>onChange</code>, <code>onFocus</code>, <code>onBlur</code>, and <code>onClick</code>.
        </p>
      </div>
      <div className="mbe4" style={{ maxWidth: "600px" }}>
        <ReactSelect
          label="Interact with this select to see events"
          name="events"
          onChange={handleEventChange}
          onFocus={() => logEvent("onFocus")}
          onBlur={() => logEvent("onBlur")}
          onClick={() => logEvent("onClick")}
        >
          <option value="">Select an option</option>
          <option value="1">Option 1</option>
          <option value="2">Option 2</option>
          <option value="3">Option 3</option>
        </ReactSelect>
        {eventLog.length > 0 && (
          <div style={{ marginTop: "0.75rem", padding: "0.75rem", background: "var(--ag-background-secondary)", borderRadius: "var(--ag-radius-md)" }}>
            <strong>Event Log (most recent first):</strong>
            <div style={{ marginTop: "0.5rem", fontFamily: "monospace", fontSize: "0.875rem", lineHeight: "1.6" }}>
              {eventLog.map((event, index) => (
                <div key={index}>{event}</div>
              ))}
            </div>
          </div>
        )}
      </div>

      <div className="mbe4">
        <h2>External Label Support</h2>
        <p className="mbs2 mbe3">
          The Select component supports external labels with helper text, required fields, and validation states.
        </p>
      </div>
      <div className="mbe4" style={{ maxWidth: "400px" }}>
        <div className="mbe3">
          <ReactSelect
            label="Favorite Framework"
            name="framework"
          >
            <option value="">Choose a framework</option>
            <option value="react">React</option>
            <option value="vue">Vue</option>
            <option value="angular">Angular</option>
            <option value="svelte">Svelte</option>
          </ReactSelect>
        </div>

        <div className="mbe3">
          <ReactSelect
            label="Country"
            helpText="Select your country of residence"
            name="country"
          >
            <option value="">Select a country</option>
            <option value="us">United States</option>
            <option value="uk">United Kingdom</option>
            <option value="ca">Canada</option>
            <option value="au">Australia</option>
          </ReactSelect>
        </div>

        <div className="mbe3">
          <ReactSelect
            label="Preferred Language"
            required
            helpText="This field is required"
            name="language"
          >
            <option value="">Select a language</option>
            <option value="en">English</option>
            <option value="es">Spanish</option>
            <option value="fr">French</option>
            <option value="de">German</option>
          </ReactSelect>
        </div>

        <div className="mbe3">
          <ReactSelect
            label="Payment Method"
            required
            invalid
            errorMessage="Please select a payment method to continue"
            name="payment"
          >
            <option value="">Select payment method</option>
            <option value="credit">Credit Card</option>
            <option value="debit">Debit Card</option>
            <option value="paypal">PayPal</option>
          </ReactSelect>
        </div>
      </div>

      <div className="mbe4">
        <h2>Label Positioning</h2>
        <p className="mbs2 mbe3">
          Control label position with <code>labelPosition</code>: 'top' (default), 'start', 'end', or 'bottom'.
          <strong>Note:</strong> 'bottom' is not recommended as the dropdown will cover the label.
        </p>
      </div>
      <div className="mbe4" style={{ maxWidth: "600px" }}>
        <div className="mbe3">
          <ReactSelect
            label="Top Label (Default)"
            labelPosition="top"
            name="pos-top"
          >
            <option value="">Select option</option>
            <option value="1">Option 1</option>
            <option value="2">Option 2</option>
            <option value="3">Option 3</option>
          </ReactSelect>
        </div>

        <div className="mbe3">
          <ReactSelect
            label="Start Position"
            labelPosition="start"
            name="pos-start"
          >
            <option value="">Select option</option>
            <option value="1">Option 1</option>
            <option value="2">Option 2</option>
            <option value="3">Option 3</option>
          </ReactSelect>
        </div>

        <div className="mbe3">
          <ReactSelect
            label="End Position"
            labelPosition="end"
            name="pos-end"
          >
            <option value="">Select option</option>
            <option value="1">Option 1</option>
            <option value="2">Option 2</option>
            <option value="3">Option 3</option>
          </ReactSelect>
        </div>

        <div className="mbe3">
          <ReactSelect
            label="Bottom Position (Not Recommended)"
            labelPosition="bottom"
            name="pos-bottom"
            helpText="The dropdown will cover this label when opened"
          >
            <option value="">Select option</option>
            <option value="1">Option 1</option>
            <option value="2">Option 2</option>
            <option value="3">Option 3</option>
          </ReactSelect>
        </div>
      </div>

      <div className="mbe4">
        <h2>CSS Shadow Parts Customization</h2>
        <p className="mbs2 mbe3">
          Use CSS Shadow Parts to customize the select's appearance.
        </p>
      </div>
      <div className="mbe4" style={{ maxWidth: "400px" }}>
        <style dangerouslySetInnerHTML={{__html: `
          .custom-select::part(ag-select) {
            border: 2px solid var(--ag-primary);
            border-radius: var(--ag-radius-lg);
            font-weight: 500;
            background-color: var(--ag-background-secondary);
          }
          .custom-select::part(ag-select):focus {
            border-color: var(--ag-primary-dark);
            box-shadow: 0 0 0 3px var(--ag-blue-100);
          }
        `}} />
        <ReactSelect
          label="Custom Styled Select"
          name="custom"
          className="custom-select"
        >
          <option value="">Select a color</option>
          <option value="red">Red</option>
          <option value="blue">Blue</option>
          <option value="green">Green</option>
          <option value="purple">Purple</option>
        </ReactSelect>
      </div>
    </section>
  );
}
Open in StackBlitz

Usage

TIP

The framework examples below import AgnosticUI as an npm package. Alternatively, you can use the CLI for complete control, AI/LLM visibility, and full code ownership:

bash
npx ag init --framework FRAMEWORK # react, vue, lit, svelte, etc.
npx ag add Select

The CLI copies source code directly into your project, giving you full visibility and control. After running npx ag add, you'll receive exact import instructions.

Features

  • Native select element - Uses browser's built-in <select> with custom styling
  • Size variants - Small, default, and large sizes
  • Multiple selection - Support for native multiple select with size attribute
  • External label support - Built-in label, helper text, and error messages
  • Validation states - Required and invalid states with accessible error messages
  • Disabled state - Proper disabled styling and accessibility
  • Custom dropdown arrow - SVG arrow icon for single select mode
  • Accessibility - Maintains all native select accessibility features
  • Responsive - 100% width by default, adapts to container

Installation

sh
npm install agnosticui-core
sh
yarn add agnosticui-core
sh
pnpm add agnosticui-core
sh
bun add agnosticui-core

Component API

Props

PropTypeDefaultDescription
size'small' | 'large' | ''''Size variant of the select
multiplebooleanfalseEnable multiple selection
disabledbooleanfalseDisable the select
namestring''Form name attribute
multipleSizenumberundefinedNumber of visible options for multiple select
labelstring''External label text
labelPosition'top' | 'start' | 'end' | 'bottom''top'Position of the label relative to select. Note: 'bottom' is not recommended as the dropdown menu may cover the label
labelHiddenbooleanfalseVisually hides the label (still accessible to screen readers)
noLabelbooleanfalseRemoves the label element entirely
requiredbooleanfalseMarks the select as required (adds asterisk to label)
invalidbooleanfalseMarks the select as invalid (shows error state)
errorMessagestring''Error message displayed when invalid is true
helpTextstring''Helper text displayed below the select

Events

The Select component supports both custom and native events following the AgnosticUI v2 event conventions.

EventFrameworkDetailDescription
changeVue: @change
React: onChange
Lit: @change
{ value: string | string[] }Fired when the selected value changes. Custom event with detail payload.
focusVue: @focus
React: onFocus
Lit: @focus
FocusEventFired when select receives focus. Native event, re-dispatched from host.
blurVue: @blur
React: onBlur
Lit: @blur
FocusEventFired when select loses focus. Native event, re-dispatched from host.
clickVue: @click
React: onClick
Lit: @click
MouseEventFired when select is clicked. Native event.

Slots

SlotDescription
defaultOption elements

CSS Shadow Parts

PartDescription
ag-selectThe select element itself

Accessibility

The Select component maintains all native select accessibility features:

  • Semantic HTML - Uses native <select> element
  • Label association - Supports proper id/for label relationships (both external labels and manual labels)
  • ARIA relationships - Automatically links helper text and error messages via aria-describedby
  • Required fields - Visual asterisk indicator and proper required attribute
  • Error states - aria-invalid attribute and associated error messages
  • Keyboard navigation - Full keyboard support (arrow keys, Enter, Space, Escape)
  • Screen reader support - Native screen reader announcements with enhanced context from helper/error text
  • Focus management - Clear focus indicator with high contrast mode support
  • Disabled state - Proper disabled and aria-disabled attributes

Customization

You can customize the Select component using CSS Shadow Parts:

css
ag-select::part(ag-select) {
  border: 2px solid blue;
  border-radius: 8px;
  font-size: 1.2rem;
}

ag-select::part(ag-select):focus {
  border-color: darkblue;
  box-shadow: 0 0 0 3px rgba(0, 0, 255, 0.3);
}

Design Tokens

The Select component uses the following design tokens:

  • --ag-space-* - Padding and spacing
  • --ag-font-size-* - Font sizes for different sizes
  • --ag-text-primary - Text color
  • --ag-border - Border color
  • --ag-radius-md - Border radius
  • --ag-focus-ring-color - Focus ring color
  • --ag-disabled-bg - Disabled background
  • --ag-transition-fast - Transition timing

Best Practices

  1. Always provide a label - Use visible labels for better accessibility
  2. Include a default option - Provide a placeholder/prompt option with empty value
  3. Use meaningful option text - Clear, descriptive option labels
  4. Consider multiple select - For selecting multiple items, set multiple and multipleSize
  5. Handle change events - Process selection changes appropriately
  6. Disable when needed - Use disabled for unavailable states
  7. Size appropriately - Use size prop for visual hierarchy
  • Input - For text input
  • Radio - For single selection from visible options
  • Checkbox - For multiple selections with checkboxes