Input Group
Combine an input with adjacent addons and controls.
use herogpui::components::input_group::InputGroup;InputGroup 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.
Prefix and suffix text uses 14px text with 20px lines alongside the input. Runs in a view's render: self is the view holding this example's state and cx is its Context<Self>.
use herogpui::prelude::{Input, InputAddon, InputGroup};
use herogpui::gpui::prelude::*;
use herogpui::gpui::px;
use herogpui::gpui;
gpui::div()
.flex()
.flex_col()
.w(px(256.))
.gap(px(12.))
.children(vec![InputGroup::new()
.label("Website")
.prefix(InputAddon::new("https://"))
.input(
Input::new(self.demo_text("ig-usage", "", cx))
// v3's group example seeds the input with
// `defaultValue`; `value` is the controlled
// spelling of the same thing.
.default_value("example.com")
.placeholder("example.com"),
)
.into_any_element()])`padding_x` overrides the held field's exposed edges only -- a side with an addon keeps the addon's inset -- `height` resizes the single-line group, and `is_bare` drops the group's 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::{Input, InputAddon, InputGroup};
use herogpui::gpui::prelude::*;
use herogpui::gpui::px;
use herogpui::gpui;
gpui::div()
.flex()
.flex_col()
.w(px(256.))
.gap(px(12.))
.children(vec![InputGroup::new()
.label("Compact")
.height(px(28.))
.padding_x(px(8.))
.is_bare(true)
.font_family(MONO_FONT)
.prefix(InputAddon::new("$"))
.input(
Input::new(self.demo_text("ig-custom-box", "", cx))
.placeholder("0.00"),
)
.into_any_element()])Also calls MONO_FONT (included when you copy).
`group_hover_bg` names the fill the hovered group box 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, Input, InputAddon, InputGroup};
use herogpui::gpui::prelude::*;
use herogpui::gpui::px;
use herogpui::gpui;
gpui::div()
.flex()
.flex_col()
.w(px(256.))
.gap(px(12.))
.children(vec![InputGroup::new()
.label("Website")
.group_hover_bg(cx.colors().accent.soft())
.prefix(InputAddon::new("https://"))
.input(
Input::new(self.demo_text("ig-hover-colour", "", cx))
.placeholder("example.com"),
)
.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, Input, InputAddon, InputGroup};
use herogpui::gpui::prelude::*;
use herogpui::gpui::px;
use herogpui::gpui;
gpui::div()
.flex()
.flex_col()
.w(px(256.))
.gap(px(12.))
.children(vec![
InputGroup::new()
.label("Primary")
.prefix(InputAddon::new("@"))
.input(
Input::new(self.demo_text("ig-v-primary", "", cx))
.placeholder("name@email.com"),
)
.into_any_element(),
InputGroup::new()
.label("Secondary")
.variant(FieldVariant::Secondary)
.prefix(InputAddon::new("@"))
.input(
Input::new(self.demo_text("ig-v-secondary", "", cx))
.placeholder("name@email.com"),
)
.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, Input, InputAddon, InputGroup, Surface};
use herogpui::gpui::prelude::*;
use herogpui::gpui::px;
use herogpui::gpui;
gpui::div()
.flex()
.flex_col()
.w(px(256.))
.gap(px(12.))
.children(vec![Surface::new()
.padding(px(24.))
.child(
InputGroup::new()
.label("Handle")
.variant(FieldVariant::Secondary)
.prefix(InputAddon::new("@"))
.input(
Input::new(self.demo_text("ig-surface", "", cx))
.placeholder("name@email.com"),
),
)
.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::{Input, InputGroup, Spinner, SpinnerSize};
use herogpui::gpui::prelude::*;
use herogpui::gpui::px;
use herogpui::gpui;
gpui::div()
.flex()
.flex_col()
.w(px(256.))
.gap(px(12.))
.children(vec![InputGroup::new()
.label("Checking availability")
.input(Input::new(self.demo_text("ig-loading", "example", cx)))
.suffix(
gpui::div()
.pr(px(8.))
.child(Spinner::new("input-group-spinner").size(SpinnerSize::Sm)),
)
.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::{Input, InputAddon, InputGroup};
use herogpui::gpui::prelude::*;
use herogpui::gpui::px;
use herogpui::gpui;
gpui::div()
.flex()
.flex_col()
.w(px(256.))
.gap(px(12.))
.children(vec![InputGroup::new()
.label("Website")
.is_required(true)
.prefix(InputAddon::new("https://"))
.input(
Input::new(self.demo_text("ig-required", "", cx))
.placeholder("name@email.com"),
)
.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::{Input, InputAddon, InputGroup};
use herogpui::gpui::prelude::*;
use herogpui::gpui::px;
use herogpui::gpui;
gpui::div()
.flex()
.flex_col()
.w(px(256.))
.gap(px(12.))
.children(vec![InputGroup::new()
.label("Website")
.is_disabled(true)
.prefix(InputAddon::new("https://"))
.input(
Input::new(self.demo_text("ig-disabled", "example.com", cx))
.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::{Input, InputAddon, InputGroup};
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![InputGroup::new()
.label("Website")
.full_width(true)
.prefix(InputAddon::new("https://"))
.input(
Input::new(self.demo_text("ig-full", "", cx))
.placeholder("name@email.com"),
)
.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::{Input, InputAddon, InputGroup};
use herogpui::gpui::prelude::*;
use herogpui::gpui::px;
use herogpui::gpui;
gpui::div()
.flex()
.flex_col()
.w(px(256.))
.gap(px(12.))
.children(vec![InputGroup::new()
.prefix(InputAddon::new("https://"))
.input(
Input::new(self.demo_text("ig-text-prefix", "", cx))
.placeholder("example.com"),
)
.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::{Input, InputAddon, InputGroup};
use herogpui::gpui::prelude::*;
use herogpui::gpui::px;
use herogpui::gpui;
gpui::div()
.flex()
.flex_col()
.w(px(256.))
.gap(px(12.))
.children(vec![InputGroup::new()
.input(
Input::new(self.demo_text("ig-text-suffix", "", cx))
.placeholder("example"),
)
.suffix(InputAddon::new(".com"))
.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, Input, InputAddon, InputGroup, icons};
use herogpui::gpui::prelude::*;
use herogpui::gpui::px;
use herogpui::gpui;
gpui::div()
.flex()
.flex_col()
.w(px(256.))
.gap(px(12.))
.children(vec![InputGroup::new()
.prefix(gpui::div().pl(px(12.)).child(gpui::svg()
.size(px(16.))
.path(icons::MAIL)
.text_color(cx.colors().foreground)
.into_any_element()))
.input(
Input::new(self.demo_text("ig-icon-text", "", cx))
.placeholder("name")
)
.suffix(InputAddon::new("@example.com"))
.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, Button, Input, InputGroup, Size, Variant, icons};
use herogpui::gpui::prelude::*;
use herogpui::gpui::px;
use herogpui::gpui;
gpui::div()
.flex()
.flex_col()
.w(px(256.))
.gap(px(12.))
.children(vec![InputGroup::new()
.label("Website")
.input(Input::new(self.demo_text("ig-copy", "example.com", cx)))
.suffix(
gpui::div().pr(px(4.)).child(
Button::new("input-group-copy-btn")
.is_icon_only(true)
.variant(Variant::Ghost)
.size(Size::Sm)
.child(gpui::svg()
.size(px(16.))
.path(icons::COPY)
.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, Button, Input, InputGroup, Size, Variant, icons};
use herogpui::gpui::prelude::*;
use herogpui::gpui::px;
use herogpui::gpui;
gpui::div()
.flex()
.flex_col()
.w(px(256.))
.gap(px(12.))
.children(vec![InputGroup::new()
.prefix(gpui::div().pl(px(12.)).child(gpui::svg()
.size(px(16.))
.path(icons::KEY)
.text_color(cx.colors().foreground)
.into_any_element()))
.input(Input::new(self.demo_text(
"ig-key",
"sk_live_51H...",
cx,
)))
.suffix(
gpui::div().pr(px(4.)).child(
Button::new("input-group-key-copy")
.is_icon_only(true)
.variant(Variant::Ghost)
.size(Size::Sm)
.child(gpui::svg()
.size(px(16.))
.path(icons::COPY)
.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, Button, Input, InputGroup, InputType, Size, Variant, icons};
use herogpui::gpui::prelude::*;
use herogpui::gpui::px;
use herogpui::gpui;
let ig_reveal = self.demo_flag("ig-reveal", false);
gpui::div()
.flex()
.flex_col()
.w(px(256.))
.gap(px(12.))
.children(vec![InputGroup::new()
.label("Password")
.input(
Input::new(self.demo_text("ig-pw", "correct horse", cx)).input_type(
if ig_reveal {
InputType::Text
} else {
InputType::Password
},
),
)
.suffix(
gpui::div().pr(px(4.)).child(
Button::new("input-group-pw-toggle")
.is_icon_only(true)
.variant(Variant::Ghost)
.size(Size::Sm)
.child(gpui::svg()
.size(px(16.))
.path(if ig_reveal {
icons::EYE_OFF
} else {
icons::EYE
})
.text_color(cx.colors().foreground)
.into_any_element())
.on_press(cx.listener(move |this, _, _, cx| {
this.set_demo_flag("ig-reveal", !ig_reveal);
cx.notify();
})),
),
)
.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, Input, InputGroup, Kbd, KbdVariant, icons};
use herogpui::gpui::prelude::*;
use herogpui::gpui::px;
use herogpui::gpui;
gpui::div()
.flex()
.flex_col()
.w(px(256.))
.gap(px(12.))
.children(vec![InputGroup::new()
.prefix(gpui::div().pl(px(12.)).child(gpui::svg()
.size(px(16.))
.path(icons::SEARCH)
.text_color(cx.colors().foreground)
.into_any_element()))
.input(
Input::new(self.demo_text("ig-kbd", "", cx)).placeholder("Search"),
)
.suffix(
gpui::div()
.pr(px(8.))
.flex()
.gap(px(4.))
.child(Kbd::new().variant(KbdVariant::Light).child("Ctrl"))
.child(Kbd::new().variant(KbdVariant::Light).child("K")),
)
.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::{Chip, ChipLabel, ChipVariant, Color, Input, InputGroup, Size};
use herogpui::gpui::prelude::*;
use herogpui::gpui::px;
use herogpui::gpui;
gpui::div()
.flex()
.flex_col()
.w(px(256.))
.gap(px(12.))
.children(vec![InputGroup::new()
.label("Plan")
.input(Input::new(self.demo_text("ig-badge", "Pro", cx)))
.suffix(
gpui::div().pr(px(8.)).child(
Chip::new()
.size(Size::Sm)
.variant(ChipVariant::Soft)
.color(Color::Accent)
.child(ChipLabel::new().child("Trial")),
),
)
.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::{Input, InputAddon, InputGroup};
use herogpui::gpui::prelude::*;
use herogpui::gpui::px;
use herogpui::gpui;
gpui::div()
.flex()
.flex_col()
.w(px(256.))
.gap(px(12.))
.children(vec![InputGroup::new()
.label("Website")
.is_required(true)
.is_invalid(true)
.error_message("Enter a valid URL")
.prefix(InputAddon::new("https://"))
.input(Input::new(self.demo_text("ig-invalid", "not a url", cx)))
.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, Input, InputGroup, icons};
use herogpui::gpui::prelude::*;
use herogpui::gpui::px;
use herogpui::gpui;
gpui::div()
.flex()
.flex_col()
.w(px(256.))
.gap(px(12.))
.children(vec![InputGroup::new()
.prefix(gpui::div().pl(px(12.)).child(gpui::svg()
.size(px(16.))
.path(icons::GLOBE)
.text_color(cx.colors().foreground)
.into_any_element()))
.input(
Input::new(self.demo_text("ig-prefix-icon", "", cx))
.placeholder("name@email.com"),
)
.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, Input, InputGroup, icons};
use herogpui::gpui::prelude::*;
use herogpui::gpui::px;
use herogpui::gpui;
gpui::div()
.flex()
.flex_col()
.w(px(256.))
.gap(px(12.))
.children(vec![InputGroup::new()
.input(
Input::new(self.demo_text("ig-suffix-icon", "", cx))
.placeholder("name@email.com"),
)
.suffix(gpui::div().pr(px(12.)).child(gpui::svg()
.size(px(16.))
.path(icons::CHECK)
.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, Input, InputGroup, icons};
use herogpui::gpui::prelude::*;
use herogpui::gpui::px;
use herogpui::gpui;
gpui::div()
.flex()
.flex_col()
.w(px(256.))
.gap(px(12.))
.children(vec![InputGroup::new()
.prefix(gpui::div().pl(px(12.)).child(gpui::svg()
.size(px(16.))
.path(icons::SEARCH)
.text_color(cx.colors().foreground)
.into_any_element()))
.input(
Input::new(self.demo_text("ig-both", "", cx))
.placeholder("Search..."),
)
.suffix(gpui::div().pr(px(12.)).child(gpui::svg()
.size(px(16.))
.path(icons::CLOSE)
.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, InputGroup, TextArea, icons};
use herogpui::gpui::prelude::*;
use herogpui::gpui::px;
use herogpui::gpui;
gpui::div()
.flex()
.flex_col()
.w(px(256.))
.gap(px(12.))
.children(vec![InputGroup::new()
.label("Note")
.prefix(
// The group's `:has(textarea)` rule owns the 8px
// addon top padding; only the addon's horizontal
// inset is spelled here, as on every bare-icon
// addon slot.
gpui::div().pl(px(12.)).child(gpui::svg()
.size(px(16.))
.path(icons::COPY)
.text_color(cx.colors().foreground)
.into_any_element()),
)
.text_area(
TextArea::new(self.demo_text("ig-area", "", cx))
.placeholder("Assign tasks or ask anything...")
.rows(3),
)
.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::{Input, InputAddon, InputGroup};
use herogpui::gpui::prelude::*;
use herogpui::gpui::px;
use herogpui::gpui;
gpui::div()
.flex()
.flex_col()
.w(px(256.))
.gap(px(12.))
.children(vec![InputGroup::new()
.label("Amount")
.description("Billed in US dollars.")
.prefix(InputAddon::new("$"))
.input(
Input::new(self.demo_text("ig-example", "", cx)).placeholder("0.00"),
)
.suffix(InputAddon::new("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::{InputGroup, TextArea};
use herogpui::gpui::prelude::*;
use herogpui::gpui::px;
use herogpui::gpui;
gpui::div()
.flex()
.flex_col()
.w(px(256.))
.gap(px(12.))
.children(vec![InputGroup::new()
.label("Changelog")
.description("Markdown is supported.")
.text_area(
TextArea::new(self.demo_text("ig-area-2", "", cx))
.placeholder("Share a quick project update...")
.rows(4),
)
.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::{Input, InputAddon, InputGroup};
use herogpui::gpui::prelude::*;
use herogpui::gpui::px;
use herogpui::gpui;
gpui::div()
.flex()
.flex_col()
.w(px(256.))
.gap(px(12.))
.children(vec![InputGroup::new()
.label("Amount")
.description("Charged monthly.")
.prefix(InputAddon::new("$"))
.input(Input::new(self.group_amount.clone()).placeholder("0.00"))
.suffix(InputAddon::new("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::{Button, FieldVariant, Input, InputGroup, Size};
use herogpui::gpui::prelude::*;
use herogpui::gpui::px;
use herogpui::gpui;
gpui::div()
.flex()
.flex_col()
.w(px(256.))
.gap(px(12.))
.children(vec![InputGroup::new()
.variant(FieldVariant::Secondary)
.input(Input::new(self.input_email.clone()).placeholder("Email"))
.suffix(
gpui::div()
.pl(px(8.))
.pr(px(4.))
.child(Button::new("input-group-send").label("Send").size(Size::Sm)),
)
.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"
};Anatomy
Rust types
Input Group 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 Input Group 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 | The width lands on the outer wrapper as well as the group box: gpui resolves a percentage against the parent, and a content-sized parent stretches nothing. |
variant(FieldVariant) | FieldVariant::Primary | FieldVariant::Secondary | Primary carries the field shadow; secondary drops it and paints the neutral default palette. |
// 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: 32 entries ported, 4 partial, 11 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 |
|---|---|---|---|
input(Partial | AnyElement | | — | Arbitrary children render after the field, and the typed slots compose the parts; the children-as-a-function form has no analogue, holds one field slot, and a disabled group dims these children without being able to make them pointer- or tab-inert. |
full_width( | true | | false | The width lands on the outer wrapper as well as the group box: gpui resolves a percentage against the parent, and a content-sized parent stretches nothing. |
focus_ring( | true | | true | GPUI extension: hides only the group's visual focus ring while retaining the held field's focus and editing behavior. |
id( | string | — | Optional GPUI identity. A named group reports role=group; an unnamed one produces no AccessKit node. |
variant( | FieldVariant:: | FieldVariant:: | Primary carries the field shadow; secondary drops it and paints the neutral default palette. |
is_disabled( | true | | false | Takes this from the TextField around the group; folds it onto InputGroup and propagates it to the held field, so a group-disabled control tracks no focus and answers no keys, while one dim covers the whole box. |
is_invalid( | true | | false | The pinned TextField composition owns the flag; folds it on and drives the group's invalid chrome with it. |
is_required( | true | | false | Marks the folded label as required. |
label( | AnyElement | — | Composes Label as a sibling; folds it above the group box. |
description( | AnyElement | — | Composes Description as a sibling; folds it below the group box. |
error_message( | AnyElement | — | Shown only while the group is invalid, as the TextField does. |
input(Partial | 'primary' | | 'primary' | The group paints the shared chrome, so the held field's own variant flag is shadowed by the group's; the field accepts it through its builder. |
input( | string | 'text' | Password masking and numeric filtering ride on the held field. |
input( | string | — | The controlled value is the bound InputState's text. |
input( | string | — | Seeds the held field's state once. |
input( | string | — | Placeholder of the held field. |
input( | true | | — | Read-only keeps the field focusable while edits are blocked. |
text_area( | number | — | Visible line count; three 20px lines plus 16px vertical padding per the field's own rows math. |
text_area(Partial | 'primary' | | 'primary' | The group paints the shared chrome, so the multi-line field's own variant flag is shadowed by the group's; the field accepts it through its builder. |
text_area( | string | — | The controlled value is the bound InputState's text. |
text_area( | string | — | Seeds the held field's state once. |
text_area( | string | — | Placeholder of the held field. |
text_area( | true | | — | Read-only keeps the field focusable while edits are blocked. |
prefix( | AnyElement | — | Leading addon content: text, icons or anything else, drawn in placeholder colour. |
suffix( | AnyElement | — | Trailing addon content: text, buttons, badges or keyboards, keeping their own handlers. |
Not ported11 HeroUI entries not ported
| HeroUI v3 | Why it is not ported |
|---|---|
InputGroup.className | Browser classes are unavailable. |
InputGroup.style | Browser inline styles are unavailable. |
InputGroup.aria-label | No accessibility tree to label. |
InputGroup.aria-labelledby | No accessibility tree to label. |
InputGroup.aria-describedby | No accessibility tree to describe. |
InputGroup.aria-details | No accessibility tree to describe. |
InputGroup.role | No accessibility role mapping. |
InputGroup.Input.className | Browser classes are unavailable. |
InputGroup.TextArea.className | Browser classes are unavailable. |
InputGroup.Prefix.className | Browser classes are unavailable. |
InputGroup.Suffix.className | Browser classes are unavailable. |
Parts
| Part | Description |
|---|---|
InputGroup | The shared field chrome box: variant palette, focus-within ring, hover fill, invalid chrome, one disabled dim over the box, and the handleClick focus transfer to the held field. |
InputGroup | The caller's Input rendered chrome-less: transparent, unrounded, flex_1 between the addons, with the padding dropped on whichever side an addon occupies. |
InputGroup | The same shared chrome around a multi-line field; its presence switches the group to items-start with auto height and 8px addon top padding. |
InputGroup | Leading addon slot in placeholder colour with a 12px inset; a click on it is the group root's business, which focuses the held field. |
InputGroup | Trailing addon slot; a focusable suffix (a copy Button) keeps its own press, and the group's click-to-focus hands the focus back to the field afterwards. |
States
| State | Builder | Description |
|---|---|---|
Hover | field.hover() | Hovering paints bg-field-hover with --field-border-hover, and secondary swaps only the fill for --default-hover; the refinement is baked off while the focus is inside, and a disabled group paints none (status-disabled is hit testing: none first). |
Focus Within | apply_field_chrome(focus_within) | The held field's focus drives the group's status-focused-field ring, field-border-focus endpoint and pinned --field-focus fill. |
Invalid | apply_field_chrome(is_invalid) | Status-invalid-field draws the danger outline and focused danger ring, with the --field-focus fill on both variants. |
DisabledPartial | is_disabled | The flag propagates to the held field, which stops tracking focus and answering keys, and one dim covers the whole group box — field, addon slots and any composed children; the folded label dims itself beside it. Only the held field is truly inert: GPUI cannot make arbitrary children pointer- or tab-inert, so a composed button in a disabled group still answers. |