Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Input Elements

Overview

<input> is a void element — it cannot have children and is configured entirely through attributes. Both <input type="text"> and <input type="text" /> (self-closing) are accepted.

The type attribute selects the widget kind. If omitted, type="text" is assumed. Supported types:

CategoryTypes
Text-basedtext, password, search, email, url, tel, number, date, datetime-local, time, month, week, color
Booleancheckbox, radio
Numericrange
Filefile
Buttonsubmit, button, reset
Hiddenhidden

Text-based Types

All text-based types render a full-featured text field with cursor movement, selection, keyboard editing, clipboard (Ctrl+A/C/V/X), and IME (CJK composition) support.

#![allow(unused)]
fn main() {
view! {
    <input
        type="text"
        placeholder="Name"
        on:input={move |v: &str, cx: &mut App| set_name.set(cx, v.to_string())}
    />
}
}

Supported attributes

AttributeApplies toDescription
valueAll text-basedInitial/current value (string).
placeholderAll text-basedPlaceholder text.
disabledAll text-basedDisables input.
readonlyAll text-basedRead-only mode.
minnumberMinimum numeric value (f64).
maxnumberMaximum numeric value (f64).
stepnumberStep increment (f64).
on:inputAll text-basedFires on every keystroke. FnMut(&str, &mut App).
on:changeAll text-basedFires on Enter/blur. FnMut(&str, &mut App).
styleAll text-basedCSS-in-Rust styles.
classAll text-basedTailwind classes.
idAll text-basedElement id (for <label for=>).
requiredAll text-basedFails validation when empty.
patternAll text-basedLiteral match or * wildcard (foo*, *bar). Not JS RegExp.
minlengthAll text-basedMinimum character count (usize).
maxlengthAll text-basedMaximum character count (usize).
listAll text-basedDatalist id for autocomplete suggestions.

Type-specific placeholder defaults

When no placeholder is specified, each type provides a format-appropriate default:

TypeDefault placeholder
text(empty)
password(empty)
searchSearch…
emailemail@example.com
urlhttps://example.com
tel+1 (555) 000-0000
number0
dateYYYY-MM-DD
datetime-localYYYY-MM-DDTHH:MM
timeHH:MM
monthYYYY-MM
weekYYYY-Www
color#RRGGBB

Note: date, datetime-local, time, month, and week render a calendar popup. color renders a preset color palette popup. tel is text-entry with format validation only.

Validation

Text-based inputs check constraint rules on every render and turn the border red (hsla(0, 0.8, 0.5, 1)) when the value is invalid. Validation does not block on:input or on:change — it is visual feedback only.

Rules applied (in order):

  1. required — value must not be empty.
  2. minlength / maxlength — character count bounds.
  3. pattern — exact literal match or a single * wildcard prefix/suffix (foo*, *bar, foo). This is not a JavaScript RegExp.
  4. Type-specific checks:
    • email — exactly one @ with non-empty local and domain parts.
    • url — must start with http:// or https://.
    • number — must parse as f64 and satisfy min/max if set.

Color picker

<input type="color"> displays a 24×full-height color swatch on the right side of the input, showing the current #RRGGBB value (gray on parse failure). Clicking the swatch opens an 8×3 preset palette of 24 colors. Clicking a preset writes the uppercase #RRGGBB value, closes the popup, and fires on:input + on:change. Free RGB sliders are not provided.

Checkbox & Radio

#![allow(unused)]
fn main() {
view! {
    <input type="checkbox"
        checked={done.get()}
        on:change={move |v: bool, cx: &mut App| set_done.set(cx, v)}
    />
}
}
#![allow(unused)]
fn main() {
view! {
    <input type="radio"
        checked={sel.get() == 0}
        on:change={move |_v: bool, cx: &mut App| set_sel.set(cx, 0)}
    />
}
}

Attributes

AttributeTypeDescription
checkedboolChecked state.
disabledboolDisables input.
on:changeFnMut(bool, &mut App)Fires on click. New checked state.
style/class/hover/active/focus/id/tabindexStandard styling attributes.

The checkbox renders as an 18×18px rounded box with a checkmark (✓) when checked. The radio renders as a similar box (radio-specific styling is planned).

Radio Groups (Roving Tabindex)

Wrap radios in a <radiogroup> to enable roving tabindex:

#![allow(unused)]
fn main() {
view! {
    <radiogroup>
        <input type="radio" checked={sel.get() == 0}
            on:change={move |_, cx| set_sel.set(cx, 0)} />
        <input type="radio" checked={sel.get() == 1}
            on:change={move |_, cx| set_sel.set(cx, 1)} />
        <input type="radio" checked={sel.get() == 2}
            on:change={move |_, cx| set_sel.set(cx, 2)} />
    </radiogroup>
}
}

Inside <radiogroup>:

  • Only the checked radio is a tab stop (Tab moves to it, then Tab again leaves the group).
  • Arrow keys (←/↑/→/↓) move focus between radios in the group.
  • Clicking a radio focuses it and fires on:change.

<radiogroup> takes no attributes. Radios outside a <radiogroup> behave as standalone focusable elements (the checked radio is a tab stop).

Range Slider

#![allow(unused)]
fn main() {
view! {
    <input type="range"
        min={0.0f64}
        max={100.0f64}
        step={1.0f64}
        value={vol.get()}
        on:change={move |v: f64, cx: &mut App| set_vol.set(cx, v)}
    />
}
}

Attributes

AttributeTypeDefaultDescription
minf640.0Minimum value.
maxf64100.0Maximum value.
stepf641.0Step increment.
valuef640.0Current value.
disabledboolfalseDisables the slider.
on:changeFnMut(f64, &mut App)Fires on drag. New value.
style/class/id/tabindexStandard styling.

The range slider is a persistent gpui entity — its drag state survives re-renders via the reactive scope slot mechanism.

File Picker

#![allow(unused)]
fn main() {
view! {
    <input type="file"
        value="Browse..."
        multiple={true}
        on:change={move |paths: Vec<std::path::PathBuf>, _cx: &mut App| {
            eprintln!("selected: {:?}", paths);
        }}
    />
}
}

Attributes

AttributeTypeDescription
valuestringButton label text.
multipleboolAllow multiple file selection.
on:changeFnMut(Vec<PathBuf>, &mut App)Fires on file selection.
style/class/hover/active/focus/id/tabindexStandard styling.

accept and name are accepted but unused in v1.

Select

<select> renders a dropdown with a popover option list. The options attribute is a Vec<(String, String)> of value-label pairs. The popover width always matches the trigger width; clicking the trigger toggles the popover, clicking an option fires on:change and closes it, and clicking outside or pressing Escape closes it.

#![allow(unused)]
fn main() {
view! {
    <select
        options={vec![
            ("1".to_string(), "One".to_string()),
            ("2".to_string(), "Two".to_string()),
        ]}
        value={sel.get()}
        on:change={move |v: &str, cx: &mut App| set_sel.set(cx, v.to_string())}
    />
}
}

Custom option content

To render rich content per option (icons, colored values, multi-line rows), pass a single closure child. The closure receives (value: &str, label: &str) and returns an element via view! {}. The same closure renders both the popover rows and the trigger’s display of the selected option.

#![allow(unused)]
fn main() {
view! {
    <select
        options={vec![
            ("1".to_string(), "One".to_string()),
            ("2".to_string(), "Two".to_string()),
        ]}
        value={sel.get()}
        on:change={move |v: &str, cx: &mut App| set_sel.set(cx, v.to_string())}
    >
        {move |value: &str, label: &str| view! {
            <div class="flex items-center gap-2">
                <span class="text-[#0f0]">{value.to_string()}</span>
                <span>{label.to_string()}</span>
            </div>
        }}
    </select>
}
}

When no child closure is given, each option renders as plain label text.

Attributes

AttributeTypeDescription
optionsVec<(String, String)>Value-label pairs (flat list).
groupsVec<(String, Vec<(String, String)>)>Grouped options; group name + value-label pairs. When non-empty, the popover renders by group.
valueStringCurrently selected value. In multiple mode, comma-separated values (a,b).
multipleboolEnables multi-select. Default false.
disabledboolDisables the select.
on:changeFnMut(&str, &mut App)Fires on selection change. In multiple mode, passes the comma-separated string.
style/class/idStandard styling.

Multiple selection

When multiple={true}, clicking an option toggles that value in the comma-separated value string. The popover stays open after each toggle (click outside or press Escape to close). The trigger text shows the selected labels joined by ", " (empty if none).

#![allow(unused)]
fn main() {
view! {
    <select
        multiple={true}
        options={vec![
            ("a".to_string(), "Alpha".to_string()),
            ("b".to_string(), "Beta".to_string()),
        ]}
        value={sel.get()}
        on:change={move |v: &str, cx: &mut App| set_sel.set(cx, v.to_string())}
    />
}
}

Grouped options

Use groups instead of options to render options under non-clickable bold group headers. Each group is (group_name, Vec<(value, label)>). When groups is non-empty, the popover renders by group; flat options are ignored for rendering but still used for label lookups.

#![allow(unused)]
fn main() {
view! {
    <select
        groups={vec![
            ("Fruits".to_string(), vec![
                ("apple".to_string(), "Apple".to_string()),
                ("banana".to_string(), "Banana".to_string()),
            ]),
            ("Vegetables".to_string(), vec![
                ("carrot".to_string(), "Carrot".to_string()),
            ]),
        ]}
        value={sel.get()}
        on:change={move |v: &str, cx: &mut App| set_sel.set(cx, v.to_string())}
    />
}
}

<select> does not read <option> or <optgroup> child nodes; use the options or groups prop instead.

Child closure signature

The optional child is a closure Fn(&str, &str) -> impl IntoElement. The first argument is the option’s value, the second is its label. The closure must be Fn (not FnMut) because it is invoked from multiple render sites.

Datalist

<datalist> provides autocomplete suggestions for text inputs. It renders nothing but registers a list of options under an id. A text input with a matching list=<id> attribute shows prefix-matched suggestions (up to 8) below the input when focused and the value is non-empty. Clicking a suggestion writes it to the input and fires on:input + on:change.

#![allow(unused)]
fn main() {
view! {
    <datalist id="cities" options={vec!["Paris".to_string(), "London".to_string()]} />
    <input type="text" list="cities" on:input={move |v: &str, cx: &mut App| {}} />
}
}

<datalist> requires an id attribute and accepts options={Vec<String>}. It does not read <option> child nodes; use the options prop.

Output

<output> is a pure <div> alias — a generic container with no special behavior. Use it to display computed results.

Textarea

<textarea> is a void element (no children) configured through attributes:

#![allow(unused)]
fn main() {
view! {
    <textarea
        placeholder="Enter text"
        value={text.get()}
        on:input={move |v: &str, cx: &mut App| set_text.set(cx, v.to_string())}
    />
}
}

Attributes

AttributeTypeDescription
valueStringCurrent content.
placeholderstringPlaceholder text.
disabledboolDisables editing.
readonlyboolRead-only mode.
rowsu32Minimum visible line count.
on:inputFnMut(&str, &mut App)Fires on every keystroke.
on:changeFnMut(&str, &mut App)Fires on blur.
style/class/id/tabindexStandard styling.

name is accepted but unused. When rows is set, the textarea’s minimum height is rows times the line height plus 8px of vertical padding; without rows the minimum height is 80px. The textarea is a multi-line text input with the same cursor/selection/clipboard/IME support as text inputs.

Submit/Button/Reset

These render as clickable buttons (like <button>). The value attribute becomes the button label. on:click wires the handler. Without an explicit tabindex they default to tabindex={0} and are in Tab order.

#![allow(unused)]
fn main() {
view! {
    <input type="submit" value="Submit" on:click={click(move |_cx| {})} />
    <input type="button" value="Click" on:click={click(move |_cx| {})} />
    <input type="reset" value="Reset" on:click={click(move |_cx| {})} />
}
}

Note: Inside a <form>, submit and reset buttons automatically invoke the form’s on:submit / on:reset handler. An explicit on:click overrides this — the auto-bind is skipped to avoid double-firing.

Form

<form> wraps its children in a form context. on:submit and on:reset accept FnMut(&mut App) closures. Child <input type="submit"> and <input type="reset"> buttons auto-invoke the enclosing form’s handler; pressing Enter in a single-line text input also triggers on:submit.

#![allow(unused)]
fn main() {
view! {
    <form on:submit={move |cx| { /* ... */ }} on:reset={move |cx| { /* ... */ }}>
        <input type="text" required={true} />
        <input type="submit" value="Go" />
        <input type="reset" value="Clear" />
    </form>
}
}

When no <form> ancestor is present, submit/reset buttons are no-ops. <form> supports standard styling attributes (style, class, hover, active, focus, id, tabindex, ref) and event handlers.

Hidden

<input type="hidden"> renders nothing (gpui::Empty):

#![allow(unused)]
fn main() {
view! {
    <input type="hidden" value="invisible" />
}
}

Label

<label> associates text with an input for click-to-focus behavior. Two forms are supported:

Explicit for attribute

#![allow(unused)]
fn main() {
view! {
    <label for="username" class="text-sm">{"Username"}</label>
    <input type="text" id="username" placeholder="Enter username" />
}
}

Clicking the label focuses the input with id="username".

Wrapping label

#![allow(unused)]
fn main() {
view! {
    <label class="flex flex-col gap-1">
        <span class="text-sm">{"Wrapped input"}</span>
        <input type="text" placeholder="Click label to focus" />
    </label>
}
}

When <label> wraps an input, clicking anywhere on the label focuses the first focusable child input. This works with all input types: text-based inputs (text, password, email, …), textarea, range, checkbox, radio, file, and select.

<label> supports all standard styling attributes (style, class, hover, active, focus, id, tabindex) and event handlers.