For a styled select component, see the Select component.
<script lang="ts">
import * as NativeSelect from "$lib/components/ui/native-select/index.js";
</script>
<NativeSelect.Root>
<NativeSelect.Option value="">Select status</NativeSelect.Option>
<NativeSelect.Option value="todo">Todo</NativeSelect.Option>
<NativeSelect.Option value="in-progress">In Progress</NativeSelect.Option>
<NativeSelect.Option value="done">Done</NativeSelect.Option>
<NativeSelect.Option value="cancelled">Cancelled</NativeSelect.Option>
</NativeSelect.Root> Installation
pnpm dlx shadcn-svelte@latest add native-select Copy and paste the following code into your project.
import Root from './native-select.svelte';
import Option from './native-select-option.svelte';
import OptGroup from './native-select-opt-group.svelte';
export {
Root,
Option,
OptGroup,
Root as NativeSelect,
Option as NativeSelectOption,
OptGroup as NativeSelectOptGroup
};
<script lang="ts">
import type { HTMLOptgroupAttributes } from 'svelte/elements';
import { cn, type WithElementRef } from '$UTILS$.js';
let {
ref = $bindable(null),
class: className,
children,
...restProps
}: WithElementRef<HTMLOptgroupAttributes> = $props();
</script>
<optgroup
bind:this={ref}
data-slot="native-select-opt-group"
class={cn('bg-zinc-950 text-zinc-300', className)}
{...restProps}
>
{@render children?.()}
</optgroup>
<script lang="ts">
import type { HTMLOptionAttributes } from 'svelte/elements';
import { cn, type WithElementRef } from '$UTILS$.js';
let {
ref = $bindable(null),
class: className,
children,
...restProps
}: WithElementRef<HTMLOptionAttributes> = $props();
</script>
<option
bind:this={ref}
data-slot="native-select-option"
class={cn('bg-zinc-950 text-zinc-50', className)}
{...restProps}
>
{@render children?.()}
</option>
<script lang="ts">
import { cn, type WithElementRef } from '$UTILS$.js';
import type { HTMLSelectAttributes } from 'svelte/elements';
import CaretDownIcon from 'phosphor-svelte/lib/CaretDown';
type NativeSelectProps = Omit<WithElementRef<HTMLSelectAttributes>, 'size'> & {
size?: 'sm' | 'default';
};
let {
ref = $bindable(null),
value = $bindable(),
class: className,
size = 'default',
children,
...restProps
}: NativeSelectProps = $props();
</script>
<div
class={cn(
'cn-native-select-wrapper group/native-select relative w-fit has-[select:disabled]:opacity-50',
className
)}
data-slot="native-select-wrapper"
data-size={size}
>
<select
bind:value
bind:this={ref}
data-slot="native-select"
data-size={size}
class="border-zinc-800 bg-zinc-900 text-zinc-50 placeholder:text-zinc-500 selection:bg-[#b9d765] selection:text-[#101207] focus-visible:border-zinc-300 focus-visible:ring-2 focus-visible:ring-zinc-300/25 aria-invalid:border-destructive aria-invalid:ring-2 aria-invalid:ring-destructive/20 dark:aria-invalid:border-destructive/50 h-9 w-full min-w-0 appearance-none rounded-none border py-2 pr-8 pl-3 text-sm transition-[color,border-color,box-shadow] select-none data-[size=sm]:h-8 outline-none disabled:pointer-events-none disabled:cursor-not-allowed"
{...restProps}
>
{@render children?.()}
</select>
<CaretDownIcon
class="text-zinc-500 top-1/2 right-3 size-3.5 -translate-y-1/2 pointer-events-none absolute select-none"
aria-hidden
data-slot="native-select-icon"
/>
</div>
Usage
<script lang="ts">
import * as NativeSelect from '$lib/components/ui/native-select/index.js';
</script> <NativeSelect.Root>
<NativeSelect.Option value="">Select a fruit</NativeSelect.Option>
<NativeSelect.Option value="apple">Apple</NativeSelect.Option>
<NativeSelect.Option value="banana">Banana</NativeSelect.Option>
<NativeSelect.Option value="blueberry">Blueberry</NativeSelect.Option>
<NativeSelect.Option value="grapes" disabled>Grapes</NativeSelect.Option>
<NativeSelect.Option value="pineapple">Pineapple</NativeSelect.Option>
</NativeSelect.Root> Examples
With Groups
Organize options using NativeSelect.OptGroup for better categorization.
<script lang="ts">
import * as NativeSelect from "$lib/components/ui/native-select/index.js";
</script>
<NativeSelect.Root>
<NativeSelect.Option value="">Select department</NativeSelect.Option>
<NativeSelect.OptGroup label="Engineering">
<NativeSelect.Option value="frontend">Frontend</NativeSelect.Option>
<NativeSelect.Option value="backend">Backend</NativeSelect.Option>
<NativeSelect.Option value="devops">DevOps</NativeSelect.Option>
</NativeSelect.OptGroup>
<NativeSelect.OptGroup label="Sales">
<NativeSelect.Option value="sales-rep">Sales Rep</NativeSelect.Option>
<NativeSelect.Option value="account-manager"
>Account Manager</NativeSelect.Option
>
<NativeSelect.Option value="sales-director"
>Sales Director</NativeSelect.Option
>
</NativeSelect.OptGroup>
<NativeSelect.OptGroup label="Operations">
<NativeSelect.Option value="support">Customer Support</NativeSelect.Option>
<NativeSelect.Option value="product-manager"
>Product Manager</NativeSelect.Option
>
<NativeSelect.Option value="ops-manager"
>Operations Manager</NativeSelect.Option
>
</NativeSelect.OptGroup>
</NativeSelect.Root> <NativeSelect.Root>
<NativeSelect.Option value="">Select a food</NativeSelect.Option>
<NativeSelect.OptGroup label="Fruits">
<NativeSelect.Option value="apple">Apple</NativeSelect.Option>
<NativeSelect.Option value="banana">Banana</NativeSelect.Option>
<NativeSelect.Option value="blueberry">Blueberry</NativeSelect.Option>
</NativeSelect.OptGroup>
<NativeSelect.OptGroup label="Vegetables">
<NativeSelect.Option value="carrot">Carrot</NativeSelect.Option>
<NativeSelect.Option value="broccoli">Broccoli</NativeSelect.Option>
<NativeSelect.Option value="spinach">Spinach</NativeSelect.Option>
</NativeSelect.OptGroup>
</NativeSelect.Root> Disabled State
Disable individual options or the entire select component.
<script lang="ts">
import * as NativeSelect from "$lib/components/ui/native-select/index.js";
</script>
<NativeSelect.Root disabled>
<NativeSelect.Option value="">Select priority</NativeSelect.Option>
<NativeSelect.Option value="low">Low</NativeSelect.Option>
<NativeSelect.Option value="medium">Medium</NativeSelect.Option>
<NativeSelect.Option value="high">High</NativeSelect.Option>
<NativeSelect.Option value="critical">Critical</NativeSelect.Option>
</NativeSelect.Root> Invalid State
Show validation errors with the aria-invalid attribute and error styling.
<script lang="ts">
import * as NativeSelect from "$lib/components/ui/native-select/index.js";
</script>
<NativeSelect.Root aria-invalid="true">
<NativeSelect.Option value="">Select role</NativeSelect.Option>
<NativeSelect.Option value="admin">Admin</NativeSelect.Option>
<NativeSelect.Option value="editor">Editor</NativeSelect.Option>
<NativeSelect.Option value="viewer">Viewer</NativeSelect.Option>
<NativeSelect.Option value="guest">Guest</NativeSelect.Option>
</NativeSelect.Root> <NativeSelect.Root aria-invalid="true">
<NativeSelect.Option value="">Select a country</NativeSelect.Option>
<NativeSelect.Option value="us">United States</NativeSelect.Option>
<NativeSelect.Option value="uk">United Kingdom</NativeSelect.Option>
<NativeSelect.Option value="ca">Canada</NativeSelect.Option>
</NativeSelect.Root> Native Select vs Select
- Use
NativeSelectwhen you need native browser behavior, better performance, or mobile-optimized dropdowns. - Use
Selectwhen you need custom styling, animations, or complex interactions.
The NativeSelect component provides native HTML select functionality with consistent styling that matches your design system.
Accessibility
- The component maintains all native HTML select accessibility features.
- Screen readers can navigate through options using arrow keys.
- The chevron icon is marked as
aria-hidden="true"to avoid duplication. - Use
aria-labeloraria-labelledbyfor additional context when needed.
<NativeSelect.Root aria-label="Choose your preferred language">
<NativeSelect.Option value="en">English</NativeSelect.Option>
<NativeSelect.Option value="es">Spanish</NativeSelect.Option>
<NativeSelect.Option value="fr">French</NativeSelect.Option>
</NativeSelect.Root> API Reference
NativeSelect.Root
The main select component that wraps the native HTML select element.
All other props are passed through to the underlying <select> element.
<NativeSelect.Root>
<NativeSelect.Option value="option1">Option 1</NativeSelect.Option>
<NativeSelect.Option value="option2">Option 2</NativeSelect.Option>
</NativeSelect.Root> NativeSelect.Option
Represents an individual option within the select.
All other props are passed through to the underlying <option> element.
<NativeSelect.Option value="apple">Apple</NativeSelect.Option>
<NativeSelect.Option value="banana" disabled>Banana</NativeSelect.Option> NativeSelect.OptGroup
Groups related options together for better organization.
All other props are passed through to the underlying <optgroup> element.
<NativeSelect.OptGroup label="Fruits">
<NativeSelect.Option value="apple">Apple</NativeSelect.Option>
<NativeSelect.Option value="banana">Banana</NativeSelect.Option>
</NativeSelect.OptGroup>