Skip to content
PorabuildHeroGPUI component library
v0.14.0 · Apache-2.0

Controls

Slider

Select a value from a range by dragging.

Rust
use herogpui::components::slider::Slider;

Slider 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.

Usage

Labels and values use 14px text with a 20px line height, independent of the surrounding text style. An element expression with window: &mut Window and cx: &mut App in scope.

Rust
use herogpui::prelude::Slider;
use herogpui::gpui::prelude::*;
use herogpui::gpui::px;
use herogpui::gpui;

gpui::div()
    .w(px(320.))
    .child(
        Slider::new("slider-main", 30.)
            .default_value(30.)
            .label("Volume")
            .show_value(true),
    )

Format options

Runs in a view's render: self is the view holding this example's state and cx is its Context<Self>.

Rust
use herogpui::prelude::{NumberFormat, Slider};
use herogpui::gpui::prelude::*;
use herogpui::gpui::px;
use herogpui::gpui;

let value = self.slider_value;

gpui::div()
    .w(px(320.))
    .child(Slider::new("slider-fmt", value)
            .label("Budget")
            .show_value(true)
            .format_options(NumberFormat::currency("EUR"))
            .on_change(cx.listener(|this, v: &f32, _, cx| {
                this.slider_value = *v;
                cx.notify();
            })))

Range Slider Anatomy

A range slider is built from its parts: a `Label`, an `Output`, and a `Track` whose `thumb` closure is handed the state so it can draw one thumb per value. Runs in a view's render: self is the view holding this example's state and cx is its Context<Self>.

Rust
use herogpui::prelude::Slider;
use herogpui::gpui::prelude::*;
use herogpui::gpui::px;
use herogpui::gpui;

let value = self.slider_value;

gpui::div()
    .w(px(320.))
    .child(Slider::new("slider-anatomy", 25.)
                .default_values([25., 75.])
                .label("Price range")
                .show_value(true)
                .thumb(|index, value| {
                    gpui::div()
                        .id(("sl-anatomy-thumb", index))
                        .size(px(18.))
                        .rounded_full()
                        .border_2()
                        .border_color(gpui::white())
                        .bg(gpui::rgb(0x0085F5))
                        // The closure is handed the value the slider
                        // already computed for this thumb, so the
                        // caller never re-derives it.
                        .flex()
                        .items_center()
                        .justify_center()
                        .child(
                            gpui::div()
                                .absolute()
                                .top(px(-20.))
                                .text_size(px(11.))
                                .child(format!("{value:.0}")),
                        )
                        .into_any_element()
                }))

Controlled Value

Runs in a view's render: self is the view holding this example's state and cx is its Context<Self>.

Rust
use herogpui::prelude::{ActiveTheme, Slider};
use herogpui::gpui::prelude::*;
use herogpui::gpui::{App, px};
use herogpui::gpui;

let volume = self.demo_value("sl-controlled", 40.);

gpui::div()
    .flex()
    .flex_col()
    .items_start()
    .justify_center()
    .gap(px(12.))
    .children(vec![
        gpui::div()
            .w(px(320.))
            .child(Slider::new("slider-controlled", volume)
                        .label("Volume")
                        .show_value(true)
                        .on_change(cx.listener(|this, v: &f32, _, cx| {
                            this.set_demo_value("sl-controlled", *v);
                            cx.notify();
                        })))
            .into_any_element(),
        para(&format!("Value: {volume:.0}"), cx),
    ])

Also calls para (included when you copy).

Custom Value Formatting

An element expression with window: &mut Window and cx: &mut App in scope.

Rust
use herogpui::prelude::Slider;
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![
        gpui::div()
            .w(px(320.))
            .child(Slider::new("slider-fmt-pct", 0.35)
                        .default_value(0.35)
                        .min_value(0.)
                        .max_value(1.)
                        .step(0.01)
                        .label("Opacity")
                        .show_value(true)
                        .format_options(herogpui::core::NumberFormat::percent()))
            .into_any_element(),
        gpui::div()
            .w(px(320.))
            .child(Slider::new("slider-fmt-cur", 1200.)
                        .default_value(1200.)
                        .min_value(0.)
                        .max_value(5000.)
                        .step(50.)
                        .label("Budget")
                        .show_value(true)
                        .format_options(herogpui::core::NumberFormat::currency("USD")))
            .into_any_element(),
    ])

Custom Output Display

The output closure receives every live value and its formatted thumb label. Runs in a view's render: self is the view holding this example's state and cx is its Context<Self>.

Rust
use herogpui::prelude::{Chip, ChipLabel, ChipVariant, Color, Slider};
use herogpui::gpui::prelude::*;
use herogpui::gpui::px;
use herogpui::gpui;

let volume = self.demo_value("sl-controlled", 40.);

gpui::div()
    .w(px(320.))
    .child(
        Slider::new("slider-output", volume)
            .label("Brightness")
            .output(|values, labels| {
                Chip::new()
                    .variant(ChipVariant::Soft)
                    .color(Color::Accent)
                    .radius(px(4.))
                    .text_size(px(12.))
                    .child(ChipLabel::new().child(format!(
                        "{:.0}% ({})",
                        values[0], labels[0]
                    )))
                    .into_any_element()
            })
            .on_change(cx.listener(|this, v: &f32, _, cx| {
                this.set_demo_value("sl-controlled", *v);
                cx.notify();
            })),
    )

Range

Runs in a view's render: self is the view holding this example's state and cx is its Context<Self>.

Rust
use herogpui::prelude::Slider;
use herogpui::gpui::prelude::*;
use herogpui::gpui::px;
use herogpui::gpui;

let value = self.slider_value;

gpui::div()
    .w(px(320.))
    .child(Slider::new("slider-range", value)
            .label("Price range")
            // An empty controlled range falls back to one
            // thumb; keep that fallback interactive too.
            .default_value(value)
            .values(self.slider_range.clone())
            .on_change_all(cx.listener(|this, vs: &[f32], _, cx| {
                this.slider_range = vs.to_vec();
                cx.notify();
            })))

Range Slider

An uncontrolled range specimen: two thumbs seeded at 100 and 500 over a 0-1000 span, stepping by 50 and formatted as US dollars. An element expression with window: &mut Window and cx: &mut App in scope.

Rust
use herogpui::prelude::Slider;
use herogpui::gpui::prelude::*;
use herogpui::gpui::px;
use herogpui::gpui;

gpui::div()
    .w(px(320.))
    .child(Slider::new("slider-range-slider", 100.)
            .default_values([100., 500.])
            .min_value(0.)
            .max_value(1000.)
            .step(50.)
            .label("Price Range")
            .show_value(true)
            .format_options(herogpui::core::NumberFormat::currency("USD")))

Basic Usage

The plainest slider: one thumb, a label, and the value readout, uncontrolled. An element expression with window: &mut Window and cx: &mut App in scope.

Rust
use herogpui::prelude::Slider;
use herogpui::gpui::prelude::*;
use herogpui::gpui::px;
use herogpui::gpui;

gpui::div()
    .w(px(320.))
    .child(Slider::new("slider-basic", 100.)
            .min_value(0.)
            .max_value(200.)
            .default_value(100.)
            .label("Storage")
            .show_value(true))

Disabled

A disabled slider keeps its value readable but answers no drag, wheel, or keys. An element expression with window: &mut Window and cx: &mut App in scope.

Rust
use herogpui::prelude::Slider;
use herogpui::gpui::prelude::*;
use herogpui::gpui::px;
use herogpui::gpui;

gpui::div()
    .w(px(320.))
    .child(Slider::new("slider-disabled", 30.)
            .default_value(30.)
            .label("Volume")
            .show_value(true)
            .is_disabled(true))

Native Seeking

Continuous seeking brackets pointer updates with drag hooks; arbitrary presets retain keyboard navigation. Runs in a view's render: self is the view holding this example's state and cx is its Context<Self>.

Rust
use herogpui::prelude::Slider;
use herogpui::gpui::prelude::*;
use herogpui::gpui::px;
use herogpui::gpui;

let seek_start = cx.entity().downgrade();
let seek_end = seek_start.clone();

gpui::div()
    .flex()
    .flex_col()
    .items_start()
    .justify_center()
    .gap(px(12.))
    .children(vec![
        gpui::div()
            .w(px(320.))
            .child(Slider::new("slider-continuous", 0.5)
                    .default_value(0.5).max_value(1.).continuous(true)
                    .sx(|el| el.rounded_full()).show_value(true)
                    .label(if self.demo_flag("sl-seeking", false) { "Preview paused" } else { "Preview playing" })
                    .on_drag_start(move |_, cx| { let _ = seek_start.update(cx, |this, cx| { this.set_demo_flag("sl-seeking", true); cx.notify(); }); })
                    .on_drag_end(move |_, cx| { let _ = seek_end.update(cx, |this, cx| { this.set_demo_flag("sl-seeking", false); cx.notify(); }); }))
            .into_any_element(),
        gpui::div()
            .w(px(320.))
            .child(Slider::new("slider-presets", 1.)
                    .default_value(1.).min_value(0.25).max_value(2.)
                    .steps([0.25, 0.5, 1., 1.5, 2.]).show_value(true))
            .into_any_element(),
    ])

Sizes

`size` is HeroGPUI's additive scale: `Md` is v3's 20px rail with its two-layer thumb, and `Sm` is a 6px pill with a single 12px knob. An element expression with window: &mut Window and cx: &mut App in scope.

Rust
use herogpui::prelude::{Slider, SliderSize};
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![
        gpui::div()
            .w(px(320.))
            .child(Slider::new("slider-size-md", 40.)
                        .default_value(40.)
                        .label("Md")
                        .show_value(true))
            .into_any_element(),
        gpui::div()
            .w(px(320.))
            .child(Slider::new("slider-size-sm", 40.)
                        .default_value(40.)
                        .size(SliderSize::Sm)
                        .label("Sm")
                        .show_value(true))
            .into_any_element(),
    ])

Vertical Orientation

The vertical specimen grows upward with its label attached. An element expression with window: &mut Window and cx: &mut App in scope.

Rust
use herogpui::prelude::{Orientation, Slider};
use herogpui::gpui::prelude::*;
use herogpui::gpui::px;
use herogpui::gpui;

gpui::div()
    .w(px(320.))
    .child(Slider::new("slider-vert-orientation", 30.)
            .default_value(30.)
            .label("Volume")
            .show_value(true)
            .orientation(Orientation::Vertical))

Disabled Thumb

`Slider.Thumb.isDisabled` fixes one thumb — dimmed, out of the roving tab stop, answering no drag or keys — while the other thumb keeps moving: the contrast a whole-slider `isDisabled` cannot show. Runs in a view's render: self is the view holding this example's state and cx is its Context<Self>.

Rust
use herogpui::prelude::Slider;
use herogpui::gpui::prelude::*;
use herogpui::gpui::px;
use herogpui::gpui;

let value = self.slider_value;

gpui::div()
    .w(px(320.))
    .child(Slider::new("slider-lock", value)
                .label("Price range")
                .default_value(value)
                .values(self.slider_range.clone())
                .disabled_keys([0])
                .on_change_all(cx.listener(|this, vs: &[f32], _, cx| {
                    this.slider_range = vs.to_vec();
                    cx.notify();
                })))

Form Example

`Slider.Thumb.name` names each end of a range. `form_fields` hands the pair to the `Form` — it is told its fields, with no context propagation — so a submission carries one value per named thumb. Runs in a view's render: self is the view holding this example's state and cx is its Context<Self>.

Rust
use herogpui::prelude::{ActiveTheme, Button, Form, FormData, Slider};
use herogpui::gpui::prelude::*;
use herogpui::gpui::{App, px};
use herogpui::gpui;

let value = self.slider_value;

gpui::div()
    .flex()
    .flex_col()
    .items_start()
    .justify_center()
    .gap(px(12.))
    .children(vec![
        gpui::div()
            .w(px(320.))
            .child({
                    // v3 renders one `<input name=…>` per thumb; the form reads
                    // them back through `form_fields`, as DateRangePicker does.
                    let slider = Slider::new("slider-form", value)
                        .label("Price range")
                        .default_value(value)
                        .values(self.slider_range.clone())
                        .start_name("min")
                        .end_name("max")
                        .on_change_all(cx.listener(|this, vs: &[f32], _, cx| {
                            this.slider_range = vs.to_vec();
                            cx.notify();
                        }));
                    let mut form = Form::new();
                    for field in slider.form_fields() {
                        form = form.field(field);
                    }
                    let form =
                        form.on_submit(cx.listener(|this, data: &FormData, _, cx| {
                            this.input_submitted = format!(
                                "min={}, max={}",
                                data.text("min").unwrap_or_default(),
                                data.text("max").unwrap_or_default(),
                            );
                            cx.notify();
                        }));
                    let submit = form.submit_handler();
                    form.child(slider.into_any_element())
                        .child(
                            Button::new("slider-form-submit")
                                .label("Save")
                                .on_press(move |_, window, cx| submit(window, cx)),
                        )
                        .into_any_element()
                })
            .into_any_element(),
        para(
            &if self.input_submitted.is_empty() {
                "Nothing submitted yet".to_owned()
            } else {
                format!("Submitted: {}", self.input_submitted)
            },
            cx,
        ),
    ])

Also calls para (included when you copy).

Vertical

Runs in a view's render: self is the view holding this example's state and cx is its Context<Self>.

Rust
use herogpui::prelude::{Orientation, Slider};

let value = self.slider_value;

Slider::new("slider-vert", value)
    .orientation(Orientation::Vertical)
    .on_change(cx.listener(|this, v: &f32, _, cx| {
        this.slider_value = *v;
        cx.notify();
    }))

Step & disabled

Runs in a view's render: self is the view holding this example's state and cx is its Context<Self>.

Rust
use herogpui::prelude::Slider;
use herogpui::gpui::prelude::*;
use herogpui::gpui::px;
use herogpui::gpui;

let value = self.slider_value;

gpui::div()
    .flex()
    .flex_col()
    .items_start()
    .justify_center()
    .gap(px(12.))
    .children(vec![
        gpui::div()
            .w(px(320.))
            .child(Slider::new("slider-step", value)
                        .step(10.0)
                        .label("Step 10")
                        .show_value(true)
                        .on_change(cx.listener(|this, v: &f32, _, cx| {
                            this.slider_value = *v;
                            cx.notify();
                        })))
            .into_any_element(),
        gpui::div()
            .w(px(320.))
            .child(Slider::new("slider-step-disabled", value)
                        .is_disabled(true)
                        .label("Disabled"))
            .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.

para
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

Slider

Slider 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 Slider 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

Slider styling
MethodValuesDescription
orientation(Orientation)Orientation::Horizontal | Orientation::VerticalThe direction in which values increase.
Rust
// 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, 4 partial, 15 not ported. Partial rows are marked in the tables; entries not ported are listed under each table with the reason.

Builders

Slider builders
BuilderValuesDefaultDescription
value(f32) / values(values)number | number[]—The current value or values (controlled). Only the active single/range value mode owns state.
default_value(f32) / default_values(values)number | number[]—The initial value or values (uncontrolled). Use this seed for interactive examples that do not store onChange values, including custom number formatting.
on_change(callback) / on_change_all(callback)Fn(value: number | number[]) -> ()—Handler called whenever a thumb changes value.
on_change_end(callback) / on_change_end_all(callback)Fn(value: number | number[]) -> ()—Handler called once when a drag or keyboard step completes.
min_value(f32)number0The minimum value.
max_value(f32)number100The maximum value.
step(f32)number1The increment between valid values.
format_options(NumberFormat)PartialIntl.NumberFormatOptions—Formats the displayed value; supports decimal, percent and currency without CLDR locale data.
orientation(Orientation)Orientation::Horizontal | Orientation::VerticalOrientation::HorizontalThe direction in which values increase.
is_disabled(bool)true | false—Disables every thumb and removes the slider from the tab order.
label(value)PartialAnyElement | render closure—Compound slider content; the monolithic port exposes its label, output and thumb customizations as builders.
output(render)AnyElement | render closure—Custom output content handed every live value and its formatted thumb label.
thumb(render)PartialAnyElement | render closure—Track content; the thumb closure receives each computed index and value.
disabled_keys(indices)true | false—Disables this thumb only and removes its form field and tab stop.
thumb_names(names)string—Name of this thumb's submitted form value.
Not ported15 HeroUI entries not ported
Slider builders not ported
HeroUI v3Why it is not ported
Slider.aria-labelAccessible label for an otherwise unlabelled slider.
Slider.aria-labelledbyID of the element that labels the slider.
Slider.classNameFor the root. Native sx pixel corners also reach the track caps and both Md thumb layers; joined single-thumb cap/fill edges remain square.
Slider.renderOverrides the root element.
Slider.Output.classNameFor the output.
Slider.Output.renderOverrides the output element.
Slider.Track.classNameFor the track.
Slider.Track.renderOverrides the track element.
Slider.Fill.classNameFor the fill.
Slider.Fill.styleInline styles for the fill.
Slider.Thumb.classNameFor the thumb.
Slider.Thumb.renderOverrides the thumb element.
Render Props.stateFull slider state passed to Output and Track render functions.
Render Props.orientationCurrent orientation supplied to a render function.
Render Props.isDisabledCurrent disabled state supplied to a render function.

Parts

Slider parts
PartDescription
SliderRoot value, form, pointer and keyboard state owner.
SliderPartialOptional text label rendered above or beside the track.
SliderFormatted current values or custom content rendered from the live value array.
SliderPointer and keyboard surface containing fill and thumbs.
SliderAccent span from the minimum or between the outermost thumbs.
SliderOne draggable, focusable value handle per value.

States

Slider states
StateBuilderDescription
HorizontalOrientation::HorizontalValues increase from left to right.
VerticalOrientation::VerticalValues increase from bottom to top.
Focus visiblering_if_focusedThe active enabled thumb carries the keyboard focus ring.
DraggingDragStatePointer drag state updates the value and scales the inner thumb to 90% over the 250ms ease-out ramp; reduced motion settles directly.
Disabled sliderSlider::is_disabledAll thumbs are inert, dimmed and removed from form submission and tab order.
Disabled thumbSlider::disabled_keysOne thumb is inert and omitted while its siblings remain interactive.
Fill startfill_from == 0The accent fill reaches the low edge.
Fill endfill_to == 1The accent fill reaches the high edge.