Select
Anatomy
Keyboard navigation keeps the active option visible in the actual scrollable surface. When an authored popup height cap makes the outer popup scroll instead of its inner list, the runtime follows that scroll owner and can switch back as the layout changes. Scaled surfaces convert visual distances to scroll distances.
Dropdown select component with support for flat option lists, grouped options, size variants, and invalid state. Native HTML <select>-equivalent with custom styling.
maud_ui::primitives::select
Props
Generated from public Rust fields. Defaults are evaluated from the implementation.
| Prop | Type | Default | Description |
|---|---|---|---|
name | String | "select" | No description |
id | String | "mui-select-826926202c439093-1968" | No description |
options | Vec<SelectOption> | [] | No description |
groups | Vec<SelectGroup> | [] | No description |
selected | Option<String> | None | No description |
placeholder | String | "Select…" | No description |
disabled | bool | false | No description |
size | Size | Default | No description |
aria_invalid | bool | false | No description |
Examples
Preferences
A realistic settings form with theme, language, and a locked timezone field.
Controls the application appearance.
Select Anatomy
Individual select states shown in isolation.
Pre-selected
Placeholder
Disabled option
Fully disabled
Small size
Invalid state
Grouped options
Options organized under labeled groups with separator helper.
Usage and composition
Import
use maud_ui::primitives::select::{self, Props, Size, SelectOption, SelectGroup};
Example
use maud::html;
use maud_ui::primitives::select;
html! {
(select::render(select::Props {
name: "theme".to_string(),
id: "select-theme".to_string(),
options: vec![
select::SelectOption { value: "light".to_string(), label: "Light".to_string(), disabled: false },
select::SelectOption { value: "dark".to_string(), label: "Dark".to_string(), disabled: false },
],
selected: Some("light".to_string()),
placeholder: "Choose theme…".to_string(),
size: select::Size::Default,
aria_invalid: false,
..Default::default()
}))
}
SelectOption Struct
| Field | Type | Description |
|---|---|---|
| value | String | Option value (sent on form submit). |
| label | String | Visible option label. |
| disabled | bool | If true, option cannot be selected (grayed out). |
SelectGroup Struct
| Field | Type | Description |
|---|---|---|
| label | String | Group heading (rendered as aria-labelledby). |
| options | Vec | Options within this group. |
Size Enum
| Value | Class | Description |
|---|---|---|
| Default | mui-select__trigger | Default size. |
| Sm | mui-select__trigger mui-select--sm | Small, compact size. |
Helpers
scroll_up_button() -> Markup
Renders the up-scroll affordance for long dropdowns (▲). Decorative, aria-hidden="true".
(select::scroll_up_button())
scroll_down_button() -> Markup
Renders the down-scroll affordance for long dropdowns (▼). Decorative, aria-hidden="true".
(select::scroll_down_button())
separator() -> Markup
Renders a thin horizontal rule (<div role="separator">) between option groups.
(select::separator())
Usage Patterns
Flat option list
html! {
(select::render(select::Props {
name: "priority".to_string(),
id: "select-priority".to_string(),
options: vec![
select::SelectOption { value: "low".to_string(), label: "Low".to_string(), disabled: false },
select::SelectOption { value: "medium".to_string(), label: "Medium".to_string(), disabled: false },
select::SelectOption { value: "high".to_string(), label: "High".to_string(), disabled: false },
],
placeholder: "Set priority…".to_string(),
..Default::default()
}))
}
Grouped options
html! {
(select::render(select::Props {
name: "timezone".to_string(),
id: "select-tz".to_string(),
groups: vec![
select::SelectGroup {
label: "North America".to_string(),
options: vec![
select::SelectOption { value: "est".to_string(), label: "Eastern".to_string(), disabled: false },
select::SelectOption { value: "cst".to_string(), label: "Central".to_string(), disabled: false },
],
},
select::SelectGroup {
label: "Europe".to_string(),
options: vec![
select::SelectOption { value: "gmt".to_string(), label: "London (GMT)".to_string(), disabled: false },
],
},
],
selected: Some("est".to_string()),
placeholder: "Select timezone…".to_string(),
..Default::default()
}))
}
With error state
html! {
(select::render(select::Props {
name: "country".to_string(),
id: "select-country".to_string(),
options: vec![
select::SelectOption { value: "us".to_string(), label: "United States".to_string(), disabled: false },
select::SelectOption { value: "ca".to_string(), label: "Canada".to_string(), disabled: false },
],
placeholder: "Select country…".to_string(),
aria_invalid: true, // Shows error styling
..Default::default()
}))
}
Nova derives popup CSS dimensions in the popup’s own layout units when its ancestor scale differs from the trigger. Aligned placement uses the layout viewport; keyboard opening scrolls the active option into view before focusing it, including a zoomed visual viewport.
Data Attributes
data-mui="select"— root element marker.data-name— input name attribute (helps with form binding).data-value— on each option (client JS uses for selection).
Hidden Input
A <input type="hidden"> carries the current selection value so the form submit includes the selected value.
Related
Combobox (searchable), Native Select (native HTML), Radio Group (mutually exclusive without dropdown).
Shadcn reference
https://ui.shadcn.com/docs/components/base/select
Accessibility
- Trigger is
role="combobox"witharia-expanded,aria-haspopup="listbox",aria-controls. - Dropdown is
role="listbox"witharia-labelledbypointing to trigger. - Options are
role="option"witharia-selected(true/false) and optionalaria-disabled. - Option groups are
role="group"witharia-labelledbypointing to group label. - Selected label is read from matched option; fallback to placeholder if none selected.
- Keyboard: Up/Down arrows move focus, Enter selects, Escape closes.