Light

Input Group

Combines an input with addons, buttons, or text for composite form controls.

Spec · from metadata

When to use

  • Input with a leading/trailing icon or button (e.g., search icon, clear button)
  • Input with inline labels, prefixes, or suffixes (e.g., currency symbols, units)
  • Input with integrated keyboard shortcut hints (Kbd)
  • Block-start or block-end addons for stacked input layouts

When not to use

  • Simple standalone input without addons — use Input directly
  • OTP/PIN input — use InputOTP instead
  • Select-style dropdowns — use Select or Combobox instead

Variants

PropValuesDefaultDescription
align (InputGroupAddon)inline-startinline-endblock-startblock-endinline-startControls addon placement: inline for left/right, block for top/bottom.
size (InputGroupButton)xssmicon-xsicon-smxsButton size within the input group.

Anti-patterns

Avoid<InputGroup>
  <Input placeholder="Search..." />
</InputGroup>
Prefer<InputGroup>
  <InputGroupInput placeholder="Search..." />
</InputGroup>

Use InputGroupInput inside InputGroup — raw Input retains its own border and shadow which conflicts.

Avoid<InputGroup>
  <InputGroupAddon>
    <Button size="sm">Go</Button>
  </InputGroupAddon>
  <InputGroupInput />
</InputGroup>
Prefer<InputGroup>
  <InputGroupAddon align="inline-end">
    <InputGroupButton>Go</InputGroupButton>
  </InputGroupAddon>
  <InputGroupInput />
</InputGroup>

Use InputGroupButton instead of Button for proper sizing and styling within the group.

Accessibility

  • Screen readerInputGroup renders as a div with role='group'. Addons also use role='group'. Ensure the input has an accessible label.
  • ContrastUses bg-input-bg and border-input tokens for consistent theming.

Token bindings

TokenCategoryUsage
input-bgcolorGroup background
inputcolorGroup border
ringcolorFocus ring
destructivecolorError state
muted-foregroundcolorAddon text color
rounded-mdradiusBorder radius

Import

import {
  InputGroup,
  InputGroupAddon,
  InputGroupButton,
  InputGroupInput,
  InputGroupText,
  InputGroupTextarea,
} from "@timelycare/helix-ui"

Props

interface InputGroupProps extends React.HTMLAttributes<HTMLDivElement> {
  className?: string
  children: React.ReactNode
}

interface InputGroupInputProps extends React.InputHTMLAttributes<HTMLInputElement> {
  className?: string
}

interface InputGroupTextareaProps extends React.TextareaHTMLAttributes<HTMLTextAreaElement> {
  className?: string
}

interface InputGroupAddonProps extends React.HTMLAttributes<HTMLDivElement> {
  align?: "inline-start" | "inline-end" | "block-start" | "block-end"
  className?: string
  children: React.ReactNode
}

interface InputGroupButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
  variant?: "default" | "secondary" | "ghost" | "outline"
  size?: "default" | "sm" | "xs" | "icon-xs" | "icon-sm"
  className?: string
  children: React.ReactNode
}

interface InputGroupTextProps extends React.HTMLAttributes<HTMLSpanElement> {
  className?: string
  children: React.ReactNode
}

Styling

Container

PropertyValueTailwind
DisplayFlexflex
Border1px solidborder border-input
Border radius8pxrounded-md
Backgroundbg-input-bgTheme-specific input background
Min widthmin-w-0Prevents flex overflow
Focus-withinRingfocus-within:ring-[3px] focus-within:ring-ring/50 focus-within:border-ring
ShadowSubtleshadow-xs

Input

PropertyValueTailwind
Height36pxh-9
BorderNone (container provides border)border-0
FocusNo ring (container handles focus)focus-visible:ring-0
ShadowNoneshadow-none

Addon

PropertyValueTailwind
DisplayFlex, centeredflex items-center
Padding0 12pxpx-3
ColorMutedtext-muted-foreground
Icon size16px[&_svg]:size-4

Typography

  • Text addons: Adelle Sans Regular, 14px (text-sm), text-muted-foreground
  • Input text: Adelle Sans Regular, 14px (text-sm), text-foreground

Addon Alignment

AlignPositionUse For
inline-start (default)Left side of inputPrefix icons, currency symbols
inline-endRight side of inputSuffix icons, validation indicators
block-startAbove the inputToolbar header (code editor)
block-endBelow the inputStatus bar, character counts

Common Patterns

Icon Prefix

<InputGroup>
  <InputGroupAddon>
    <SearchIcon />
  </InputGroupAddon>
  <InputGroupInput placeholder="Search..." />
</InputGroup>

Icon Suffix

<InputGroup>
  <InputGroupInput type="email" placeholder="Enter your email" />
  <InputGroupAddon align="inline-end">
    <MailIcon />
  </InputGroupAddon>
</InputGroup>

Both Sides

<InputGroup>
  <InputGroupAddon>
    <CreditCardIcon />
  </InputGroupAddon>
  <InputGroupInput placeholder="Card number" />
  <InputGroupAddon align="inline-end">
    <CheckIcon />
  </InputGroupAddon>
</InputGroup>

Text Addons (Currency)

<InputGroup>
  <InputGroupAddon>
    <InputGroupText>$</InputGroupText>
  </InputGroupAddon>
  <InputGroupInput placeholder="0.00" />
  <InputGroupAddon align="inline-end">
    <InputGroupText>USD</InputGroupText>
  </InputGroupAddon>
</InputGroup>

URL Input

<InputGroup>
  <InputGroupAddon>
    <InputGroupText>https://</InputGroupText>
  </InputGroupAddon>
  <InputGroupInput placeholder="example.com" />
</InputGroup>

With Button

<InputGroup>
  <InputGroupInput placeholder="Type to search..." />
  <InputGroupAddon align="inline-end">
    <InputGroupButton variant="secondary">Search</InputGroupButton>
  </InputGroupAddon>
</InputGroup>

With Copy Button

<InputGroup>
  <InputGroupInput placeholder="https://example.com" readOnly />
  <InputGroupAddon align="inline-end">
    <InputGroupButton aria-label="Copy" size="icon-xs">
      <CopyIcon />
    </InputGroupButton>
  </InputGroupAddon>
</InputGroup>

With Spinner (Loading)

<InputGroup data-disabled>
  <InputGroupInput placeholder="Searching..." disabled />
  <InputGroupAddon align="inline-end">
    <Spinner />
  </InputGroupAddon>
</InputGroup>

Textarea with Footer

<InputGroup>
  <InputGroupTextarea placeholder="Enter your message" />
  <InputGroupAddon align="block-end">
    <InputGroupText className="text-muted-foreground text-xs">
      120 characters left
    </InputGroupText>
  </InputGroupAddon>
</InputGroup>

Code Editor Pattern

<InputGroup>
  <InputGroupTextarea placeholder="console.log('hello');" className="min-h-[200px]" />
  <InputGroupAddon align="block-start" className="border-b">
    <InputGroupText className="font-mono font-medium">script.js</InputGroupText>
    <InputGroupButton className="ml-auto" size="icon-xs">
      <CopyIcon />
    </InputGroupButton>
  </InputGroupAddon>
  <InputGroupAddon align="block-end" className="border-t">
    <InputGroupText>Line 1, Column 1</InputGroupText>
    <InputGroupButton size="sm" className="ml-auto" variant="default">
      Run
    </InputGroupButton>
  </InputGroupAddon>
</InputGroup>

Accessibility

  • InputGroupInput is a standard <input> element — inherits all native accessibility
  • InputGroupTextarea is a standard <textarea> — inherits all native accessibility
  • Icon addons are decorative; use aria-label on buttons within addons
  • Link aria-describedby to any visible hint/description text
  • data-disabled on the container provides visual disabled state; also set disabled on the input

Gotchas

ProblemSolution
Double focus ringInputGroupInput strips its own ring; the container handles focus
Addon not vertically centeredEnsure items-center is on the addon (default behavior)
Button addon wrong sizeUse size="icon-xs" or size="sm" for buttons inside addons
Textarea addon on wrong sideUse align="block-start" (above) or align="block-end" (below)
Disabled state doesn't dim addonsAdd data-disabled to the InputGroup container
Custom radius neededOverride with className="[--radius:9999px]" on InputGroup

See Also

  • Related Components: Input (standalone input), Textarea (standalone textarea), Button Group (button container), Spinner (loading indicator in addons)
  • Tokens: Colorsinput, ring, muted-foreground tokens

Last updated: February 24, 2026