Select
Native HTML <select> styled to match the system, plus a Combobox-backed custom select pattern for polished dropdown menus.
Overview
The Select component is a core building block of the Byteion design system, providing a robust, accessibility-compliant interface for its specific UI capabilities.
Why this exists
Native select elements are notoriously hard to style consistently across browsers. Byteion provides a unified appearance that matches the rest of the form system while preserving native behavior (keyboard navigation, mobile picker).
Mental model
A select is a single-choice input with a predefined list. Use it when options are known and finite. If options exceed 10, consider a combobox instead.
When to use
When not to use
Basic example
The following example demonstrates standard usage, fully compliant with Byteion's token-first approach.
import { Select } from '@byteion/react';
const countries = [
{ value: 'us', label: 'United States' },
{ value: 'ca', label: 'Canada' },
{ value: 'uk', label: 'United Kingdom' },
{ value: 'de', label: 'Germany' },
];
export default function SelectDemo() {
return (
<div className="grid max-w-md gap-4 p-6">
<Select label="Country" placeholder="Select country" options={countries} />
<Select label="Plan" defaultValue="pro" options={[
{ value: 'free', label: 'Free' },
{ value: 'pro', label: 'Pro' },
{ value: 'enterprise', label: 'Enterprise' },
]} />
<Select label="Disabled" disabled options={countries} />
</div>
);
}
<script setup lang="ts">
import { ref } from 'vue';
import { Select } from '@byteion/vue';
const country = ref<string | null>(null);
const countries = [
{ value: 'us', label: 'United States' },
{ value: 'ca', label: 'Canada' },
{ value: 'uk', label: 'United Kingdom' },
{ value: 'de', label: 'Germany' },
];
</script>
<template>
<div class="grid max-w-md gap-4 p-6">
<Select v-model="country" label="Country" placeholder="Select country" :options="countries" />
<Select label="Plan" model-value="pro" :options="[
{ value: 'free', label: 'Free' },
{ value: 'pro', label: 'Pro' },
{ value: 'enterprise', label: 'Enterprise' },
]" />
<Select label="Disabled" disabled :options="countries" />
</div>
</template>
Variants
Each variant communicates a different meaning. Choose based on priority, not aesthetics.
- outline — outline style variant.
- filled — filled style variant.
Sizes
Size should match the importance and context of the component.
- sm — sm size.
- md — md size.
- lg — lg size.
States
This component reacts seamlessly to the following interaction and data states:
- default — Resting appearance before interaction.
- hover — Pointer is over the element — visual feedback via color or shadow.
- focus-visible — focus-visible state.
- disabled — Non-interactive — grayed out, cursor not-allowed.
- error — Validation failed — red border and error message.
- success — Validation passed — green indicator.
- invalid — Input value does not match expected format.
Anatomy
Usage guidelines
- Provide a default/placeholder option like"Select…".
- Keep options under 10 for usability.
- Use optgroup for categorized lists.
Do and Don't
Do
- Use a clear placeholder option.
- Order options logically (alphabetical or by frequency).
- Use optgroup for large lists.
Don't
- Do not exceed 10 options without providing search.
- Do not use selects for boolean choices.
- Do not disable the default browser picker on mobile.
Accessibility
Native selects inherit browser keyboard navigation. Label must be associated via for attribute.
Role:combobox
ARIA Attributes
aria-invalidaria-requiredaria-describedbyaria-label
Keyboard Navigation
- Tab: Moves focus into and out of the field
- Space / Enter / Down: Opens the option list
- Arrow keys: Navigate options
- Escape: Close without changing selection
Customization
Match input styling via the shared form token system.
Compose with React cva()
Use the exportedcva() +cn() utilities to compose your own variant on top of the Byteion tokens.
import { cva, cn, type VariantProps } from '@byteion/react';
const mySelect = cva('base classes…', {
variants: {
accent: { soft:'border-primary-200', loud:'border-primary-500' },
},
defaultVariants: { accent:'soft' },
});
type Props = React.HTMLAttributes<HTMLDivElement> & VariantProps<typeof mySelect>;
Compose with Vue cva()
Use the exportedcva() +cn() utilities inside standard Vue setup blocks.
<script setup lang="ts">
import { computed, useAttrs } from 'vue';
import { cva, type VariantProps } from '@byteion/vue';
interface Props { accent?:'soft' |'loud' }
const props = withDefaults(defineProps<Props>(), { accent:'soft' });
const attrs = useAttrs();
const mySelect = cva('base classes…', {
variants: { accent: { soft:'border-primary-200', loud:'border-primary-500' } },
});
const cls = computed(() => mySelect({ accent: props.accent, class: attrs.class as string | undefined }));
</script>
Runtime events
Byteion keeps framework APIs idiomatic: HTML uses DOM custom events, React uses callback props, and Vue uses emitted events / v-model updates.
HTML / JS events
No HTML runtime events are declared by this adapter.
React callbacks
No React callback events are declared by this adapter.
Vue emits
update:modelValue[value: string]
Remote / AJAX
This component does not implement Byteion JS remote loading. Use normal HTML form submission, framework state, or your application data layer around it.
API Reference
Native HTML select element. No custom data attributes needed.
HTML Data Attributes
No custom data attributes required.
React Props · SelectProps
Extracted directly fromreact/src/components/Select.tsx.
export interface SelectProps
extends Omit<SelectHTMLAttributes<HTMLSelectElement>, 'size' | 'children'>,
VariantProps<typeof selectStyles> {
label?: ReactNode;
helperText?: ReactNode;
error?: ReactNode;
success?: ReactNode;
/** Either pass `options` or use `children` for raw `<option>`/`<optgroup>` elements. */
options?: SelectOption[];
/** Placeholder shown when no value is selected. Renders as a disabled first option. */
placeholder?: string;
children?: ReactNode;
}
React Variant Schema
Internal variant composition defined viacva().
cva(
'block w-full appearance-none rounded-lg border bg-card pl-3.5 pr-9 text-sm font-medium text-foreground outline-none transition-colors duration-200 ease-out-soft shadow-field hover:shadow-field-hover focus-visible:shadow-field-focus dark:border-border dark:bg-input dark:text-foreground dark:shadow-field-dark dark:hover:shadow-field-dark-hover dark:focus-visible:shadow-field-focus-dark',
{
variants: {
variant: {
outline: 'border-border hover:border-border focus-visible:border-primary-400 dark:focus-visible:border-primary-500 dark:hover:border-border',
filled: 'border-border bg-muted/60 hover:border-border hover:bg-muted focus-visible:border-primary-400 dark:border-border dark:bg-input dark:hover:border-border dark:hover:bg-input dark:focus-visible:border-primary-500',
},
size: {
sm: 'h-9 rounded-md pl-3 pr-8 text-sm',
md: 'h-10 pl-3.5 pr-9 text-sm',
lg: 'h-11 pl-4 pr-10 text-base',
xl: 'h-12 rounded-xl pl-4 pr-10 text-base',
},
state: {
default: '',
error: '!border-danger-400 focus-visible:!shadow-field-error',
success: '!border-success-400 focus-visible:!shadow-field-success',
},
},
defaultVariants: { variant: 'outline', size: 'md', state: 'default' },
},
)
Vue Props · SelectProps
Extracted directly fromvue/src/components/Select.vue.
export interface SelectProps {
modelValue?: string;
defaultValue?: string;
options?: SelectOption[];
variant?: 'outline' | 'filled';
size?: 'sm' | 'md' | 'lg';
state?: 'default' | 'error' | 'success';
label?: string;
helperText?: string;
error?: string;
success?: string;
placeholder?: string;
required?: boolean;
id?: string;
}
Developer tips
Common mistakes
Production usage tips
data-bi-* attributes instead of styling hooks.
Premium examples
Advanced patterns available in Byteion Pro:
Related components
Unlock Premium Patterns
Get access to copy-paste ready Pro blocks, SaaS templates, and advanced data-tables built with this component.
Explore Pro Components