Native Select
A styled wrapper around the real native select control.
Installation
Included in poetry-ui — available as
poetry_native_select
the moment you've installed Poetry,
with no per-component step. To own the source and edit it, copy it into your app:
bin/rails g poetry:add native-select
Default
<%# The labelled pair - a bare select has no accessible name. The fast
path is options: [[label, value], ...] pairs plus selected:. %>
<div class="grid w-80 gap-2">
<%= poetry_label(for_id: "native-select-sort") { "Sort by" } %>
<%= poetry_native_select(name: "sort", id: "native-select-sort",
options: [["Newest first", "newest"], ["Oldest first", "oldest"], ["Title", "title"]],
selected: "newest") %>
</div>
Grouped
<%# A content block overrides options: - compose real <option>/<optgroup>
elements; the helpers stamp the styled classes. %>
<div class="grid w-80 gap-2">
<%= poetry_label(for_id: "native-select-timezone") { "Timezone" } %>
<%= poetry_native_select(name: "timezone", id: "native-select-timezone") do %>
<%= poetry_native_select_optgroup(label: "Americas") do %>
<%= poetry_native_select_option(value: "america/new_york") { "New York" } %>
<%= poetry_native_select_option(value: "america/chicago") { "Chicago" } %>
<% end %>
<%= poetry_native_select_optgroup(label: "Europe") do %>
<%= poetry_native_select_option(value: "europe/london") { "London" } %>
<%= poetry_native_select_option(value: "europe/berlin") { "Berlin" } %>
<% end %>
<% end %>
</div>
Invalid
Choose a country to continue.
<%# invalid: true flags the control with aria-invalid and the data-invalid
attribute, so the wrapper and chevron pick up the error styling. Pair it
with a message the field can point at. %>
<div class="grid w-80 gap-2">
<%= poetry_label(for_id: "native-select-country") { "Billing country" } %>
<%= poetry_native_select(name: "country", id: "native-select-country",
invalid: true,
options: [["Select a country", ""], ["Canada", "ca"], ["Germany", "de"], ["Japan", "jp"]],
selected: "") %>
<p class="text-sm text-destructive">Choose a country to continue.</p>
</div>
Small disabled
<%# label: sets aria-label for label-less placements; disabled: dims the
whole wrapper, chevron included. %>
<div class="w-80">
<%= poetry_native_select(size: :sm, disabled: true, name: "plan", id: "native-select-plan",
label: "Plan", options: %w[Starter Team], selected: "Team") %>
</div>
API
Poetry::Ui::NativeSelect::Component — options are
constructor keywords (the poetry_* helper forwards them);
slots are composed inside the block. Generated from the gem's source documentation.
| Option | Type | Details | Description |
|---|---|---|---|
| described_by: | String | Space-separated hint/error ids wired to the SELECT itself - a raw aria-describedby in html_attributes would land on the wrapper div, unassociated for assistive technology. | |
| disabled: | Boolean | defaults to false |
Disables the native select; the wrapper dims the whole pair. |
| id: | String | The select's dom id - the seam a Label's for_id: points at. | |
| invalid: | Boolean | defaults to false |
Marks the select invalid (aria-invalid on the element itself). |
| label: | String | The accessible name for label-less placements (a visible Label paired via id:/for_id: is still the default pattern). | |
| name: | String | The submitted field name, forwarded to the native select. | |
| size: | Symbol | one of: default, sm; defaults to :default |
The control size axis; :sm suits dense toolbars and table rows. |
Styling
Every part carries a stable data-slot attribute — target
[data-slot=…] from your own CSS to restyle it. State rides
data attributes on the parts below. This contract is verified against rendered DOM in CI.
| Part | Description |
|---|---|
| [data-slot=native-select-wrapper] | Relative shell around the select and the chevron - dims the pair when the select is disabled |
| [data-slot=native-select] | The real <select> - appearance-none (the chevron replaces the native arrow); platform picker and form submission stay native |
| [data-slot=native-select-icon] | The decorative chevron wrapper - absolutely pinned, aria-hidden |
| [data-slot=native-select-option] | An <option> from the options: fast path (or poetry_native_select_option) - Canvas system colors keep the native dropdown legible |
State attributes
| Part | Attribute | Condition | Values |
|---|---|---|---|
| native-select-wrapper | data-size | always - the resolved size | default · sm |
| native-select | data-size | always - the resolved size (mirrors the wrapper) | default · sm |