<InputGroup>
<InputGroup.Input aria-label="Subdomain" maxLength={24} />
<InputGroup.Suffix>.workers.dev</InputGroup.Suffix>
</InputGroup>

Installation

Barrel

import { InputGroup } from "@cloudflare/kumo";

Granular

import { InputGroup } from "@cloudflare/kumo/components/input-group";

Examples

Icon

Use Addon to place an icon at the start of the input as a visual identifier.

<InputGroup>
<InputGroup.Addon>
  <LinkIcon className="fill-kumo-subtle" />
</InputGroup.Addon>
<InputGroup.Input placeholder="Paste a link..." aria-label="Link" />
</InputGroup>

Text

Use Addon to place text prefixes or suffixes alongside the input.

<>
{/* Start only */}
<InputGroup>
  <InputGroup.Addon>@</InputGroup.Addon>
  <InputGroup.Input placeholder="username" aria-label="Username" />
</InputGroup>

{/* End only */}

<InputGroup>
<InputGroup.Input placeholder="email" aria-label="Email" />
<InputGroup.Addon align="end">@example.com</InputGroup.Addon>
</InputGroup>

{/* Both sides */}

<InputGroup>
  <InputGroup.Addon>/api/</InputGroup.Addon>
  <InputGroup.Input placeholder="endpoint" aria-label="API path" />
  <InputGroup.Addon align="end">.json</InputGroup.Addon>
</InputGroup>
</>

Button

Place InputGroup.Button inside an Addon for actions that operate directly on the input value, such as reveal/hide or clear.

{/* Reveal / hide password */}
<InputGroup>
<InputGroup.Input
  type={show ? "text" : "password"}
  defaultValue="password"
  aria-label="Password"
/>
<InputGroup.Addon align="end" className="pr-1">
  <InputGroup.Button
    variant="ghost"
    size="sm"
    aria-label={show ? "Hide password" : "Show password"}
    onClick={() => setShow(!show)}
  >
    {show ? <EyeSlashIcon size={16} /> : <EyeIcon size={16} />}
  </InputGroup.Button>
</InputGroup.Addon>
</InputGroup>

{/* Clearable search */}

<InputGroup>
<InputGroup.Input
  value={searchValue}
  placeholder="Search"
  aria-label="Search"
  onChange={(e) => setSearchValue(e.target.value)}
/>
{searchValue && (
  <InputGroup.Addon align="end" className="pr-1">
    <InputGroup.Button
      variant="ghost"
      size="sm"
      aria-label="Delete search"
      onClick={() => setSearchValue("")}
    >
      <XIcon size={16} />
    </InputGroup.Button>
  </InputGroup.Addon>
)}
</InputGroup>

Button with Tooltip

Pass a tooltip prop to InputGroup.Button to show a tooltip on hover. When no explicit aria-label is provided, the button derives it from a string tooltip value.

<InputGroup>
<InputGroup.Addon>
  <MagnifyingGlassIcon className="text-kumo-subtle" />
</InputGroup.Addon>
<InputGroup.Input
  placeholder="Search with query language..."
  aria-label="Search"
/>
<InputGroup.Addon align="end" className="pr-1">
  <InputGroup.Button
    size="sm"
    tooltip="Query language help"
    onClick={() => setShowHelp(true)}
  >
    <QuestionIcon size={16} />
  </InputGroup.Button>
</InputGroup.Addon>
</InputGroup>

Kbd

Place a keyboard shortcut hint inside an end Addon. Use a bare kbd element — no border or background needed.

<InputGroup>
<InputGroup.Addon>
  <MagnifyingGlassIcon className="text-kumo-subtle" />
</InputGroup.Addon>
<InputGroup.Input placeholder="Search..." aria-label="Search" />
<InputGroup.Addon align="end">
  <kbd>⌘K</kbd>
</InputGroup.Addon>
</InputGroup>

Loading

Place a Loader inside an end Addon as a status indicator while validating the input value.

<InputGroup>
<InputGroup.Input defaultValue="kumo" aria-label="kumo" />
<InputGroup.Addon align="end">
  <Loader className="fill-kumo-subtle" />
</InputGroup.Addon>
</InputGroup>

Inline Suffix

Suffix renders text that flows seamlessly next to the typed value — useful for domain inputs like .workers.dev. Pair with a status icon Addon to show validation state.

This subdomain is unavailable
<InputGroup label="Subdomain" error={errorState}>
<InputGroup.Input aria-label="Subdomain" maxLength={24} value={value} onChange={handleChange} />
<InputGroup.Suffix>.workers.dev</InputGroup.Suffix>
{status === "loading" && (
  <InputGroup.Addon align="end"><Loader className="fill-kumo-subtle" /></InputGroup.Addon>
)}
{status === "success" && (
  <InputGroup.Addon align="end">
    <CheckCircleIcon weight="duotone" className="fill-kumo-success" />
  </InputGroup.Addon>
)}
{status === "error" && (
  <InputGroup.Addon align="end">
    <XCircleIcon weight="duotone" className="fill-kumo-danger" />
  </InputGroup.Addon>
)}
</InputGroup>

Sizes

Four sizes: xs, sm, base (default), and lg. The size applies to the entire group. Use the label prop on InputGroup for built-in Field support.

<>
{/* Extra small */}
<InputGroup size="xs" label="Extra Small">
  <InputGroup.Addon>
    <MagnifyingGlassIcon className="text-kumo-subtle" />
  </InputGroup.Addon>
  <InputGroup.Input placeholder="Extra small input" />
</InputGroup>

{/* Small */}

<InputGroup size="sm" label="Small">
<InputGroup.Addon>
  <MagnifyingGlassIcon className="text-kumo-subtle" />
</InputGroup.Addon>
<InputGroup.Input placeholder="Small input" />
</InputGroup>

{/* Base (default) */}

<InputGroup label="Base (default)">
<InputGroup.Addon>
  <MagnifyingGlassIcon className="text-kumo-subtle" />
</InputGroup.Addon>
<InputGroup.Input placeholder="Base input" />
</InputGroup>

{/* Large */}

<InputGroup size="lg" label="Large">
  <InputGroup.Addon>
    <MagnifyingGlassIcon className="text-kumo-subtle" />
  </InputGroup.Addon>
  <InputGroup.Input placeholder="Large input" />
</InputGroup>
</>

States

Various input states including error, disabled, and with description. Pass label, error, and description props directly to InputGroup.

Please enter a valid email address

Must be at least 8 characters

<>
{/* Error state */}
<InputGroup
  label="Error State"
  error={{ message: "Please enter a valid email address", match: true }}
>
  <InputGroup.Input
    type="email"
    defaultValue="invalid-email"
  />
  <InputGroup.Addon align="end">@example.com</InputGroup.Addon>
</InputGroup>

{/* Disabled */}

<InputGroup label="Disabled" disabled>
<InputGroup.Addon>
  <MagnifyingGlassIcon className="text-kumo-subtle" />
</InputGroup.Addon>
<InputGroup.Input placeholder="Search..." />
</InputGroup>

{/* With description and tooltip */}

<InputGroup
  label="With Description"
  description="Must be at least 8 characters"
  labelTooltip="Your password is stored securely"
>
  <InputGroup.Input
    type={show ? "text" : "password"}
    placeholder="Enter password"
  />
  <InputGroup.Addon align="end">
    <InputGroup.Button
      variant="ghost"
      size="sm"
      aria-label={show ? "Hide password" : "Show password"}
      onClick={() => {}}
    >
      {show ? <EyeSlashIcon size={14} /> : <EyeIcon size={14} />}
    </InputGroup.Button>
  </InputGroup.Addon>
</InputGroup>
</>

API Reference

InputGroup

The root container that provides context to all child components. Accepts Field props (label, description, error) and wraps content in a Field when label is provided.

PropTypeDefaultDescription
labelReactNode-The label content — can be a string or any React node.
descriptionReactNode-Helper text displayed below the control (hidden when `error` is present).
errorobject-Validation error with a message and a browser `ValidityState` match key.
requiredboolean-When explicitly `false`, shows gray "(optional)" text after the label. When `true` or `undefined`, no indicator is shown.
labelTooltipReactNode-Tooltip content displayed next to the label via an info icon.
htmlForstring--
classNamestring--
idstring--
langstring--
titlestring--
childrenReactNode--
size"xs" | "sm" | "base" | "lg"--
disabledboolean--
focusMode"container" | "individual"--

InputGroup.Input

The text input element. Inherits size, disabled, and error from InputGroup context. Accepts all standard input attributes except Field-related props which are handled by the parent.

PropTypeDefault

No component-specific props. Accepts standard HTML attributes.

InputGroup.Addon

Container for icons, text, or compact buttons positioned at the start or end of the input.

PropTypeDefault

No component-specific props. Accepts standard HTML attributes.

InputGroup.Suffix

Inline text that flows seamlessly next to the typed value (e.g., .workers.dev). The input width adjusts automatically as the user types.

PropTypeDefault

No component-specific props. Accepts standard HTML attributes.

Validation Error Types

When using error as an object, the match property corresponds to HTML5 ValidityState values:

MatchDescription
valueMissingRequired field is empty
typeMismatchValue doesn’t match type (e.g., invalid email)
patternMismatchValue doesn’t match pattern attribute
tooShortValue shorter than minLength
tooLongValue longer than maxLength
rangeUnderflowValue less than min
rangeOverflowValue greater than max
trueAlways show error (for server-side validation)

Accessibility

Label Requirement

InputGroup requires an accessible name via one of:

  • label prop on InputGroup (renders a visible label with built-in Field support)

  • aria-label on InputGroup.Input for inputs without a visible label

  • aria-labelledby on InputGroup.Input for custom label association

Missing accessible names trigger console warnings in development.

Group Role

InputGroup automatically renders with role="group", which semantically associates the input with its addons for assistive technologies.