Cognition

Components

Input Group

An Input with attached context: a leading or trailing icon, a prefix or suffix, or a single action, all inside one visual boundary so the field still reads as a single control.

Input Group is a composed pattern built on the Input primitive. It does not replace Input. It extends it for the cases where attached context is required.

Preview

Rendered with live Cognition tokens. Focus it and the whole group rings, no dark: classes.

Variants

<InputGroup leadingIcon={<Search />} />
<InputGroup trailingIcon={<Check />} />
https://
<InputGroup leadingText="https://" />
USD
<InputGroup trailingText="USD" />
<InputGroup trailingAction={<Button size="sm">…</Button>} />
<InputGroup leadingIcon={<AtSign />} trailingAction={…} />

Leading and trailing slots compose: an icon, a prefix or suffix, an action, or an icon paired with an action.

States

Default. Empty and at rest.

Focused. The whole group rings (shown statically here).

Filled. Holds a value.

<InputGroup error />
<InputGroup disabled />

Focus rings the entire boundary, not just the inner field. Error swaps the border and ring to the danger token.

API

Prop
Type
Default
Description
leadingIcon
ReactNode
undefined
Icon rendered before the field, for context such as search or email.
trailingIcon
ReactNode
undefined
Icon rendered after the field, for status such as a validation check.
leadingText
string
undefined
Static prefix shown before the value, for example a protocol or currency.
trailingText
string
undefined
Static suffix shown after the value, for example a unit or domain.
trailingAction
ReactNode
undefined
A single action attached to the trailing edge, usually a small Button.
error
boolean
false
Applies the danger border and ring to signal an invalid value.
disabled
boolean
false
Dims the group and blocks input.

All standard input props pass through to the inner field.

Don't and Do

Don't

Don't stack multiple trailing actions in one group. Two or more competing buttons crowd the field and blur which one the value belongs to. When more than one action is needed, move them out of the field into a different pattern.

Do
<InputGroup
  leadingIcon={<Search />}
  placeholder="Search the workspace..."
  trailingAction={<Button size="sm">Search</Button>}
/>

Use it to add contextual affordance to an input without breaking the field's visual boundary.

import { InputGroup } from "@/components/ui/input-group";
import { Globe } from "lucide-react";

export function SiteField() {
  return (
    <InputGroup
      leadingIcon={<Globe />}
      leadingText="https://"
      trailingText=".distyl.ai"
      placeholder="workspace"
    />
  );
}