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

Date and time

Date Range Picker

Pick a start and end date together.

Rust
use herogpui::components::date_picker::{DateRangePicker, DateRangeState};

DateRangePicker on docs.rsSource at v0.14.0

Usage

Showing International Calendar, live: HeroGPUI compiled to WebAssembly. The canvas has no accessibility tree; each example's description and code below carry the same information.

International Calendar

Both fields and the popover grid share the locale override. Segment order follows that locale; selected values stay Gregorian ISO dates. 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, DateRangePicker, DateRangeState};
use herogpui::gpui::prelude::*;
use herogpui::gpui::px;
use herogpui::gpui;

gpui::div()
    .flex()
    .flex_col()
    .w(px(256.))
    .gap(px(12.))
    .children(vec![DateRangePicker::new(
        self.demo_range("drp-indian", cx),
    )
    .label("Stay")
    .locale("en-US-u-ca-indian")
    .trigger_hover_bg(cx.colors().accent.soft())
    .into_any_element()])

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::{DateRangePicker, DateRangeState};
use herogpui::gpui::prelude::*;
use herogpui::gpui::px;
use herogpui::gpui;

gpui::div()
    .flex()
    .flex_col()
    .w(px(256.))
    .gap(px(12.))
    .children(vec![DateRangePicker::new(
        self.demo_range("drp-disabled", cx),
    )
    .label("Stay")
    .is_disabled(true)
    .into_any_element()])

Controlled

The range lives in the state entity the caller owns. 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::{DateRangePicker, DateRangeState};
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![
        {
            // `value` seeds the caller's copy in, and
            // `on_change` is how the caller gets one: a range
            // picker reports the change with no arguments, so
            // the range is read out of the state entity.
            let state = self.demo_range("drp-controlled", cx);
            let held = cx.entity().downgrade();
            let (start, end) = {
                let range = state.read(cx);
                (range.start, range.end)
            };
            DateRangePicker::new(state.clone())
                .label("Stay")
                .value(start, end)
                .on_change(move |_, cx| {
                    let (start, end) = {
                        let range = state.read(cx);
                        (range.start, range.end)
                    };
                    if let Some(gallery) = held.upgrade() {
                        gallery.update(cx, |gallery, cx| {
                            gallery.set_demo_text_value(
                                "drp-controlled",
                                match (start, end) {
                                    (Some(a), Some(b)) => format!(
                                        "{} to {}",
                                        a.format_iso(),
                                        b.format_iso()
                                    ),
                                    _ => String::new(),
                                },
                            );
                            cx.notify();
                        });
                    }
                })
                .into_any_element()
        },
    ])

Validation

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::{DateRangePicker, DateRangeState};
use herogpui::gpui::prelude::*;
use herogpui::gpui::px;
use herogpui::gpui;

gpui::div()
    .flex()
    .flex_col()
    .w(px(256.))
    .gap(px(12.))
    .children(vec![DateRangePicker::new(
        self.demo_range("drp-invalid", cx),
    )
    .label("Stay")
    .is_invalid(true)
    .into_any_element()])

Format Options

Both ends follow the operating system's regional date order, separators, and numeric padding. Their state and submitted values stay ISO-formatted. 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::{DateRangePicker, DateRangeState};
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![
        DateRangePicker::new(self.demo_range("drp-format", cx))
            .label("Stay")
            .into_any_element(),
    ])

Form Example

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::{Button, DateRangePicker, DateRangeState, Form};
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![Form::new()
        .child(
            DateRangePicker::new(self.demo_range("drp-form", cx))
                .label("Stay")
                // v3 submits a range as two named fields.
                .start_name("check_in")
                .end_name("check_out"),
        )
        .child(Button::new("date-range-picker-form-submit").label("Book"))
        .into_any_element()])

Custom Indicator

Custom indicators replace the default calendar glyph and range separator without changing field or trigger behavior. 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, DateRangePicker, DateRangeState, 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![
        DateRangePicker::new(self.demo_range("drp-indicator", cx))
            .label("Stay")
            .trigger_indicator(gpui::svg()
                                   .size(px(16.))
                                   .path(icons::CHECK)
                                   .text_color(cx.colors().foreground)
                                   .into_any_element())
            .range_separator(gpui::div().child("to"))
            .into_any_element(),
    ])

Render Function

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::{Button, DateRangePicker, DateRangeState};
use herogpui::gpui::prelude::*;
use herogpui::gpui::px;
use herogpui::gpui;

let is_open = self.range_open;
let render_props_gallery = cx.entity().downgrade();

gpui::div()
    .flex()
    .flex_col()
    .items_start()
    .justify_center()
    .gap(px(12.))
    .children(vec![DateRangePicker::new(
        self.demo_range("drp-render-props", cx),
    )
    .is_open(is_open)
    .is_required(true)
    .content(move |state| {
        let gallery = render_props_gallery.clone();
        gpui::div()
            .flex()
            .flex_col()
            .items_start()
            .gap(px(8.))
            .child(format!(
                "{} · {} · {}",
                if state.is_required {
                    "required"
                } else {
                    "optional"
                },
                if state.is_invalid { "invalid" } else { "valid" },
                if state.is_open { "open" } else { "closed" },
            ))
            .child(
                Button::new("date-range-picker-render-props-toggle")
                    .label(if state.is_open { "Close" } else { "Open" })
                    .on_press(move |_, _, cx| {
                        if let Some(gallery) = gallery.upgrade() {
                            gallery.update(cx, |gallery, cx| {
                                gallery.range_open = !gallery.range_open;
                                cx.notify();
                            });
                        }
                    }),
            )
            .into_any_element()
    })
    .into_any_element()])

Usage

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::{Date, DateRangePicker, DateRangeState};
use herogpui::gpui::prelude::*;
use herogpui::gpui::px;
use herogpui::gpui;

let is_open = self.range_open;

gpui::div()
    .flex()
    .flex_col()
    .items_start()
    .justify_center()
    .gap(px(12.))
    .children(vec![gpui::div()
        .w(px(320.))
        .child(
            DateRangePicker::new(self.demo_range("drp-usage", cx))
                .label("Trip dates")
                // v3's Usage seeds the range and bounds it.
                .default_value((
                    Date::new(2025, 12, 8),
                    Date::new(2025, 12, 14),
                ))
                .min_value(Date::new(2025, 1, 1))
                .is_open(is_open)
                .on_open_change(cx.listener(
                    |this, open: &bool, _, cx| {
                        this.range_open = *open;
                        cx.notify();
                    },
                ))
                .on_change(|_, _cx| {}),
        )
        .into_any_element()])

Placement

The range calendar popover accepts the shared 22-value placement vocabulary. Defaults to a centered bottom popover; this specimen opts into top placement. 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::{DateRangePicker, DateRangeState, Placement};
use herogpui::gpui::prelude::*;
use herogpui::gpui::px;
use herogpui::gpui;

gpui::div()
    .flex()
    .flex_col()
    .w(px(256.))
    .gap(px(12.))
    .children(vec![
        DateRangePicker::new(self.demo_range("drp-placement", cx))
            .label("Top placement")
            .placement(Placement::Top)
            .default_open(true)
            .into_any_element(),
    ])

Anatomy

Rust types

DateRangePicker

Date Range Picker 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 Date Range Picker 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

Date Range Picker styling
MethodValuesDescription
placement(Placement)PlacementThe full 22-value union renders through the shared vocabulary with default eight-pixel popover offset: physical and aligned spellings plus the logical start/end aliases, which share their left/right side's pixels in this LTR-only port. The centered bottom value matches default.
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: 31 entries ported, 6 partial, 2 not ported. Partial rows are marked in the tables; entries not ported are listed under each table with the reason.

Builders

Date Range Picker builders
BuilderValuesDefaultDescription
value(start, end)RangeValue<DateValue> | null—Seeds the controlled start and end into the caller-owned DateRangeState on the first render.
default_value((start, end))RangeValue<DateValue> | null—Seeds the uncontrolled complete range once without fighting later edits.
on_change(callback)PartialFn(value: RangeValue<DateValue> | null) -> ()—Reports valid field edits and completed calendar ranges; the range is read from DateRangeState.
is_open(bool)true | false—Controls the range calendar popover without mutating local open state.
default_open(bool)true | falsefalseSeeds uncontrolled popover state.
on_open_change(callback)Fn(isOpen: bool) -> ()—Reports trigger, selection, Escape, outside-press and focus-departure changes.
is_disabled(bool)true | falsefalseRemoves both fields and the trigger from interaction and form submission.
is_read_only(bool)true | falsefalseKeeps both dates focusable while suppressing edits, opening and selection.
is_required(bool)true | falsefalseBlocks native submission until both named range ends are present and focuses the start field first.
is_invalid(bool)true | false—Forces invalid field and range-calendar state.
validate(callback)PartialFn(value: RangeValue<DateValue> | null) -> ValidationError | true | null—A callback supplies one optional validation message for the complete range; structured ValidationError objects are not exposed.
validation_behavior(ValidationBehavior)ValidationBehavior::Native | ValidationBehavior::AriaValidationBehavior::NativeSelects blocking native validation or non-blocking ARIA-style validation for both fields.
validation_errors(errors)Partialstring[]—Server messages take precedence and mark both fields invalid; independently composed FieldError content is unavailable.
min_value(date)DateValue—Earliest valid field and calendar date.
max_value(date)DateValue—Latest valid field and calendar date.
is_date_unavailable(predicate)Fn(date: DateValue) -> bool—Rejects individual dates inside the selected range.
first_day_of_week(Weekday)stringWeekday::LocaleThe range calendar grid defaults from the operating system's regional date preferences through CLDR week data; the local Weekday enum provides an explicit override.
should_close_on_select(bool)Partialtrue | falsetrueThe boolean form controls whether a completed calendar range dismisses the popover; callback-valued policy is not represented.
auto_focus(bool)true | falsefalseFocuses the editable start field on first render.
start_name(text)string—Submits the displayed start date under this name.
end_name(text)string—Submits the displayed end date under this name.
content(render)AnyElement | Fn(values: DateRangePickerRenderProps) -> AnyElement—Replacement content receives disabled, invalid, read-only, required, focus and open state.
trigger_indicator(element)AnyElementAnyElement::CalendarIconReplaces the glyph inside the existing trigger geometry and behavior.
range_separator(element)AnyElement" - "Replaces the text between the two editable date fields.
placement(Placement)PlacementPlacement::BottomThe full 22-value union renders through the shared vocabulary with default eight-pixel popover offset: physical and aligned spellings plus the logical start/end aliases, which share their left/right side's pixels in this LTR-only port. The centered bottom value matches default.
Not ported2 HeroUI entries not ported
Date Range Picker builders not ported
HeroUI v3Why it is not ported
DateRangePicker.className / styleBrowser styles and inline styles are unavailable.
DateRangePicker.renderRoot-element substitution has no GPUI equivalent.

Parts

Date Range Picker parts
PartDescription
DateRangePickerRoot state owner and focus-within scope with two system-regionally formatted DateFields. Simultaneous pickers need distinct DateRangeState owners.
DateRangePickerPartialSeparate keyboard and pointer action inside the shared range field.
DateRangePickerDefault or caller-supplied 16px trigger glyph.
DateRangePickerDefault or caller-supplied separator between the start and end fields.
DateRangePickerPartialRangeCalendar overlay positioned by the shared Placement vocabulary.

States

Date Range Picker states
StateBuilderDescription
Opencontrolled/open overlay stateMounts the range calendar overlay and hands open state to replacement content.
Focus withinclose_on_blur/content focus scopeTracks focus across both editable fields, the trigger and the calendar.
Focus visiblefocus_visibleDraws keyboard focus and hands keyboard-visible root focus to replacement content.
Disabledis_disabled(bool)Applies disabled opacity and removes editing, opening and submission.
Read onlyis_read_only(bool)Keeps both fields focusable while making field, trigger and calendar inert.
Requiredis_required(bool)Marks the label and blocks native submission until both range ends exist.
Invalidresolved DateRangePicker validityCombines controlled, server, custom, syntax and date-constraint validation.