- Accordion
- Alert
- Alert Dialog
- Autocomplete
- Avatar
- Badge
- Breadcrumb
- Button
- Card
- Checkbox
- Checkbox Group
- Collapsible
- Combobox
- Dialog
- EmptyNew
- Field
- Fieldset
- Form
- Frame
- Group
- Input
- Input GroupNew
- KbdNew
- Label
- Menu
- Meter
- Number Field
- Pagination
- Popover
- Preview Card
- Progress
- Radio Group
- Scroll Area
- Select
- Separator
- Sheet
- SkeletonNew
- Slider
- SpinnerNew
- Switch
- Table
- Tabs
- Textarea
- Toast
- Toggle
- Toggle Group
- Toolbar
- Tooltip
Input Group
A flexible component for grouping inputs with addons, buttons, and other elements.
import { SearchIcon } from "lucide-react"
import {
InputGroup,
InputGroupAddon,
InputGroupInput,
} from "@/components/ui/input-group"
export default function InputGroupDemo() {
return (
<InputGroup>
<InputGroupInput type="search" placeholder="Search" aria-label="Search" />
<InputGroupAddon>
<SearchIcon />
</InputGroupAddon>
</InputGroup>
)
}
Installation
pnpm dlx shadcn@latest add @coss/input-group
Usage
import { Input } from "@/components/ui/input"
import {
InputGroup,
InputGroupAddon,
InputGroupInput,
InputGroupText,
} from "@/components/ui/input-group"<InputGroup>
<InputGroupInput type="email" placeholder="Email" />
<InputGroupAddon>
<MailIcon />
</InputGroupAddon>
</InputGroup>Examples
With End Icon
import { MailIcon } from "lucide-react"
import {
InputGroup,
InputGroupAddon,
InputGroupInput,
} from "@/components/ui/input-group"
export default function InputGroupWithEndIcon() {
return (
<InputGroup>
<InputGroupInput type="email" placeholder="Email" aria-label="Email" />
<InputGroupAddon align="inline-end">
<MailIcon />
</InputGroupAddon>
</InputGroup>
)
}
With Start Text
import {
InputGroup,
InputGroupAddon,
InputGroupInput,
InputGroupText,
} from "@/components/ui/input-group"
export default function InputGroupWithStartText() {
return (
<InputGroup>
<InputGroupInput
type="search"
className="*:[input]:ps-1!"
placeholder="coss"
aria-label="Set your URL"
/>
<InputGroupAddon>
<InputGroupText>i.cal.com/</InputGroupText>
</InputGroupAddon>
</InputGroup>
)
}
With End Text
import {
InputGroup,
InputGroupAddon,
InputGroupInput,
InputGroupText,
} from "@/components/ui/input-group"
export default function InputGroupWithEndText() {
return (
<InputGroup>
<InputGroupInput
type="text"
placeholder="Choose a username"
aria-label="Choose a username"
/>
<InputGroupAddon align="inline-end">
<InputGroupText>@coss.com</InputGroupText>
</InputGroupAddon>
</InputGroup>
)
}
With Start and End Text
import {
InputGroup,
InputGroupAddon,
InputGroupInput,
InputGroupText,
} from "@/components/ui/input-group"
export default function InputGroupWithStartEndText() {
return (
<InputGroup>
<InputGroupInput
type="text"
className="*:[input]:ps-1!"
placeholder="coss"
aria-label="Enter your domain"
/>
<InputGroupAddon>
<InputGroupText>https://</InputGroupText>
</InputGroupAddon>
<InputGroupAddon align="inline-end">
<InputGroupText>.com</InputGroupText>
</InputGroupAddon>
</InputGroup>
)
}
With Number Field
import {
InputGroup,
InputGroupAddon,
InputGroupText,
} from "@/components/ui/input-group"
import {
NumberField,
NumberFieldInput,
} from "@/components/ui/number-field"
export default function InputGroupWithNumberField() {
return (
<InputGroup>
<NumberField defaultValue={10} aria-label="Enter the amount">
<NumberFieldInput className="text-left" />
</NumberField>
<InputGroupAddon>
<InputGroupText>€</InputGroupText>
</InputGroupAddon>
<InputGroupAddon align="inline-end">
<InputGroupText>EUR</InputGroupText>
</InputGroupAddon>
</InputGroup>
)
}
With Tooltip
"use client"
import { InfoIcon } from "lucide-react"
import { Button } from "@/components/ui/button"
import {
InputGroup,
InputGroupAddon,
InputGroupInput,
} from "@/components/ui/input-group"
import {
Popover,
PopoverPopup,
PopoverTrigger,
} from "@/components/ui/popover"
export default function InputGroupWithEndTooltip() {
return (
<InputGroup>
<InputGroupInput
type="password"
placeholder="Password"
aria-label="Password"
/>
<InputGroupAddon align="inline-end">
<Popover openOnHover>
<PopoverTrigger
render={
<Button
variant="ghost"
size="icon-xs"
aria-label="Password requirements"
/>
}
>
<InfoIcon />
</PopoverTrigger>
<PopoverPopup tooltipStyle side="top">
<p>Min. 8 characters</p>
</PopoverPopup>
</Popover>
</InputGroupAddon>
</InputGroup>
)
}
With Icon Button
"use client"
import { useRef } from "react"
import { CheckIcon, CopyIcon } from "lucide-react"
import { useCopyToClipboard } from "@/hooks/use-copy-to-clipboard"
import { Button } from "@/components/ui/button"
import {
InputGroup,
InputGroupAddon,
InputGroupInput,
} from "@/components/ui/input-group"
import {
Tooltip,
TooltipPopup,
TooltipTrigger,
} from "@/components/ui/tooltip"
export default function InputGroupWithIconButton() {
const { copyToClipboard, isCopied } = useCopyToClipboard()
const inputRef = useRef<HTMLInputElement>(null)
return (
<InputGroup>
<InputGroupInput
ref={inputRef}
type="text"
defaultValue="https://coss.com"
aria-label="Url"
/>
<InputGroupAddon align="inline-end">
<Tooltip>
<TooltipTrigger
render={
<Button
variant="ghost"
aria-label="Copy"
size="icon-xs"
onClick={() => {
if (inputRef.current) {
copyToClipboard(inputRef.current.value)
}
}}
/>
}
>
{isCopied ? <CheckIcon /> : <CopyIcon />}
</TooltipTrigger>
<TooltipPopup>
<p>Copy to clipboard</p>
</TooltipPopup>
</Tooltip>
</InputGroupAddon>
</InputGroup>
)
}
With Button
import { Button } from "@/components/ui/button"
import {
InputGroup,
InputGroupAddon,
InputGroupInput,
} from "@/components/ui/input-group"
export default function InputGroupWithButton() {
return (
<InputGroup>
<InputGroupInput type="search" placeholder="Type to search…" />
<InputGroupAddon align="inline-end">
<Button variant="secondary" size="xs">
Search
</Button>
</InputGroupAddon>
</InputGroup>
)
}
With Badge
import { Badge } from "@/components/ui/badge"
import {
InputGroup,
InputGroupAddon,
InputGroupInput,
} from "@/components/ui/input-group"
export default function InputGroupWithBadge() {
return (
<InputGroup>
<InputGroupInput type="search" placeholder="Type to search…" />
<InputGroupAddon align="inline-end">
<Badge variant="info">Badge</Badge>
</InputGroupAddon>
</InputGroup>
)
}
With Keyboard Shortcut
import {
InputGroup,
InputGroupAddon,
InputGroupInput,
} from "@/components/ui/input-group"
import { Kbd } from "@/components/ui/kbd"
export default function InputGroupWithKbd() {
return (
<InputGroup>
<InputGroupInput type="search" placeholder="Search…" />
<InputGroupAddon align="inline-end">
<Kbd>⌘K</Kbd>
</InputGroupAddon>
</InputGroup>
)
}
With Inner Label
"use client"
import { InfoIcon } from "lucide-react"
import { Button } from "@/components/ui/button"
import {
InputGroup,
InputGroupAddon,
InputGroupInput,
} from "@/components/ui/input-group"
import { Label } from "@/components/ui/label"
import {
Popover,
PopoverPopup,
PopoverTrigger,
} from "@/components/ui/popover"
export default function InputGroupWithInnerLabel() {
return (
<InputGroup>
<InputGroupInput id="email-1" type="email" placeholder="[email protected]" />
<InputGroupAddon align="block-start">
<Label htmlFor="email-1" className="text-foreground">
Email
</Label>
<Popover openOnHover>
<PopoverTrigger
className="ml-auto"
render={<Button variant="ghost" size="icon-xs" className="-m-1" />}
>
<InfoIcon />
</PopoverTrigger>
<PopoverPopup tooltipStyle side="top">
<p>We'll use this to send you notifications</p>
</PopoverPopup>
</Popover>
</InputGroupAddon>
</InputGroup>
)
}
Small Size
import { SearchIcon } from "lucide-react"
import {
InputGroup,
InputGroupAddon,
InputGroupInput,
} from "@/components/ui/input-group"
export default function InputGroupSm() {
return (
<InputGroup>
<InputGroupInput
size="sm"
type="search"
placeholder="Search"
aria-label="Search"
/>
<InputGroupAddon>
<SearchIcon />
</InputGroupAddon>
</InputGroup>
)
}
Large Size
import { SearchIcon } from "lucide-react"
import {
InputGroup,
InputGroupAddon,
InputGroupInput,
} from "@/components/ui/input-group"
export default function InputGroupLg() {
return (
<InputGroup>
<InputGroupInput
size="lg"
type="search"
placeholder="Search"
aria-label="Search"
/>
<InputGroupAddon>
<SearchIcon />
</InputGroupAddon>
</InputGroup>
)
}
Disabled
import { ArrowRightIcon } from "lucide-react"
import { Button } from "@/components/ui/button"
import {
InputGroup,
InputGroupAddon,
InputGroupInput,
} from "@/components/ui/input-group"
export default function InputGroupDisabled() {
return (
<InputGroup>
<InputGroupInput
type="email"
placeholder="Your best email"
aria-label="Subscribe to our newsletter"
disabled
/>
<InputGroupAddon align="inline-end">
<Button variant="ghost" aria-label="Subscribe" size="icon-xs" disabled>
<ArrowRightIcon />
</Button>
</InputGroupAddon>
</InputGroup>
)
}
Loading
import {
InputGroup,
InputGroupAddon,
InputGroupInput,
} from "@/components/ui/input-group"
import { Spinner } from "@/components/ui/spinner"
export default function InputGroupLoading() {
return (
<InputGroup>
<InputGroupInput type="search" placeholder="Searching…" disabled />
<InputGroupAddon align="inline-end">
<Spinner />
</InputGroupAddon>
</InputGroup>
)
}
With Textarea
"use client"
import { ArrowUpIcon, PlusIcon } from "lucide-react"
import { Button } from "@/components/ui/button"
import {
InputGroup,
InputGroupAddon,
InputGroupText,
InputGroupTextarea,
} from "@/components/ui/input-group"
import {
Menu,
MenuItem,
MenuPopup,
MenuTrigger,
} from "@/components/ui/menu"
import {
Tooltip,
TooltipPopup,
TooltipTrigger,
} from "@/components/ui/tooltip"
export default function InputGroupWithTextarea() {
return (
<InputGroup>
<InputGroupTextarea placeholder="Ask, Search or Chat…" />
<InputGroupAddon align="block-end">
<Menu>
<Tooltip>
<TooltipTrigger
render={
<MenuTrigger
render={
<Button
variant="ghost"
size="icon-sm"
className="rounded-full"
aria-label="Add files"
/>
}
>
<PlusIcon />
</MenuTrigger>
}
/>
<TooltipPopup>Add files and more</TooltipPopup>
</Tooltip>
<MenuPopup align="start">
<MenuItem>Add photos & files</MenuItem>
<MenuItem>Create image</MenuItem>
<MenuItem>Thinking</MenuItem>
<MenuItem>Deep research</MenuItem>
</MenuPopup>
</Menu>
<InputGroupText className="ml-auto">78% used</InputGroupText>
<Tooltip>
<TooltipTrigger
render={
<Button
variant="default"
className="rounded-full"
size="icon-sm"
aria-label="Send"
>
<ArrowUpIcon />
</Button>
}
/>
<TooltipPopup>Send</TooltipPopup>
</Tooltip>
</InputGroupAddon>
</InputGroup>
)
}
API Reference
InputGroup
The main component that wraps inputs and addons.
| Prop | Type | Description |
|---|---|---|
className | string | Additional CSS classes to apply to the component |
...props | React.ComponentProps<'div'> | All standard div attributes are supported |
InputGroupAddon
A container for addons like icons, text, buttons, and other elements. Can be positioned at the start or end (inline), or top or bottom (block) of the input.
| Prop | Type | Default | Description |
|---|---|---|---|
align | 'inline-start' | 'inline-end' | 'block-start' | 'block-end' | 'inline-start' | The position of the addon relative to the input. Use inline-start or inline-end for inputs, and block-start or block-end for textareas |
className | string | Additional CSS classes to apply to the component | |
...props | React.ComponentProps<'div'> | All standard div attributes are supported |
For proper focus navigation, the InputGroupAddon component should be placed
after the input in the DOM order.
InputGroupText
A text container component for displaying text content within an InputGroupAddon. Automatically styles text with muted foreground color and handles icon sizing.
| Prop | Type | Description |
|---|---|---|
className | string | Additional CSS classes to apply to the component |
...props | React.ComponentProps<'span'> | All standard span attributes are supported |
InputGroupInput
A specialized input component for use within InputGroup. It's essentially an unstyled Input component that inherits styling from the parent InputGroup.
| Prop | Type | Description |
|---|---|---|
className | string | Additional CSS classes to apply to the component |
...props | InputProps | All props from the Input component are supported, including type, placeholder, disabled, etc. |
InputGroupTextarea
A specialized textarea component for use within InputGroup. It's essentially an unstyled Textarea component that inherits styling from the parent InputGroup.
| Prop | Type | Description |
|---|---|---|
className | string | Additional CSS classes to apply to the component |
...props | TextareaProps | All props from the Textarea component are supported, including placeholder, disabled, rows, etc. |
Comparing with shadcn
If you're already familiar with shadcn/ui, this guide highlights the small differences and similarities so you can get started with coss ui quickly.
Quick Checklist
- No
InputGroupButtoncomponent - use the regularButtoncomponent directly insideInputGroupAddoninstead - To disable an input group, disable the
InputGroupInputorInputGroupTextareadirectly (and any Button inside it) - no need to add adata-disabledattribute onInputGroup.
On This Page