Color Picker
A complete color picking surface in a popover.
use herogpui::components::color_picker::ColorPicker;ColorPicker 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.
The panel flips near window edges and scrolls to keep the alpha control reachable in short windows. Runs in a view's render: self is the view holding this example's state and cx is its Context<Self>.
use herogpui::prelude::{ColorPicker, PickerColor};
use herogpui::gpui::prelude::*;
use herogpui::gpui::px;
use herogpui::gpui;
let value = self.picker_color;
gpui::div()
.flex()
.flex_col()
.items_start()
.justify_center()
.gap(px(12.))
.children(vec![ColorPicker::new("color-picker-main", value)
// v3's Usage is uncontrolled; "Controlled" is separate.
.default_value(value)
.label("Accent")
.font_family(MONO_FONT)
.show_alpha(true)
.on_change(cx.listener(|this, c: &PickerColor, _, cx| {
this.picker_color = *c;
cx.notify();
}))
.into_any_element()])Also calls MONO_FONT (included when you copy).
A controlled-open picker exercises the initial overlay mount and placement resolver without requiring a trigger press. Runs in a view's render: self is the view holding this example's state and cx is its Context<Self>.
use herogpui::prelude::{ColorPicker, Placement};
use herogpui::gpui::prelude::*;
use herogpui::gpui::px;
use herogpui::gpui;
let value = self.picker_color;
gpui::div()
.flex()
.flex_col()
.items_start()
.justify_center()
.gap(px(12.))
.children(vec![ColorPicker::new("color-picker-open", value)
.is_open(true)
.placement(Placement::BottomStart)
.label("Accent")
.font_family(MONO_FONT)
.show_alpha(true)
// Controlled open state intentionally ignores close
// requests so the initial placement stays visible in
// the visual specimen.
.on_open_change(|_, _, _| {})
.into_any_element()])Also calls MONO_FONT (included when you copy).
The caller owns the color value while the trigger owns its ordinary open state — the same split a dialog trigger composes. 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, ColorPicker, PickerColor};
use herogpui::gpui::prelude::*;
use herogpui::gpui::{App, px};
use herogpui::gpui;
let value = self.picker_color;
gpui::div()
.flex()
.flex_col()
.items_start()
.justify_center()
.gap(px(12.))
.children(vec![
ColorPicker::new("color-picker-controlled", value)
.label("Brand")
.on_change(cx.listener(|this, c: &PickerColor, _, cx| {
this.picker_color = *c;
cx.notify();
},))
.into_any_element(),
para(&format!("Value: {}", value.to_hex()), cx),
])Also calls para (included when you copy).
A preset row beside the picker — the default layout. Runs in a view's render: self is the view holding this example's state and cx is its Context<Self>.
use herogpui::prelude::{ColorPicker, ColorSwatchPicker, PickerColor};
use herogpui::gpui::prelude::*;
use herogpui::gpui::px;
use herogpui::gpui;
let value = self.picker_color;
gpui::div()
.flex()
.flex_col()
.items_start()
.justify_center()
.gap(px(12.))
.children(vec![
ColorSwatchPicker::new("color-picker-presets", palette())
.value(value)
.on_change(cx.listener(|this, c: &PickerColor, _, cx| {
this.picker_color = *c;
cx.notify();
}))
.into_any_element(),
])Also calls palette (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::{ColorChannel, ColorField, ColorPicker};
use herogpui::gpui::prelude::*;
use herogpui::gpui::px;
use herogpui::gpui;
let value = self.picker_color;
gpui::div()
.flex()
.flex_col()
.items_start()
.justify_center()
.gap(px(12.))
.children(vec![
ColorField::new("color-picker-field", value)
.state(self.demo_text("cp-field", "#0085F5", cx))
.label("Hex")
.into_any_element(),
gpui::div()
.flex()
.flex_wrap()
.items_center()
.justify_center()
.gap(px(12.))
.children(vec![
ColorField::new("color-picker-field-h", value)
.state(self.demo_text("cp-field-h", "", cx))
.channel(ColorChannel::Hue)
.label("H")
.into_any_element(),
ColorField::new("color-picker-field-s", value)
.state(self.demo_text("cp-field-s", "", cx))
.channel(ColorChannel::Saturation)
.label("S")
.into_any_element(),
ColorField::new("color-picker-field-l", value)
.state(self.demo_text("cp-field-l", "", cx))
.channel(ColorChannel::Lightness)
.label("L")
.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::{ColorChannel, ColorPicker, ColorSlider, PickerColor};
use herogpui::gpui::prelude::*;
use herogpui::gpui::px;
use herogpui::gpui;
let value = self.picker_color;
gpui::div()
.flex()
.flex_col()
.items_start()
.justify_center()
.gap(px(12.))
.children(vec![
ColorSlider::new("color-picker-sl-hue", value, ColorChannel::Hue)
.on_change(cx.listener(|this, c: &PickerColor, _, cx| {
this.picker_color = *c;
cx.notify();
}))
.into_any_element(),
ColorSlider::new("color-picker-sl-alpha", value, ColorChannel::Alpha)
.on_change(cx.listener(|this, c: &PickerColor, _, cx| {
this.picker_color = *c;
cx.notify();
}))
.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()
}/// The demo palette used by the color pages.
fn palette() -> Vec<PickerColor> {
[
"#0085F5", "#17C964", "#F5A524", "#F31260", "#7828C8", "#0E8AAA", "#71717A", "#18181B",
]
.into_iter()
.filter_map(PickerColor::from_hex)
.collect()
}Anatomy
Rust types
Color 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 Color 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
| Method | Values | Description |
|---|---|---|
placement(Placement) | Placement | Positions and flips the floating panel to the side with more room when the preferred side cannot fit through the shared placement engine, keeping a 12px cross-axis viewport inset with the scroller capped to the available height. |
// 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: 12 entries ported, 6 partial, 4 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 |
|---|---|---|---|
new( | string | | — | Controlled color value; owner acceptance drives the next frame. |
default_value( | string | | — | Seeds the picker-owned color once. |
on_change( | Fn( | — | Reports changes from the area and each rendered slider. |
label / show_alphaPartial | AnyElement | — | The Rust root renders a fixed trigger, area, hue slider, optional alpha slider, and readout rather than arbitrary compound children. |
built-in triggerPartial | AnyElement | | — | The trigger is the fixed swatch and hexadecimal value and does not delegate Button render state. |
is_disabled( | true | | false | Inherited Button state disables the trigger and suppresses the panel. |
placement( | Placement | Placement:: | Positions and flips the floating panel to the side with more room when the preferred side cannot fit through the shared placement engine, keeping a 12px cross-axis viewport inset with the scroller capped to the available height. |
built-in popover contentPartial | AnyElement | — | The built-in area, sliders, and hexadecimal readout cannot be replaced independently. |
Not ported3 HeroUI entries not ported
| HeroUI v3 | Why it is not ported |
|---|---|
ColorPicker.className | Browser styles are unavailable. |
ColorPicker.Trigger.className | Browser styles are unavailable. |
ColorPicker.Popover.className | Browser styles are unavailable. |
Parts
| Part | Description |
|---|---|
ColorPicker | Color-value and internal DialogTrigger state owner. |
ColorPickerPartial | Focusable swatch and hexadecimal-value trigger with fixed content. |
ColorPickerPartial | Dismissible floating color controls with fixed built-in children. |
States
| State | Builder | Description |
|---|---|---|
Open | internal keyed state | The default trigger owns open state; Rust's composition-only is_open builder reports without mutating until its owner accepts the change. |
Focused | tab_stop_handle | The trigger participates in tab order and regains focus after dismissal. |
Focus visible | ring_if_focused | Keyboard focus draws the status ring. |
PressedPartial | trigger_pressed dismissal guard | Press tracking protects outside dismissal but is not delegated to trigger content. |
Disabled | is_disabled | The trigger dims, leaves pointer interaction, and suppresses its panel. |
Entering | Motion::LIST_IN | Panel fades and grows from 95% over 150ms with ease-smooth. |
Exiting | Motion::LIST_OUT | Panel is retained while fading and shrinking to 95% over 100ms. |
Not ported1 HeroUI entry not ported
| HeroUI v3 | Why it is not ported |
|---|---|
Hovered | The fixed Rust trigger does not expose Button hover render state. |