Number Field
A numeric field with steppers, range and step.
use herogpui::components::number_field::{NumberField, NumberState};NumberField on docs.rsSource at v0.14.0
Usage
Showing Usage, live: HeroGPUI compiled to WebAssembly. The canvas has no accessibility tree; each example's description and code below carry the same information.
Runs in a view's render: self is the view holding this example's state and cx is its Context<Self>.
use herogpui::prelude::{NumberField, NumberState};
use herogpui::gpui::prelude::*;
use herogpui::gpui::px;
use herogpui::gpui;
gpui::div()
.flex()
.flex_col()
.w(px(256.))
.gap(px(12.))
.children(vec![NumberField::new(self.demo_number(
"nf-usage",
1024.,
0.,
4096.,
1.,
cx,
))
// v3's basic example seeds the field with `defaultValue`.
.default_value(1024.)
.min_value(0.)
.name("width")
.full_width(true)
.label("Width")
.into_any_element()])`height` resizes the group and the inner field together; `padding_x` moves the input's inset without touching the steppers; `is_bare` drops the group's paint. Runs in a view's render: self is the view holding this example's state and cx is its Context<Self>.
use herogpui::prelude::{NumberField, NumberState};
use herogpui::gpui::prelude::*;
use herogpui::gpui::px;
use herogpui::gpui;
gpui::div()
.flex()
.flex_col()
.w(px(256.))
.gap(px(12.))
.children(vec![NumberField::new(
self.demo_number("nf-custom-box", 5., 0., 20., 1., cx),
)
.label("Compact")
.height(px(28.))
.padding_x(px(8.))
.is_bare(true)
.font_family(MONO_FONT)
.into_any_element()])Also calls MONO_FONT (included when you copy).
`group_hover_bg` names the fill the hovered group fades to, in place of `--field-hover`; a focused, invalid or disabled group keeps its own chrome. Runs in a view's render: self is the view holding this example's state and cx is its Context<Self>.
use herogpui::prelude::{ActiveTheme, NumberField, NumberState};
use herogpui::gpui::prelude::*;
use herogpui::gpui::px;
use herogpui::gpui;
gpui::div()
.flex()
.flex_col()
.w(px(256.))
.gap(px(12.))
.children(vec![NumberField::new(
self.demo_number("nf-hover-colour", 5., 0., 20., 1., cx),
)
.label("Quantity")
.group_hover_bg(cx.colors().accent.soft())
.into_any_element()])Runs in a view's render: self is the view holding this example's state and cx is its Context<Self>.
use herogpui::prelude::{NumberField, NumberState};
use herogpui::gpui::prelude::*;
use herogpui::gpui::px;
use herogpui::gpui;
gpui::div()
.flex()
.flex_col()
.items_start()
.justify_center()
.gap(px(12.))
.children(vec![NumberField::new(self.number.clone())
.label("Quantity")
.hide_steppers(true)
.into_any_element()])Runs in a view's render: self is the view holding this example's state and cx is its Context<Self>.
use herogpui::prelude::{NumberField, NumberFormat, NumberState};
use herogpui::gpui::prelude::*;
use herogpui::gpui::px;
use herogpui::gpui;
gpui::div()
.flex()
.flex_col()
.items_start()
.justify_center()
.gap(px(12.))
.children(vec![NumberField::new(self.price.clone())
.label("Price")
.format_options(NumberFormat::currency("USD"))
.into_any_element()])Runs in a view's render: self is the view holding this example's state and cx is its Context<Self>.
use herogpui::prelude::{FieldVariant, NumberField, NumberState};
use herogpui::gpui::prelude::*;
use herogpui::gpui::px;
use herogpui::gpui;
gpui::div()
.flex()
.flex_col()
.items_start()
.justify_center()
.gap(px(12.))
.children(vec![
NumberField::new(self.demo_number("nf-primary", 5., 0., 20., 1., cx))
.label("Primary")
.into_any_element(),
NumberField::new(self.demo_number("nf-secondary", 5., 0., 20., 1., cx))
.label("Secondary")
.variant(FieldVariant::Secondary)
.into_any_element(),
])Runs in a view's render: self is the view holding this example's state and cx is its Context<Self>.
use herogpui::prelude::{FieldVariant, NumberField, NumberState, Surface};
use herogpui::gpui::prelude::*;
use herogpui::gpui::px;
use herogpui::gpui;
gpui::div()
.flex()
.flex_col()
.items_start()
.justify_center()
.gap(px(12.))
.children(vec![Surface::new()
.padding(px(24.))
.gap(px(16.))
.child(
NumberField::new(self.demo_number(
"nf-surface",
2.,
0.,
10.,
1.,
cx,
))
.label("Seats")
.variant(FieldVariant::Secondary)
.description("The secondary variant, for use on a surface"),
)
.into_any_element()])Runs in a view's render: self is the view holding this example's state and cx is its Context<Self>.
use herogpui::prelude::{NumberField, NumberState};
use herogpui::gpui::prelude::*;
use herogpui::gpui::px;
use herogpui::gpui;
gpui::div()
.flex()
.flex_col()
.items_start()
.justify_center()
.gap(px(12.))
.children(vec![NumberField::new(
self.demo_number("nf-desc", 1., 0., 99., 1., cx),
)
.label("Quantity")
.description("How many licences to buy")
.into_any_element()])Runs in a view's render: self is the view holding this example's state and cx is its Context<Self>.
use herogpui::prelude::{NumberField, NumberState};
use herogpui::gpui::prelude::*;
use herogpui::gpui::px;
use herogpui::gpui;
gpui::div()
.flex()
.flex_col()
.items_start()
.justify_center()
.gap(px(12.))
.children(vec![NumberField::new(
self.demo_number("nf-req", 1., 0., 99., 1., cx),
)
.label("Quantity")
.is_required(true)
.into_any_element()])Runs in a view's render: self is the view holding this example's state and cx is its Context<Self>.
use herogpui::prelude::{NumberField, NumberState};
use herogpui::gpui::prelude::*;
use herogpui::gpui::px;
use herogpui::gpui;
gpui::div()
.flex()
.flex_col()
.items_start()
.justify_center()
.gap(px(12.))
.children(vec![NumberField::new(
self.demo_number("nf-dis", 8., 0., 99., 1., cx),
)
.label("Quantity")
.is_disabled(true)
.into_any_element()])Runs in a view's render: self is the view holding this example's state and cx is its Context<Self>.
use herogpui::prelude::{NumberField, NumberState};
use herogpui::gpui::prelude::*;
use herogpui::gpui::px;
use herogpui::gpui;
gpui::div()
.flex()
.flex_col()
.items_start()
.justify_center()
.gap(px(12.))
.children(vec![NumberField::new(
self.demo_number("nf-full", 3., 0., 99., 1., cx),
)
.label("Quantity")
.full_width(true)
.into_any_element()])Runs in a view's render: self is the view holding this example's state and cx is its Context<Self>.
use herogpui::prelude::{NumberField, NumberState};
use herogpui::gpui::prelude::*;
use herogpui::gpui::px;
use herogpui::gpui;
gpui::div()
.flex()
.flex_col()
.items_start()
.justify_center()
.gap(px(12.))
.children(vec![NumberField::new(self.demo_number(
"nf-invalid",
0.,
0.,
99.,
1.,
cx,
))
.label("Quantity")
// `minValue`/`maxValue` on the component, which is what
// clamps the steppers and the typed value.
.min_value(1.)
.max_value(99.)
.is_required(true)
.is_invalid(true)
.validation_errors(["Order at least one"])
.into_any_element()])Runs in a view's render: self is the view holding this example's state and cx is its Context<Self>.
use herogpui::prelude::{ActiveTheme, NumberField, NumberState};
use herogpui::gpui::prelude::*;
use herogpui::gpui::{App, px};
use herogpui::gpui;
let nf_controlled = self
.demo_number("nf-ctl", 5., 0., 20., 1., cx)
.read(cx)
.value();
gpui::div()
.flex()
.flex_col()
.items_start()
.justify_center()
.gap(px(12.))
.children(vec![
NumberField::new(self.demo_number("nf-ctl", 5., 0., 20., 1., cx))
.label("Quantity")
.on_change(cx.listener(|_, _v: &f64, _, cx| cx.notify()))
.into_any_element(),
para(&format!("Value: {nf_controlled}"), cx),
])Also calls para (included when you copy).
Runs in a view's render: self is the view holding this example's state and cx is its Context<Self>.
use herogpui::prelude::{NumberField, NumberState};
use herogpui::gpui::prelude::*;
use herogpui::gpui::px;
use herogpui::gpui;
gpui::div()
.flex()
.flex_col()
.items_start()
.justify_center()
.gap(px(12.))
.children(vec![
NumberField::new(self.demo_number("nf-step-5", 10., 0., 100., 5., cx))
.label("Step 5")
.into_any_element(),
NumberField::new(self.demo_number(
"nf-step-tenth",
1.5,
0.,
10.,
0.1,
cx,
))
.label("Step 0.1")
.into_any_element(),
])Runs in a view's render: self is the view holding this example's state and cx is its Context<Self>.
use herogpui::prelude::{Button, Form, FormField, NumberField, NumberState};
use herogpui::gpui::prelude::*;
use herogpui::gpui::px;
use herogpui::gpui;
gpui::div()
.flex()
.flex_col()
.items_start()
.justify_center()
.gap(px(12.))
.children(vec![{
let seats = self.demo_number("nf-form", 1., 1., 99., 1., cx);
Form::new()
.field(FormField::number(seats.clone()).name("seats"))
.child(
NumberField::new(seats)
.label("Seats")
.name("seats")
.is_required(true),
)
.child(Button::new("number-field-form-submit").label("Buy"))
.into_any_element()
}])Runs in a view's render: self is the view holding this example's state and cx is its Context<Self>.
use herogpui::prelude::{NumberField, NumberState};
use herogpui::gpui::prelude::*;
use herogpui::gpui::px;
use herogpui::gpui;
gpui::div()
.flex()
.flex_col()
.items_start()
.justify_center()
.gap(px(12.))
.children(vec![NumberField::new(self.demo_number(
"nf-validate",
200.,
0.,
1000.,
10.,
cx,
))
.label("Budget")
.description("At least 100")
.validate(|value| (*value < 100.).then(|| "Budget must be at least 100".into()))
.into_any_element()])Runs in a view's render: self is the view holding this example's state and cx is its Context<Self>.
use herogpui::prelude::{ActiveTheme, NumberField, NumberState, icons};
use herogpui::gpui::prelude::*;
use herogpui::gpui::px;
use herogpui::gpui;
gpui::div()
.flex()
.flex_col()
.items_start()
.justify_center()
.gap(px(12.))
.children(vec![NumberField::new(
self.demo_number("nf-icons", 1024., 0., 4096., 1., cx,)
)
.label("Width (Custom Icons)")
.description("Custom icon children")
.decrement_icon(gpui::svg()
.size(px(16.))
.path(icons::CHEVRON_LEFT)
.text_color(cx.colors().foreground)
.into_any_element())
.increment_icon(gpui::svg()
.size(px(16.))
.path(icons::CHEVRON_RIGHT)
.text_color(cx.colors().foreground)
.into_any_element())
.into_any_element()])Runs in a view's render: self is the view holding this example's state and cx is its Context<Self>.
use herogpui::prelude::{ActiveTheme, NumberField, NumberFormat, NumberState, icons};
use herogpui::gpui::prelude::*;
use herogpui::gpui::px;
use herogpui::gpui;
gpui::div()
.flex()
.flex_col()
.items_start()
.justify_center()
.gap(px(12.))
.children(vec![NumberField::new(
self.demo_number("nf-chev", 99., 0., 999., 1., cx),
)
.label("Amount")
.format_options(NumberFormat::currency("EUR"))
.vertical_steppers(true)
.increment_icon(
gpui::svg()
.size(px(11.))
.path(icons::CHEVRON_UP)
.text_color(cx.colors().foreground),
)
.decrement_icon(
gpui::svg()
.size(px(11.))
.path(icons::CHEVRON_DOWN)
.text_color(cx.colors().foreground),
)
.into_any_element()])Helpers used by these examples
Plain functions over the public API, defined once here and copied with each example that calls them.
const MONO_FONT: &str = if cfg!(target_arch = "wasm32") {
// The web shell bundles JetBrains Mono; the literal family "monospace"
// resolves to nothing there and panics text resolution.
"JetBrains Mono"
} else if cfg!(target_os = "macos") {
"Menlo"
} else {
"Consolas"
};fn para(text: &str, cx: &App) -> gpui::AnyElement {
let colors = cx.colors();
gpui::div()
.text_size(px(14.5))
.line_height(px(24.))
.text_color(colors.foreground)
.max_w(px(720.))
.child(text.to_owned())
.into_any_element()
}Anatomy
Rust types
Number Field is assembled from these builders. The API reference lists each one.
Customization
Shared appearance belongs on ThemeBuilder::components and .recipe("name"). Typed builders change how Number Field looks at one call site. Every builder also carries sx for placement, width and flex: it takes GPUI's own styling methods and refines them over the component's root after the theme, so an instance override wins. Behaviour builders live in the API reference below.
Styling
| Method | Values | Description |
|---|---|---|
full_width(bool) | true | false | Expands both the root and number group to the available width. |
variant(FieldVariant) | FieldVariant::Primary | FieldVariant::Secondary | Selects elevated or lower-emphasis field chrome. |
// The one slot for caller-owned low-level styling: GPUI's
// styling methods, refined over the root element last.
.sx(|el| {
el.bg(gpui::rgba(0xffa500ff)) // background
.text_color(gpui::rgba(0x000000ff)) // text
.w(gpui::px(13.)) // size
.h(gpui::px(12.))
})API reference
Against HeroUI v3.2.6: 29 entries ported, 20 partial, 6 not ported. Partial rows are marked in the tables; entries not ported are listed under each table with the reason.
Builders
| Builder | Values | Default | Description |
|---|---|---|---|
content(Partial | AnyElement | | — | Built-in field anatomy or replacement content receiving the complete resolved number-field state. |
full_width( | true | | false | Expands both the root and number group to the available width. |
focus_ring( | true | | true | GPUI extension: hides only the group's visual focus ring while retaining focus, editing and stepper behavior. |
new(Partial | string | — | The bound NumberState entity supplies stable GPUI identity rather than a element id. |
variant( | FieldVariant:: | FieldVariant:: | Selects elevated or lower-emphasis field chrome. |
default_value( | number | — | Seeds the uncontrolled NumberState once before render content observes it. |
on_change(Partial | Fn( | — | Reports each changed numeric value; the local state cannot represent undefined empty value. |
format_options(Partial | Intl. | — | Supports decimal, currency and percentage formatting without the complete Intl option surface. |
is_required( | true | | false | Marks the label and live FormField required and blocks invalid native submission. |
is_invalid( | true | | false | Forces invalid chrome and validation reporting. |
validate(Partial | Fn( | — | Custom validation returns one optional GPUI message. |
validation_behavior( | ValidationBehavior:: | ValidationBehavior:: | Chooses blocking native or non-blocking allow behavior. |
validation_errors( | string[] | — | Server messages take precedence over the custom validator. |
min_value( | number | — | Inclusive lower bound for typing, keys, wheel input and steppers. |
max_value( | number | — | Inclusive upper bound for typing, keys, wheel input and steppers. |
step( | number | 1 | Positive finite increment lattice shared by every input path. |
is_disabled( | true | | false | Dims the whole field, removes interaction and omits form data. |
is_read_only( | true | | false | Keeps the field focusable while blocking text, key, wheel and stepper edits. |
name( | string | — | Registers the current number under this form field name. |
auto_focus( | true | | false | Focuses the numeric input once, including when replacement content is rendered. |
vertical_steppers(Partial | true | | — | Builds the group monolithically, including either horizontal buttons or the documented vertical-chevron composition. |
increment_icon( | AnyElement | < | Replaces the increment glyph while preserving the button's spin behavior. |
decrement_icon( | AnyElement | < | Replaces the decrement glyph while preserving the button's spin behavior. |
Not ported6 HeroUI entries not ported
| HeroUI v3 | Why it is not ported |
|---|---|
NumberField.className | Browser styles are unavailable. |
NumberField.style | Browser inline styles are unavailable. |
NumberField.render | Root-element substitution has no GPUI equivalent. |
NumberField.locale | Locale-sensitive separators, digit systems and currency placement require CLDR data not bundled by this port. |
NumberField.aria-label / aria-labelledby / aria-describedby / aria-details | Gpui-pre 0.3.3 exposes no accessibility attribute tree. |
NumberField.Input.className | Arbitrary input classes are unavailable. |
Parts
| Part | Description |
|---|---|
NumberField | Root stack owning numeric state, validation, formatting, focus and form registration. |
NumberFieldPartial | Unified 36px field shell; composition is selected through typed builders rather than arbitrary children. |
NumberFieldPartial | Inner Input bound to NumberState and stripped of independent group-edge chrome. |
NumberFieldPartial | Built-in repeatable spin button with caller-replaceable content. |
NumberField | Required- and invalid-aware field label above the group. |
NumberField | Help text shown below the group while the field is valid. |
NumberField | Resolved server, custom or controlled validation message replacing Description. |
States
| State | Builder | Description |
|---|---|---|
Invalid | validation::resolve | Invalid chrome and FieldError replace the normal description. |
Disabled | is_disabled | The whole field is dimmed, inert, omitted from form data and outside the tab order. |
Read only | is_read_only interaction gates | Focus remains available while every numeric mutation path is disabled. |
Focus within | shared Input focus_handle | Focus on the input or either spin button uses the unified field focus treatment. |
Focus visiblePartial | focus_visible | Replacement content receives keyboard-visible focus state; the built-in group uses focus-within chrome. |
HoveredPartial | group hover_fade_with_duration_and_easing(150ms, EaseSmooth) | The enabled unfocused group uses the hover fill and border; the fill interpolates over 150ms ease-smooth while the border endpoint remains immediate. Button hover intentionally has no additional rule body. |
PressedPartial | pressed_with_background | Press fills and geometrically scales the spin button while starting the repeat schedule; arbitrary icon children retain their own fixed size. |
Required | is_required | Required state reaches label, validation, form submission and replacement content. |