Input Group

Add icons, text, keys and buttons inside an input or textarea.

View as Markdown
12 results
<InputGroup>  <InputGroupInput aria-label="Search" placeholder="Search..." />  <InputGroupAddon>    <HugeiconsIcon icon={Search01Icon} />  </InputGroupAddon>  <InputGroupAddon align="inline-end">12 results</InputGroupAddon></InputGroup>

Installation

$ npx shadcn@latest add @constella/input-group

Usage

import {  InputGroup,  InputGroupAddon,  InputGroupButton,  InputGroupInput,  InputGroupText,  InputGroupTextarea,} from "@/components/primitives/input-group";
<InputGroup>  <InputGroupInput placeholder="Search..." />  <InputGroupAddon>    <HugeiconsIcon icon={Search01Icon} />  </InputGroupAddon></InputGroup>

Composition

InputGroup├── InputGroupInput or InputGroupTextarea├── InputGroupAddon├── InputGroupButton└── InputGroupText

Align

Use the align prop on InputGroupAddon to place it around the input.

Put InputGroupAddon after the input in the DOM, and place it with align: the tab order follows the DOM, so the input stays first.

inline-start

Use align="inline-start" to place the addon at the start of the input. This is the default.

Icon positioned at the start.

<Field>  <FieldLabel>Input</FieldLabel>  <InputGroup>    <InputGroupInput placeholder="Search..." />    <InputGroupAddon align="inline-start">      <HugeiconsIcon icon={Search01Icon} />    </InputGroupAddon>  </InputGroup>  <FieldDescription>Icon positioned at the start.</FieldDescription></Field>

inline-end

Use align="inline-end" to place the addon at the end of the input.

Icon positioned at the end.

<Field>  <FieldLabel>Input</FieldLabel>  <InputGroup>    <InputGroupInput placeholder="Enter password" type="password" />    <InputGroupAddon align="inline-end">      <HugeiconsIcon icon={ViewOffSlashIcon} />    </InputGroupAddon>  </InputGroup>  <FieldDescription>Icon positioned at the end.</FieldDescription></Field>

block-start

Use align="block-start" to place the addon above the input.

Full Name

Header positioned above the input.

script.js

Header positioned above the textarea.

const styles = stylex.create({  code: {    fontFamily: fonts.mono,    fontSize: fontSizes.textSm,    lineHeight: lineHeights.textSm,    fontVariantLigatures: "none",  },});
<FieldGroup>  <Field>    <FieldLabel>Input</FieldLabel>    <InputGroup>      <InputGroupInput placeholder="Enter your name" />      <InputGroupAddon align="block-start">        <InputGroupText>Full Name</InputGroupText>      </InputGroupAddon>    </InputGroup>    <FieldDescription>Header positioned above the input.</FieldDescription>  </Field>  <Field>    <FieldLabel>Textarea</FieldLabel>    <InputGroup>      <InputGroupTextarea        placeholder="console.log('Hello, world!');"        sx={styles.code}      />      <InputGroupAddon align="block-start">        <HugeiconsIcon icon={FileScriptIcon} />        <InputGroupText sx={styles.code}>script.js</InputGroupText>        <InputGroupButton aria-label="Copy" size="icon-xs">          <HugeiconsIcon icon={Copy01Icon} />        </InputGroupButton>      </InputGroupAddon>    </InputGroup>    <FieldDescription>Header positioned above the textarea.</FieldDescription>  </Field></FieldGroup>;

block-end

Use align="block-end" to place the addon below the input.

USD

Footer positioned below the input.

0/280

Footer positioned below the textarea.

<FieldGroup>  <Field>    <FieldLabel>Input</FieldLabel>    <InputGroup>      <InputGroupInput placeholder="Enter amount" />      <InputGroupAddon align="block-end">        <InputGroupText>USD</InputGroupText>      </InputGroupAddon>    </InputGroup>    <FieldDescription>Footer positioned below the input.</FieldDescription>  </Field>  <Field>    <FieldLabel>Textarea</FieldLabel>    <InputGroup>      <InputGroupTextarea placeholder="Write a comment..." />      <InputGroupAddon align="block-end">        <InputGroupText>0/280</InputGroupText>        <InputGroupButton size="sm" variant="primary">          Post        </InputGroupButton>      </InputGroupAddon>    </InputGroup>    <FieldDescription>Footer positioned below the textarea.</FieldDescription>  </Field></FieldGroup>

Size

Use the size prop to match the group to the buttons beside it, and size icons to match.

SizeHeightIcon
sm28pxsizes.iconSm
default32pxsizes.icon
lg36pxsizes.icon
12 results
12 results
12 results
<InputGroup size="sm">  <InputGroupInput aria-label="Small" placeholder="Small" />  <InputGroupAddon>    <HugeiconsIcon icon={Search01Icon} size={sizes.iconSm} />  </InputGroupAddon>  <InputGroupAddon align="inline-end">12 results</InputGroupAddon></InputGroup><InputGroup>…</InputGroup><InputGroup size="lg">…</InputGroup>

With Icon

Put an icon in an InputGroupAddon. An addon can hold more than one.

<InputGroup>  <InputGroupInput aria-label="Card number" placeholder="Card number" />  <InputGroupAddon>    <HugeiconsIcon icon={CreditCardIcon} />  </InputGroupAddon>  <InputGroupAddon align="inline-end">    <HugeiconsIcon icon={Tick02Icon} />  </InputGroupAddon></InputGroup>

Text

Use InputGroupText for a prefix, a suffix or a hint. A text prefix or suffix sits close to the value, so https:// and acme.com read as one.

$
USD
https://
.com
@acme.com
120 characters left
<InputGroup>  <InputGroupAddon>    <InputGroupText>https://</InputGroupText>  </InputGroupAddon>  <InputGroupInput aria-label="Website" placeholder="acme.com" />  <InputGroupAddon align="inline-end">    <InputGroupText>.com</InputGroupText>  </InputGroupAddon></InputGroup>

Button

Use InputGroupButton for an action inside the input. It's a ghost Button that defaults to size="xs" and type="button", so it never submits the form by accident.

https://
<InputGroup>  <InputGroupInput    aria-label="Profile link"    placeholder="https://x.com/acme"    readOnly  />  <InputGroupAddon align="inline-end">    <InputGroupButton aria-label="Copy" size="icon-xs">      <HugeiconsIcon icon={Copy01Icon} />    </InputGroupButton>  </InputGroupAddon></InputGroup><InputGroup>  <InputGroupInput aria-label="Search" placeholder="Type to search..." />  <InputGroupAddon align="inline-end">    <InputGroupButton variant="secondary">Search</InputGroupButton>  </InputGroupAddon></InputGroup>

Render a button as a Popover trigger to explain the field.

<InputGroupAddon>  <Popover>    <PopoverTrigger      render={<InputGroupButton size="icon-xs" variant="secondary" />}    >      <HugeiconsIcon icon={InformationCircleIcon} />    </PopoverTrigger>    <PopoverContent align="start">      <PopoverHeader>        <PopoverTitle>Your connection is not secure.</PopoverTitle>      </PopoverHeader>    </PopoverContent>  </Popover>  <InputGroupText>https://</InputGroupText></InputGroupAddon>

Textarea

Use InputGroupTextarea with block-start and block-end addons for a header and a footer. Add separated to a block addon to draw an edge between it and the textarea.

Line 1, Column 1
script.js
<InputGroup>  <InputGroupTextarea    aria-label="Code"    placeholder="console.log('Hello, world!');"  />  <InputGroupAddon align="block-end" separated>    <InputGroupText>Line 1, Column 1</InputGroupText>    <InputGroupButton size="sm" variant="primary">      Run <HugeiconsIcon data-icon="inline-end" icon={ArrowTurnBackwardIcon} />    </InputGroupButton>  </InputGroupAddon>  <InputGroupAddon align="block-start" separated>    <InputGroupText>      <HugeiconsIcon icon={JavaScriptIcon} />      script.js    </InputGroupText>    <InputGroupButton aria-label="Refresh" size="icon-xs">      <HugeiconsIcon icon={Refresh01Icon} />    </InputGroupButton>    <InputGroupButton aria-label="Copy" size="icon-xs">      <HugeiconsIcon icon={Copy01Icon} />    </InputGroupButton>  </InputGroupAddon></InputGroup>

Custom Input

Add data-slot="input-group-control" to your own input, so the group draws its focus ring, invalid edge and disabled fade. Spread inputGroupTextareaStyles() on it for the control's look. Here, a textarea from react-textarea-autosize:

import TextareaAutosize from "react-textarea-autosize";
<InputGroup>  <TextareaAutosize    aria-label="Message"    data-slot="input-group-control"    placeholder="Autoresize textarea..."    {...stylex.props(inputGroupTextareaStyles())}  />  <InputGroupAddon align="block-end">    <InputGroupButton size="sm" variant="primary">      Submit    </InputGroupButton>  </InputGroupAddon></InputGroup>;

Render a button as a Dropdown Menu trigger for more actions.

<InputGroup>  <InputGroupInput placeholder="Enter file name" />  <InputGroupAddon align="inline-end">    <DropdownMenu>      <DropdownMenuTrigger        render={<InputGroupButton aria-label="More" size="icon-xs" />}      >        <HugeiconsIcon icon={MoreHorizontalIcon} />      </DropdownMenuTrigger>      <DropdownMenuContent align="end">        <DropdownMenuItem>Settings</DropdownMenuItem>        <DropdownMenuItem>Copy path</DropdownMenuItem>      </DropdownMenuContent>    </DropdownMenu>  </InputGroupAddon></InputGroup>

Kbd

Put a Kbd in an addon to show a shortcut.

⌘K
<InputGroup>  <InputGroupInput aria-label="Search" placeholder="Search..." />  <InputGroupAddon>    <HugeiconsIcon icon={Search01Icon} />  </InputGroupAddon>  <InputGroupAddon align="inline-end">    <Kbd>⌘K</Kbd>  </InputGroupAddon></InputGroup>

Spinner

Put a Spinner in an addon while something loads.

Saving...
Please wait...
<InputGroup>  <InputGroupInput aria-label="Notes" placeholder="Saving changes..." />  <InputGroupAddon align="inline-end">    <InputGroupText>Saving...</InputGroupText>    <Spinner />  </InputGroupAddon></InputGroup>

API Reference

InputGroup is a <div role="group"> that takes size: "sm" (12px text), "default" or "lg" (13px). It draws the box, focus ring, invalid edge and disabled fade for the control inside, so InputGroupInput and InputGroupTextarea drop their own.

InputGroupAddon takes align: "inline-start" (default), "inline-end", "block-start" or "block-end". Use the inline ones with an input and the block ones with a textarea. In a block addon, buttons sit at the end, after any text; separated draws an edge between it and the control. Clicking an addon focuses the control, unless the click lands on a button.

InputGroupButton takes size: "xs" (default), "sm", "icon-xs" or "icon-sm", and every other Button prop; variant defaults to "ghost".

InputGroupInput renders Base UI's input in a slot. On a touch screen the input renders at 16px and scales down into the slot to the group's text size, so iOS doesn't zoom on focus. It takes every Base UI Input prop, and its sx goes to the <input>; set text size on InputGroup instead, since a size there keeps the scaling. InputGroupTextarea takes every Textarea prop. InputGroupText takes render. Every part takes sx, applied last.