View as Markdown
Native Select
A styled native HTML select element with consistent design system integration.
100%
Loading...
<twig:NativeSelect>
<twig:NativeSelect:Option value="">Select status</twig:NativeSelect:Option>
<twig:NativeSelect:Option value="todo">Todo</twig:NativeSelect:Option>
<twig:NativeSelect:Option value="in-progress">In Progress</twig:NativeSelect:Option>
<twig:NativeSelect:Option value="done">Done</twig:NativeSelect:Option>
<twig:NativeSelect:Option value="cancelled">Cancelled</twig:NativeSelect:Option>
</twig:NativeSelect>
Installation
Note
Available since UX Toolkit 3.5.
php bin/console ux:install native-select --kit shadcn
Install the following Composer dependencies:
composer require twig/html-extra:^3.24.0 symfony/ux-icons symfony/ux-twig-component:^3.5 tales-from-a-dev/twig-tailwind-extra:^1.3.0
Install the following front-end dependencies:
npm install shadcn tw-animate-css
# ...or with Symfony AssetMapper
php bin/console importmap:require tw-animate-css/dist/tw-animate.css shadcn/dist/tailwind.css
Copy the following file(s) into your app:
templates/components/NativeSelect.html.twig
{%- props
## 'default'|'sm' The select size.
size = 'default'
-%}
<div
data-slot="native-select-wrapper"
data-size="{{ size }}"
class="{{ ('group/native-select relative w-fit has-[select:disabled]:opacity-50 ' ~ attributes.render('class'))|tailwind_merge }}"
>
<select
data-slot="native-select"
data-size="{{ size }}"
class="h-8 w-full min-w-0 appearance-none rounded-lg border border-input bg-transparent py-1 ps-2.5 pe-8 text-sm transition-colors outline-none select-none selection:bg-primary selection:text-primary-foreground placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:pointer-events-none disabled:cursor-not-allowed aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 data-[size=sm]:h-7 data-[size=sm]:rounded-[min(var(--radius-md),10px)] data-[size=sm]:py-0.5 dark:bg-input/30 dark:hover:bg-input/50 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 dark:[color-scheme:dark]"
{{ attributes.without('class') }}
>
{##- The select options, typically `NativeSelect:Option` and `NativeSelect:OptGroup` components. -#}
{%- block content %}{% endblock -%}
</select>
<twig:ux:icon name="lucide:chevron-down" class="pointer-events-none absolute top-1/2 end-2.5 size-4 -translate-y-1/2 text-muted-foreground select-none" aria-hidden="true" data-slot="native-select-icon" />
</div>
templates/components/NativeSelect/OptGroup.html.twig
<optgroup data-slot="native-select-optgroup" {{ attributes.defaults({class: 'bg-[Canvas] text-[CanvasText]'|tailwind_classes}) }}>
{##- The `NativeSelect:Option` components in this group. -#}
{%- block content %}{% endblock -%}
</optgroup>
templates/components/NativeSelect/Option.html.twig
<option data-slot="native-select-option" {{ attributes.defaults({class: 'bg-[Canvas] text-[CanvasText]'|tailwind_classes}) }}>
{##- The option label. -#}
{%- block content %}{% endblock -%}
</option>
Usage
<twig:NativeSelect>
<twig:NativeSelect:Option value="">Select a fruit</twig:NativeSelect:Option>
<twig:NativeSelect:Option value="apple">Apple</twig:NativeSelect:Option>
<twig:NativeSelect:Option value="banana">Banana</twig:NativeSelect:Option>
<twig:NativeSelect:Option value="blueberry">Blueberry</twig:NativeSelect:Option>
</twig:NativeSelect>
Examples
Groups
Use NativeSelect:OptGroup to organize options into labeled groups.
100%
Loading...
<twig:NativeSelect>
<twig:NativeSelect:Option value="">Select department</twig:NativeSelect:Option>
<twig:NativeSelect:OptGroup label="Engineering">
<twig:NativeSelect:Option value="frontend">Frontend</twig:NativeSelect:Option>
<twig:NativeSelect:Option value="backend">Backend</twig:NativeSelect:Option>
<twig:NativeSelect:Option value="devops">DevOps</twig:NativeSelect:Option>
</twig:NativeSelect:OptGroup>
<twig:NativeSelect:OptGroup label="Sales">
<twig:NativeSelect:Option value="sales-rep">Sales Rep</twig:NativeSelect:Option>
<twig:NativeSelect:Option value="account-manager">Account Manager</twig:NativeSelect:Option>
<twig:NativeSelect:Option value="sales-director">Sales Director</twig:NativeSelect:Option>
</twig:NativeSelect:OptGroup>
<twig:NativeSelect:OptGroup label="Operations">
<twig:NativeSelect:Option value="support">Customer Support</twig:NativeSelect:Option>
<twig:NativeSelect:Option value="product-manager">Product Manager</twig:NativeSelect:Option>
<twig:NativeSelect:Option value="ops-manager">Operations Manager</twig:NativeSelect:Option>
</twig:NativeSelect:OptGroup>
</twig:NativeSelect>
Disabled
100%
Loading...
<twig:NativeSelect disabled>
<twig:NativeSelect:Option value="">Disabled</twig:NativeSelect:Option>
<twig:NativeSelect:Option value="apple">Apple</twig:NativeSelect:Option>
<twig:NativeSelect:Option value="banana">Banana</twig:NativeSelect:Option>
<twig:NativeSelect:Option value="blueberry">Blueberry</twig:NativeSelect:Option>
</twig:NativeSelect>
Invalid
Set aria-invalid="true" to display the error state.
100%
Loading...
<twig:NativeSelect aria-invalid="true">
<twig:NativeSelect:Option value="">Error state</twig:NativeSelect:Option>
<twig:NativeSelect:Option value="apple">Apple</twig:NativeSelect:Option>
<twig:NativeSelect:Option value="banana">Banana</twig:NativeSelect:Option>
<twig:NativeSelect:Option value="blueberry">Blueberry</twig:NativeSelect:Option>
</twig:NativeSelect>
RTL
To enable RTL support, set the dir="rtl" attribute on the root element.
100%
Loading...
<div class="flex flex-col items-center gap-4">
{# Arabic #}
<div dir="rtl">
<twig:NativeSelect>
<twig:NativeSelect:Option value="">اختر الحالة</twig:NativeSelect:Option>
<twig:NativeSelect:Option value="todo">مهام</twig:NativeSelect:Option>
<twig:NativeSelect:Option value="in-progress">قيد التنفيذ</twig:NativeSelect:Option>
<twig:NativeSelect:Option value="done">منجز</twig:NativeSelect:Option>
<twig:NativeSelect:Option value="cancelled">ملغي</twig:NativeSelect:Option>
</twig:NativeSelect>
</div>
{# Hebrew #}
<div dir="rtl">
<twig:NativeSelect>
<twig:NativeSelect:Option value="">בחר סטטוס</twig:NativeSelect:Option>
<twig:NativeSelect:Option value="todo">לעשות</twig:NativeSelect:Option>
<twig:NativeSelect:Option value="in-progress">בתהליך</twig:NativeSelect:Option>
<twig:NativeSelect:Option value="done">הושלם</twig:NativeSelect:Option>
<twig:NativeSelect:Option value="cancelled">בוטל</twig:NativeSelect:Option>
</twig:NativeSelect>
</div>
</div>
Accessibility
NativeSelectrenders a real<select>, so it keeps the platform's own picker, keyboard handling and type-ahead. This is the most accessible way to present a list of options.- Give it a visible
Labelbound withforandid. The first option is not a label, and a placeholder option should bedisabledso it cannot be chosen. - The chevron is drawn with
<twig:ux:icon>, which rendersaria-hidden="true", and it is not focusable, so it never sits between the user and the control. NativeSelect:OptGrouprenders a real<optgroup>, whoselabelis announced when moving into the group.- Set
aria-invalid="true"for the invalid styling, and pointaria-describedbyat the message explaining what is wrong. Prefer the nativerequiredattribute over a client-side check alone.
API Reference
<twig:NativeSelect>
| Prop | Type | Default |
|---|---|---|
size The select size.
|
'default'|'sm' |
'default' |
| Block | Description |
|---|---|
content |
The select options, typically NativeSelect:Option and NativeSelect:OptGroup components. |
<twig:NativeSelect:OptGroup>
| Block | Description |
|---|---|
content |
The NativeSelect:Option components in this group. |
<twig:NativeSelect:Option>
| Block | Description |
|---|---|
content |
The option label. |