Input Group

Input or textarea with inline addons and actions.

Pair an input or textarea with icons, text, buttons, and helper content.

12 results
tsx
import {  InputGroup,  InputGroupAddon,  InputGroupInput,} from "@/components/ui/input-group"function SearchIcon(props: React.SVGProps<SVGSVGElement>) {  return (    <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true" {...props}>      <circle cx="11" cy="11" r="8" />      <path d="m21 21-4.3-4.3" />    </svg>  )}export function Example() {  return (    <InputGroup className="max-w-xs" variant="filled">      <InputGroupInput placeholder="Search..." />      <InputGroupAddon>        <SearchIcon />      </InputGroupAddon>      <InputGroupAddon align="inline-end">12 results</InputGroupAddon>    </InputGroup>  )}

Installation

npx @arctis-sh/@arctis-sh/ui@latest add input-group

Usage

import {  InputGroup,  InputGroupAddon,  InputGroupInput,} from "@/components/ui/input-group"
<InputGroup>  <InputGroupInput placeholder="Search…" />  <InputGroupAddon>    <SearchIcon />  </InputGroupAddon></InputGroup>

Composition

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

Align

Set align on InputGroupAddon to place the addon. Keep addons after the control in the DOM; align controls their visual position.

inline-start

The default. Places the addon at the start of the input.

tsx
import {  InputGroup,  InputGroupAddon,  InputGroupInput,} from "@/components/ui/input-group"function SearchIcon(props: React.SVGProps<SVGSVGElement>) {  return (    <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true" {...props}>      <circle cx="11" cy="11" r="8" />      <path d="m21 21-4.3-4.3" />    </svg>  )}export function Example() {  return (    <InputGroup className="max-w-xs">      <InputGroupInput placeholder="Search..." />      <InputGroupAddon align="inline-start">        <SearchIcon />      </InputGroupAddon>    </InputGroup>  )}

inline-end

Addon at the end of the input.

tsx
import {  InputGroup,  InputGroupAddon,  InputGroupInput,} from "@/components/ui/input-group"function EyeOffIcon(props: React.SVGProps<SVGSVGElement>) {  return (    <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true" {...props}>      <path d="M10.733 5.076a10.744 10.744 0 0 1 11.205 6.575 1 1 0 0 1 0 .696 10.747 10.747 0 0 1-1.444 2.49" />      <path d="M14.084 14.158a3 3 0 0 1-4.242-4.242" />      <path d="M17.479 17.499a10.75 10.75 0 0 1-15.417-5.151 1 1 0 0 1 0-.696 10.75 10.75 0 0 1 4.446-5.143" />      <path d="m2 2 20 20" />    </svg>  )}export function Example() {  return (    <InputGroup className="max-w-xs">      <InputGroupInput type="password" placeholder="Password" />      <InputGroupAddon align="inline-end">        <EyeOffIcon />      </InputGroupAddon>    </InputGroup>  )}

block-start

Addon above the control.

Full Name
script.js
tsx
import {  InputGroup,  InputGroupAddon,  InputGroupButton,  InputGroupInput,  InputGroupText,  InputGroupTextarea,} from "@/components/ui/input-group"function CopyIcon(props: React.SVGProps<SVGSVGElement>) {  return (    <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true" {...props}>      <rect x="9" y="9" width="13" height="13" rx="2" ry="2" />      <path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1" />    </svg>  )}function FileCodeIcon(props: React.SVGProps<SVGSVGElement>) {  return (    <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true" {...props}>      <path d="M4 22h14a2 2 0 0 0 2-2V7l-5-5H6a2 2 0 0 0-2 2v4" />      <path d="M14 2v4a2 2 0 0 0 2 2h4" />      <path d="m5 12-3 3 3 3" />      <path d="m9 18 3-3-3-3" />    </svg>  )}export function Example() {  return (    <div className="flex w-full max-w-sm flex-col gap-4">      <InputGroup>        <InputGroupInput placeholder="Enter your name" />        <InputGroupAddon align="block-start">          <InputGroupText>Full Name</InputGroupText>        </InputGroupAddon>      </InputGroup>      <InputGroup variant="filled">        <InputGroupTextarea placeholder="// write a function..." className="min-h-24" />        <InputGroupAddon align="block-start" className="border-b border-border">          <InputGroupText>            <FileCodeIcon />            script.js          </InputGroupText>          <InputGroupButton size="icon-xs" className="ml-auto" aria-label="Copy">            <CopyIcon />          </InputGroupButton>        </InputGroupAddon>      </InputGroup>    </div>  )}

block-end

Addon below the control.

USD
0/280
tsx
import {  InputGroup,  InputGroupAddon,  InputGroupButton,  InputGroupInput,  InputGroupText,  InputGroupTextarea,} from "@/components/ui/input-group"export function Example() {  return (    <div className="flex w-full max-w-sm flex-col gap-4">      <InputGroup>        <InputGroupInput placeholder="0.00" />        <InputGroupAddon align="block-end">          <InputGroupText>USD</InputGroupText>        </InputGroupAddon>      </InputGroup>      <InputGroup variant="filled">        <InputGroupTextarea placeholder="Write a comment..." className="min-h-24" />        <InputGroupAddon align="block-end" className="border-t border-border">          <InputGroupText>0/280</InputGroupText>          <InputGroupButton className="ml-auto" size="sm" variant="default">            Post          </InputGroupButton>        </InputGroupAddon>      </InputGroup>    </div>  )}

Icon

tsx
import {  InputGroup,  InputGroupAddon,  InputGroupInput,} from "@/components/ui/input-group"function CheckIcon(props: React.SVGProps<SVGSVGElement>) {  return (    <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true" {...props}>      <path d="M20 6 9 17l-5-5" />    </svg>  )}function CreditCardIcon(props: React.SVGProps<SVGSVGElement>) {  return (    <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true" {...props}>      <rect width="20" height="14" x="2" y="5" rx="2" />      <path d="M2 10h20" />    </svg>  )}function MailIcon(props: React.SVGProps<SVGSVGElement>) {  return (    <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true" {...props}>      <path d="m22 7-8.991 5.727a2 2 0 0 1-2.009 0L2 7" />      <rect x="2" y="4" width="20" height="16" rx="2" />    </svg>  )}export function Example() {  return (    <div className="grid w-full max-w-sm gap-4">      <InputGroup>        <InputGroupInput placeholder="Email" />        <InputGroupAddon>          <MailIcon />        </InputGroupAddon>      </InputGroup>      <InputGroup variant="filled">        <InputGroupInput placeholder="Card number" />        <InputGroupAddon>          <CreditCardIcon />        </InputGroupAddon>        <InputGroupAddon align="inline-end">          <CheckIcon />        </InputGroupAddon>      </InputGroup>    </div>  )}

Text

$
USD
https://
.com
@company.com
tsx
import {  InputGroup,  InputGroupAddon,  InputGroupInput,  InputGroupText,} from "@/components/ui/input-group"export function Example() {  return (    <div className="grid w-full max-w-sm gap-4">      <InputGroup>        <InputGroupInput placeholder="0.00" />        <InputGroupAddon>          <InputGroupText>$</InputGroupText>        </InputGroupAddon>        <InputGroupAddon align="inline-end">          <InputGroupText>USD</InputGroupText>        </InputGroupAddon>      </InputGroup>      <InputGroup variant="filled">        <InputGroupInput placeholder="example" />        <InputGroupAddon>          <InputGroupText>https://</InputGroupText>        </InputGroupAddon>        <InputGroupAddon align="inline-end">          <InputGroupText>.com</InputGroupText>        </InputGroupAddon>      </InputGroup>      <InputGroup variant="filled">        <InputGroupInput placeholder="username" />        <InputGroupAddon align="inline-end">          <InputGroupText>@company.com</InputGroupText>        </InputGroupAddon>      </InputGroup>    </div>  )}

Button

https://
tsx
import {  InputGroup,  InputGroupAddon,  InputGroupButton,  InputGroupInput,  InputGroupText,} from "@/components/ui/input-group"function SearchIcon(props: React.SVGProps<SVGSVGElement>) {  return (    <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true" {...props}>      <circle cx="11" cy="11" r="8" />      <path d="m21 21-4.3-4.3" />    </svg>  )}export function Example() {  return (    <div className="grid w-full max-w-sm gap-4">      <InputGroup>        <InputGroupInput placeholder="example.com" />        <InputGroupAddon>          <InputGroupText>https://</InputGroupText>        </InputGroupAddon>        <InputGroupAddon align="inline-end">          <InputGroupButton variant="default">Search</InputGroupButton>        </InputGroupAddon>      </InputGroup>      <InputGroup variant="filled">        <InputGroupInput placeholder="Find anything..." />        <InputGroupAddon align="inline-end">          <InputGroupButton variant="secondary">Go</InputGroupButton>        </InputGroupAddon>      </InputGroup>      <InputGroup>        <InputGroupInput placeholder="Search..." />        <InputGroupAddon align="inline-end">          <InputGroupButton size="icon-xs" variant="outline" aria-label="Search">            <SearchIcon />          </InputGroupButton>        </InputGroupAddon>      </InputGroup>      <InputGroup variant="filled">        <InputGroupInput placeholder="Filter..." />        <InputGroupAddon align="inline-end">          <InputGroupButton size="icon-xs" aria-label="Search">            <SearchIcon />          </InputGroupButton>        </InputGroupAddon>      </InputGroup>    </div>  )}

Variant

variant="filled" uses a muted fill without an outline. The default variant keeps the border.

<InputGroup variant="filled">  <InputGroupInput placeholder="Search…" /></InputGroup>

Kbd

⌘K
tsx
import { Kbd } from "@/components/ui/kbd"import {  InputGroup,  InputGroupAddon,  InputGroupInput,} from "@/components/ui/input-group"function SearchIcon(props: React.SVGProps<SVGSVGElement>) {  return (    <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true" {...props}>      <circle cx="11" cy="11" r="8" />      <path d="m21 21-4.3-4.3" />    </svg>  )}export function Example() {  return (    <InputGroup className="max-w-xs">      <InputGroupInput placeholder="Search..." />      <InputGroupAddon>        <SearchIcon />      </InputGroupAddon>      <InputGroupAddon align="inline-end">        <Kbd>⌘K</Kbd>      </InputGroupAddon>    </InputGroup>  )}

Spinner

Saving...
tsx
import { Spinner } from "@/components/ui/spinner"import {  InputGroup,  InputGroupAddon,  InputGroupInput,  InputGroupText,} from "@/components/ui/input-group"export function Example() {  return (    <div className="grid w-full max-w-sm gap-4">      <InputGroup>        <InputGroupInput placeholder="Saving..." disabled />        <InputGroupAddon align="inline-end">          <Spinner />        </InputGroupAddon>      </InputGroup>      <InputGroup data-disabled>        <InputGroupInput placeholder="Please wait..." disabled />        <InputGroupAddon>          <Spinner />        </InputGroupAddon>        <InputGroupAddon align="inline-end">          <InputGroupText>Saving...</InputGroupText>        </InputGroupAddon>      </InputGroup>    </div>  )}

Textarea

Line 1, Column 1
script.js
tsx
import {  InputGroup,  InputGroupAddon,  InputGroupButton,  InputGroupText,  InputGroupTextarea,} from "@/components/ui/input-group"function CopyIcon(props: React.SVGProps<SVGSVGElement>) {  return (    <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true" {...props}>      <rect x="9" y="9" width="13" height="13" rx="2" ry="2" />      <path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1" />    </svg>  )}function RefreshIcon(props: React.SVGProps<SVGSVGElement>) {  return (    <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true" {...props}>      <path d="M3 12a9 9 0 0 1 9-9 9.75 9.75 0 0 1 6.74 2.74L21 8" />      <path d="M21 3v5h-5" />      <path d="M21 12a9 9 0 0 1-9 9 9.75 9.75 0 0 1-6.74-2.74L3 16" />      <path d="M8 16H3v5" />    </svg>  )}export function Example() {  return (    <div className="grid w-full max-w-md gap-4">      <InputGroup variant="filled">        <InputGroupTextarea placeholder="Write a comment..." className="min-h-24" />        <InputGroupAddon align="block-end" className="border-t border-border">          <InputGroupText>Line 1, Column 1</InputGroupText>          <InputGroupButton className="ml-auto" size="sm" variant="default">            Run          </InputGroupButton>        </InputGroupAddon>      </InputGroup>      <InputGroup variant="filled">        <InputGroupTextarea defaultValue="console.log('hello')" className="min-h-24" />        <InputGroupAddon align="block-start" className="border-b border-border">          <InputGroupText>script.js</InputGroupText>          <InputGroupButton size="icon-xs" className="ml-auto" aria-label="Refresh">            <RefreshIcon />          </InputGroupButton>          <InputGroupButton size="icon-xs" aria-label="Copy">            <CopyIcon />          </InputGroupButton>        </InputGroupAddon>      </InputGroup>    </div>  )}

API Reference

InputGroup

Groups the control with its addons.

PropTypeDefault
variant"default" | "filled""default"
classNamestring
<InputGroup>  <InputGroupInput />  <InputGroupAddon /></InputGroup>
<InputGroup variant="filled">  <InputGroupInput placeholder="Search…" /></InputGroup>

InputGroupAddon

Holds icons, text, or buttons beside the control. Place it after the input in the DOM, then use align to set its visual position.

PropTypeDefault
align"inline-start" | "inline-end" | "block-start" | "block-end""inline-start"
classNamestring
<InputGroupAddon align="inline-end">  <SearchIcon /></InputGroupAddon>

For InputGroupInput, prefer inline-start or inline-end. For InputGroupTextarea, prefer block-start or block-end.

<InputGroupAddon>  <InputGroupButton>Button</InputGroupButton>  <InputGroupButton>Button</InputGroupButton></InputGroupAddon>

InputGroupButton

Buttons inside an addon.

PropTypeDefault
size"xs" | "sm" | "icon-xs" | "icon-sm""xs"
variantButton variant"ghost"
classNamestring
<InputGroupButton>Button</InputGroupButton><InputGroupButton size="icon-xs" aria-label="Copy">  <CopyIcon /></InputGroupButton>

InputGroupInput

Input styled to sit inside a group. It uses data-slot="input-group-control" and passes other props to Input.

PropTypeDefault
classNamestring
<InputGroup>  <InputGroupInput placeholder="Enter text..." />  <InputGroupAddon>    <SearchIcon />  </InputGroupAddon></InputGroup>

InputGroupTextarea

Textarea styled to sit inside a group. It uses data-slot="input-group-control" and passes other props to Textarea.

PropTypeDefault
classNamestring
<InputGroup>  <InputGroupTextarea placeholder="Enter message..." />  <InputGroupAddon align="block-end">    <InputGroupButton>Send</InputGroupButton>  </InputGroupAddon></InputGroup>

InputGroupText

Muted helper text inside an addon.

<InputGroupText>https://</InputGroupText>