ColorPicker
A versatile color picker component that provides an intuitive interface for color selection. Features include color palettes, hex input, featured colors, and support for various color formats including RGB, HSL, and hex values with alpha channel support.
#Import
use gpui_kit::component::color_picker::{
ColorPicker, ColorPickerEvent, ColorPickerState, ColorSelect,
};// Installed in ui/components. That directory is on the Slint include path, so import by file name:
import { Swatch, ColorPicker } from "color-picker.slint";use iced::keyboard;
use iced::widget::{column, container, mouse_area, row, scrollable, svg, text};
use iced::{Alignment, Border, Color, Element, Length, Subscription, Task, Theme};#Usage
// The whole of a colour picker: the value, the hex text, the open flag and
// the reports. `new` starts on nothing; `value` is the chosen colour and
// `None` is the empty state, `hex` is the text the field shows, and the four
// callbacks are what the trigger, a palette press or a slider drag, the hex
// field and the two tabs ask for. The preview under it is the caller's, made
// from the value the picker reported.
let usage = body_box(
column![
ColorPicker::new()
.value(app.value[slot::USAGE])
.hex(app.hex[slot::USAGE].clone())
.open(app.open[slot::USAGE])
.tab(app.tab[slot::USAGE])
.id("color-picker-0")
.on_open_change(|open| Message::Opened {
slot: slot::USAGE,
open,
})
.on_change(|value| Message::Changed {
slot: slot::USAGE,
value,
})
.on_hex_input(|text| Message::HexInput {
slot: slot::USAGE,
text,
})
.on_hex_submit(Message::HexSubmit { slot: slot::USAGE })
.on_tab(|tab| Message::Tab {
slot: slot::USAGE,
tab,
}),
preview(app.value[slot::USAGE]),
muted_line(
"The square trigger opens the palette; the HSLA tab and the hex \
field at the foot of the panel edit the same value.",
),
]
.spacing(12.0),
);#Basic Color Picker
use gpui_kit::{Entity, Window, Context};
// Create color picker state
let color_picker = cx.new(|cx|
ColorPickerState::new(window, cx)
.default_value(cx.theme().primary)
);
// Create the color picker component
ColorPicker::new(&color_picker)#With Event Handling
use gpui_kit::{Subscription, Entity};
let color_picker = cx.new(|cx| ColorPickerState::new(window, cx));
let _subscription = cx.subscribe(&color_picker, |this, _, ev, _| match ev {
ColorPickerEvent::Change(color) => {
if let Some(color) = color {
println!("Selected color: {}", color.to_hex());
// Handle color change
}
}
});
ColorPicker::new(&color_picker)#Setting Default Color
use gpui_kit::Hsla;
let color_picker = cx.new(|cx|
ColorPickerState::new(window, cx)
.default_value(cx.theme().blue) // Set default color
);#Different Sizes
// Small color picker
ColorPicker::new(&color_picker).small()
// Medium color picker (default)
ColorPicker::new(&color_picker)
// Large color picker
ColorPicker::new(&color_picker).large()
// Extra small color picker
ColorPicker::new(&color_picker).xsmall()// The trigger's height, inset and type come from one size step — the same
// ladder `input` and `select` use — so a picker sits beside the fields in a
// form. The panel does not change with it.
let sizes = body_box(
column![
row![
picker(app, slot::XSMALL, false, ControlSize::XSmall),
picker(app, slot::SMALL, false, ControlSize::Small),
picker(app, slot::MEDIUM, false, ControlSize::Medium),
picker(app, slot::LARGE, false, ControlSize::Large),
]
.spacing(12.0)
.align_y(Alignment::Center),
muted_line("Extra small, small, medium and large square triggers."),
]
.spacing(12.0),
);#With Custom Featured Colors
use gpui_kit::Hsla;
let featured_colors = vec![
cx.theme().red,
cx.theme().green,
cx.theme().blue,
cx.theme().yellow,
// Add your custom colors
];
ColorPicker::new(&color_picker)
.featured_colors(featured_colors)// `featured` replaces the top row — the Slint `swatches` property, GPUI's
// `featured_colors`. The nine family ramps below it are the component's own
// and do not change.
let featured = body_box(
column![
picker(app, slot::FEATURED, true, ControlSize::Medium).featured(vec![
Color::from_rgb8(0xff, 0x6b, 0x6b),
Color::from_rgb8(0x4e, 0xcd, 0xc4),
Color::from_rgb8(0x45, 0xb7, 0xd1),
Color::from_rgb8(0x96, 0xce, 0xb4),
Color::from_rgb8(0xff, 0xea, 0xa7),
]),
muted_line(
"A caller's own five: open the panel and the featured row is the \
brand set rather than the component's default eleven.",
),
]
.spacing(12.0),
);#With Icon Instead of Color Square
use gpui_kit::component::IconName;
ColorPicker::new(&color_picker)
.icon(IconName::Palette)#With Label
ColorPicker::new(&color_picker)
.label("Background Color")#Custom Anchor Position
use gpui_kit::Anchor;
ColorPicker::new(&color_picker)
.anchor(Anchor::TopRight) // Dropdown opens to top-right#Theme Color
// The field trigger: a swatch, the hex value and a caret, sharing the height
// and frame of the inputs around it. `field(true)` is GPUI's `ColorSelect`
// and the Slint `field` property; the preview under it is the application's.
let accent = app.value[slot::THEME];
let theme_color = body_box(
column![
picker(app, slot::THEME, true, ControlSize::Medium),
preview(accent),
row![
text("Selected color")
.size(theme::FONT_SIZE_SM)
.width(Length::Fill)
.style(|theme: &Theme| text::Style {
color: Some(Tokens::of(theme).muted_foreground),
}),
text(app.hex[slot::THEME].clone())
.size(theme::FONT_SIZE_SM)
.font(theme::MONO)
.style(|theme: &Theme| text::Style {
color: Some(Tokens::of(theme).foreground),
}),
]
.spacing(8.0)
.align_y(Alignment::Center),
]
.spacing(16.0),
);Select a color and preview the resulting value. The gallery opens on indigo.
ColorPicker::new(&self.color).with_size(self.size)Rectangle {
border-width: 1px;
border-color: Theme.border;
border-radius: Theme.radius-lg;
VerticalLayout {
padding: 16px;
spacing: 16px;
HorizontalLayout {
spacing: 16px;
alignment: center;
VerticalLayout {
horizontal-stretch: 1;
spacing: 4px;
Text {
text: "Accent color";
color: Theme.foreground;
font-size: Theme.font-size-sm;
font-weight: 500;
}
Text {
text: "Used for primary actions and highlights.";
color: Theme.muted-foreground;
font-size: Theme.font-size-sm;
wrap: word-wrap;
}
}
accent-picker := ColorPicker {
value <=> root.accent;
}
}
Rectangle {
border-width: 1px;
border-color: Theme.border;
border-radius: Theme.radius-lg;
clip: true;
VerticalLayout {
Rectangle {
height: 96px;
background: root.accent;
}
Rectangle {
background: Theme.muted;
HorizontalLayout {
padding-left: 12px;
padding-right: 12px;
padding-top: 8px;
padding-bottom: 8px;
spacing: 8px;
alignment: center;
Text {
horizontal-stretch: 1;
text: "Selected color";
color: Theme.muted-foreground;
font-size: Theme.font-size-sm;
vertical-alignment: center;
}
Text {
text: accent-picker.hex(root.accent);
color: Theme.foreground;
font-size: Theme.font-size-sm;
font-weight: 500;
font-family: Theme.mono-font-family;
vertical-alignment: center;
}
}
}
}
}
}
}#Color Select
// The same control in a form: the framed field, and the placeholder a field
// shows while nothing is chosen.
let color_select = body_box(
column![
picker(app, slot::SELECT_VALUE, true, ControlSize::Medium),
ColorPicker::new()
.value(app.value[slot::SELECT_EMPTY])
.hex(app.hex[slot::SELECT_EMPTY].clone())
.open(app.open[slot::SELECT_EMPTY])
.field(true)
.placeholder("Pick a color")
.id("color-picker-8")
.on_open_change(|open| Message::Opened {
slot: slot::SELECT_EMPTY,
open,
})
.on_change(|value| Message::Changed {
slot: slot::SELECT_EMPTY,
value,
})
.on_hex_input(|text| Message::HexInput {
slot: slot::SELECT_EMPTY,
text,
})
.on_hex_submit(Message::HexSubmit {
slot: slot::SELECT_EMPTY,
})
.on_tab(|tab| Message::Tab {
slot: slot::SELECT_EMPTY,
tab,
}),
muted_line(
"The first field holds a value; the second is empty and shows its \
placeholder until a colour is picked.",
),
]
.spacing(12.0),
);ColorSelect draws the picker as a framed field, like a Select: a swatch of
the current color, its hex value and a caret. Clicking anywhere on the field
opens the same popover. Use it in forms, where the control should share the
height and frame of the inputs around it; keep ColorPicker for a compact
swatch in a toolbar.
use gpui_kit::component::{Sizable as _, form::field};
field()
.label("Theme color")
.child(ColorSelect::new(&color_picker))
// Follows the same sizes as Input and Select.
ColorSelect::new(&color_picker).large()
// Shown while no color is selected.
ColorSelect::new(&color_picker).placeholder("Pick a color")VerticalLayout {
spacing: 12px;
alignment: start;
ColorPicker {
field: true;
value: #6366f1;
}
ColorPicker {
field: true;
empty: true;
placeholder: "Pick a color";
}
}#Color Theme Editor
struct ThemeEditor {
primary_color: Entity<ColorPickerState>,
secondary_color: Entity<ColorPickerState>,
accent_color: Entity<ColorPickerState>,
}
impl ThemeEditor {
fn new(window: &mut Window, cx: &mut Context<Self>) -> Self {
let primary_color = cx.new(|cx|
ColorPickerState::new(window, cx)
.default_value(cx.theme().primary)
);
let secondary_color = cx.new(|cx|
ColorPickerState::new(window, cx)
.default_value(cx.theme().secondary)
);
let accent_color = cx.new(|cx|
ColorPickerState::new(window, cx)
.default_value(cx.theme().accent)
);
Self {
primary_color,
secondary_color,
accent_color,
}
}
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
v_flex()
.gap_4()
.child(
h_flex()
.gap_2()
.items_center()
.child("Primary Color:")
.child(ColorPicker::new(&self.primary_color))
)
.child(
h_flex()
.gap_2()
.items_center()
.child("Secondary Color:")
.child(ColorPicker::new(&self.secondary_color))
)
.child(
h_flex()
.gap_2()
.items_center()
.child("Accent Color:")
.child(ColorPicker::new(&self.accent_color))
)
}
}// Three field pickers, one per theme role. Each is the same control and its
// own caller-owned value — GPUI's editor, which the Slint example shows as
// the two pickers of its Color Select card.
let editor = body_box(
column![
editor_row(app, slot::PRIMARY, "Primary"),
editor_row(app, slot::SECONDARY, "Secondary"),
editor_row(app, slot::ACCENT, "Accent"),
]
.spacing(12.0),
);#Brand Color Selector
use gpui_kit::component::{Sizable as _};
let brand_colors = vec![
Hsla::parse_hex("#FF6B6B").unwrap(), // Brand Red
Hsla::parse_hex("#4ECDC4").unwrap(), // Brand Teal
Hsla::parse_hex("#45B7D1").unwrap(), // Brand Blue
Hsla::parse_hex("#96CEB4").unwrap(), // Brand Green
Hsla::parse_hex("#FFEAA7").unwrap(), // Brand Yellow
];
ColorPicker::new(&color_picker)
.featured_colors(brand_colors)
.label("Brand Color")
.large()#Toolbar Color Picker
use gpui_kit::component::{Sizable as _, IconName);
ColorPicker::new(&text_color_picker)
.icon(IconName::Type)
.small()
.anchor(Anchor::BottomLeft)#Color Palette Builder
struct ColorPalette {
colors: Vec<Entity<ColorPickerState>>,
}
impl ColorPalette {
fn add_color(&mut self, window: &mut Window, cx: &mut Context<Self>) {
let color_picker = cx.new(|cx| ColorPickerState::new(window, cx));
// Subscribe to color changes
cx.subscribe(&color_picker, |this, _, ev, _| match ev {
ColorPickerEvent::Change(color) => {
if let Some(color) = color {
this.update_palette_preview();
}
}
});
self.colors.push(color_picker);
cx.notify();
}
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
h_flex()
.gap_2()
.children(
self.colors.iter().map(|color_picker| {
ColorPicker::new(color_picker).small()
})
)
.child(
Button::new("add-color")
.icon(IconName::Plus)
.ghost()
.on_click(cx.listener(|this, _, window, cx| {
this.add_color(window, cx);
}))
)
}
}#With Color Validation
let color_picker = cx.new(|cx| ColorPickerState::new(window, cx));
let _subscription = cx.subscribe(&color_picker, |this, _, ev, _| match ev {
ColorPickerEvent::Change(color) => {
if let Some(color) = color {
// Validate color accessibility
if this.validate_contrast(color) {
this.apply_color(color);
} else {
this.show_contrast_warning();
}
}
}
});#Color Selection Interface
#Color Palettes
The color picker includes predefined color palettes organized by color family:
- Stone: Neutral grays and stone colors
- Red: Red color variations from light to dark
- Orange: Orange color variations
- Yellow: Yellow color variations
- Green: Green color variations
- Cyan: Cyan color variations
- Blue: Blue color variations
- Purple: Purple color variations
- Pink: Pink color variations
Each palette provides multiple shades and tints of the base color, allowing for precise color selection.
#Featured Colors Section
A customizable section at the top of the picker that displays frequently used or brand colors. If not specified, defaults to theme colors:
- Primary colors from the current theme
- Light variants of theme colors
- Essential UI colors (red, blue, green, yellow, cyan, magenta)
#Hex Input Field
A text input field that allows direct entry of hex color values:
- Supports standard 6-digit hex format (#RRGGBB)
- Real-time validation and preview
- Updates color picker state automatically
- Press Enter to confirm selection
#Color Formats
#RGB (Red, Green, Blue)
Colors are internally represented using GPUI’s Hsla format but can be converted to RGB:
let color = cx.theme().blue;
// Access RGB components through Hsla methods#HSL (Hue, Saturation, Lightness)
Native format used by the color picker:
use gpui_kit::Hsla;
// Create HSL color
let color = Hsla::hsl(240.0, 100.0, 50.0); // Blue color
// Access components
let hue = color.h;
let saturation = color.s;
let lightness = color.l;#Hex Format
Standard web hex format with # prefix:
// Convert color to hex
let hex_string = color.to_hex(); // Returns "#3366FF"
// Parse hex string to color
if let Ok(color) = Hsla::parse_hex("#3366FF") {
// Use parsed color
}#Alpha Channel
Full alpha channel support for transparency:
use gpui_kit::hsla;
// Create color with alpha
let semi_transparent = hsla(0.5, 0.8, 0.6, 0.7); // 70% opacity
// Modify existing color opacity
let transparent_blue = cx.theme().blue.opacity(0.5);The color picker preserves alpha values when selecting colors and allows modification through the alpha component of HSLA colors.
#API Reference
#Swatch
| Name | Kind | Type | Default |
|---|---|---|---|
name | field | string | |
fill | field | color |
#ColorPicker
A compact color control. Mirrors GPUI Component's `ColorPicker` and `ColorSelect`: a square trigger, or a framed field with the hex value. The popup is the palette and HSLA panel.
| Name | Kind | Type | Default | Description |
|---|---|---|---|---|
swatches | in | [Swatch] | ||
value | in-out | color | #6366f1 | |
empty | in-out | bool | false | No color yet. The field shows `placeholder` instead of a hex value. |
placeholder | in | string | "Pick a color" | |
field | in | bool | false | Draw the trigger as a framed field, like a Select. |
changed | callback | (value: color) |