Input Group
Display additional information or actions to an input or textarea.
import { LuArrowUp, LuSearch } from "react-icons/lu";
import { TbCheck, TbInfoCircle, TbPlus } from "react-icons/tb";
import { css } from "styled-system/css";
import { styled } from "styled-system/jsx";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import {
InputGroup,
InputGroupAddon,
InputGroupButton,
InputGroupInput,
InputGroupText,
InputGroupTextarea,
} from "@/components/ui/input-group";
import { Separator } from "@/components/ui/separator";
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
export default function InputGroupDemo() {
return (
<styled.div css={{ display: "grid", w: "full", maxW: "sm", gap: "6" }}>
<InputGroup>
<InputGroupInput placeholder="Search..." />
<InputGroupAddon>
<LuSearch />
</InputGroupAddon>
<InputGroupAddon align="inline-end">12 results</InputGroupAddon>
</InputGroup>
<InputGroup>
<InputGroupInput placeholder="example.com" css={{ pl: "1!" }} />
<InputGroupAddon>
<InputGroupText>https://</InputGroupText>
</InputGroupAddon>
<InputGroupAddon align="inline-end">
<Tooltip>
<TooltipTrigger asChild>
<InputGroupButton css={{ rounded: "full" }} size="icon-xs">
<TbInfoCircle />
</InputGroupButton>
</TooltipTrigger>
<TooltipContent>This is content in a tooltip.</TooltipContent>
</Tooltip>
</InputGroupAddon>
</InputGroup>
<InputGroup>
<InputGroupTextarea placeholder="Ask, Search or Chat..." />
<InputGroupAddon align="block-end">
<InputGroupButton variant="outline" css={{ rounded: "full" }} size="icon-xs">
<TbPlus />
</InputGroupButton>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<InputGroupButton variant="ghost">Auto</InputGroupButton>
</DropdownMenuTrigger>
<DropdownMenuContent side="top" align="start">
<DropdownMenuItem>Auto</DropdownMenuItem>
<DropdownMenuItem>Agent</DropdownMenuItem>
<DropdownMenuItem>Manual</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
<InputGroupText css={{ ml: "auto" }}>52% used</InputGroupText>
<Separator orientation="vertical" css={{ h: "4!" }} />
<InputGroupButton css={{ rounded: "full" }} size="icon-xs" disabled>
<LuArrowUp />
<styled.span css={{ srOnly: true }}>Send</styled.span>
</InputGroupButton>
</InputGroupAddon>
</InputGroup>
<InputGroup>
<InputGroupInput placeholder="@shadcn" />
<InputGroupAddon align="inline-end">
<styled.div
css={{
bg: "primary",
color: "primary.fg",
display: "flex",
w: "4",
h: "4",
alignItems: "center",
justifyContent: "center",
rounded: "full",
}}
>
<TbCheck className={css({ w: "3", h: "3" })} />
</styled.div>
</InputGroupAddon>
</InputGroup>
</styled.div>
);
}
Installation
npx nore-ui-cli@latest add input-groupUsage
import {
InputGroup,
InputGroupAddon,
InputGroupButton,
InputGroupInput,
InputGroupText,
InputGroupTextarea,
} from "@/components/ui/input-group";
<InputGroup>
<InputGroupInput placeholder="Search..." />
<InputGroupAddon>
<SearchIcon />
</InputGroupAddon>
<InputGroupAddon align="inline-end">
<InputGroupButton>Search</InputGroupButton>
</InputGroupAddon>
</InputGroup>
Examples
Icon
import { LuCheck, LuCreditCard, LuInfo, LuMail, LuSearch, LuStar } from "react-icons/lu";
import { styled } from "styled-system/jsx";
import { InputGroup, InputGroupAddon, InputGroupInput } from "@/components/ui/input-group";
export default function InputGroupIcon() {
return (
<styled.div css={{ display: "grid", w: "full", maxW: "sm", gap: "6" }}>
<InputGroup>
<InputGroupInput placeholder="Search..." />
<InputGroupAddon>
<LuSearch />
</InputGroupAddon>
</InputGroup>
<InputGroup>
<InputGroupInput type="email" placeholder="Enter your email" />
<InputGroupAddon>
<LuMail />
</InputGroupAddon>
</InputGroup>
<InputGroup>
<InputGroupInput placeholder="Card number" />
<InputGroupAddon>
<LuCreditCard />
</InputGroupAddon>
<InputGroupAddon align="inline-end">
<LuCheck />
</InputGroupAddon>
</InputGroup>
<InputGroup>
<InputGroupInput placeholder="Card number" />
<InputGroupAddon align="inline-end">
<LuStar />
<LuInfo />
</InputGroupAddon>
</InputGroup>
</styled.div>
);
}
Text
Display additional text information alongside inputs.
import { styled } from "styled-system/jsx";
import {
InputGroup,
InputGroupAddon,
InputGroupInput,
InputGroupText,
InputGroupTextarea,
} from "@/components/ui/input-group";
export default function InputGroupTextExample() {
return (
<styled.div css={{ display: "grid", w: "full", maxW: "sm", gap: "6" }}>
<InputGroup>
<InputGroupAddon>
<InputGroupText>$</InputGroupText>
</InputGroupAddon>
<InputGroupInput placeholder="0.00" />
<InputGroupAddon align="inline-end">
<InputGroupText>USD</InputGroupText>
</InputGroupAddon>
</InputGroup>
<InputGroup>
<InputGroupAddon>
<InputGroupText>https://</InputGroupText>
</InputGroupAddon>
<InputGroupInput placeholder="example.com" css={{ pl: "0.5!" }} />
<InputGroupAddon align="inline-end">
<InputGroupText>.com</InputGroupText>
</InputGroupAddon>
</InputGroup>
<InputGroup>
<InputGroupInput placeholder="Enter your username" />
<InputGroupAddon align="inline-end">
<InputGroupText>@company.com</InputGroupText>
</InputGroupAddon>
</InputGroup>
<InputGroup>
<InputGroupTextarea placeholder="Enter your message" />
<InputGroupAddon align="block-end">
<InputGroupText css={{ color: "muted.fg", textStyle: "xs" }}>
120 characters left
</InputGroupText>
</InputGroupAddon>
</InputGroup>
</styled.div>
);
}
Button
Add buttons to perform actions within the input group.
"use client";
import * as React from "react";
import { TbCheck, TbCopy, TbInfoCircle, TbStar } from "react-icons/tb";
import { css } from "styled-system/css";
import { styled } from "styled-system/jsx";
import { useCopyToClipboard } from "@/hooks/use-copy-to-clipboard";
import {
InputGroup,
InputGroupAddon,
InputGroupButton,
InputGroupInput,
} from "@/components/ui/input-group";
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
export default function InputGroupButtonExample() {
const { copyToClipboard, isCopied } = useCopyToClipboard();
const [isFavorite, setIsFavorite] = React.useState(false);
return (
<styled.div css={{ display: "grid", w: "full", maxW: "sm", gap: "6" }}>
<InputGroup>
<InputGroupInput placeholder="https://x.com/shadcn" readOnly />
<InputGroupAddon align="inline-end">
<InputGroupButton
aria-label="Copy"
title="Copy"
size="icon-xs"
onClick={() => {
copyToClipboard("https://x.com/shadcn");
}}
>
{isCopied ? <TbCheck /> : <TbCopy />}
</InputGroupButton>
</InputGroupAddon>
</InputGroup>
<InputGroup css={{ rounded: "full" }}>
<Popover>
<PopoverTrigger asChild>
<InputGroupAddon>
<InputGroupButton variant="secondary" size="icon-xs" css={{ rounded: "full" }}>
<TbInfoCircle />
</InputGroupButton>
</InputGroupAddon>
</PopoverTrigger>
<PopoverContent
align="start"
css={{ display: "flex", flexDir: "column", gap: "1", rounded: "xl", textStyle: "sm" }}
>
<styled.p css={{ fontWeight: "medium" }}>Your connection is not secure.</styled.p>
<p>You should not enter any sensitive information on this site.</p>
</PopoverContent>
</Popover>
<InputGroupAddon css={{ color: "muted.fg", pl: "1.5" }}>https://</InputGroupAddon>
<InputGroupInput id="input-secure-19" />
<InputGroupAddon align="inline-end">
<InputGroupButton onClick={() => setIsFavorite(!isFavorite)} size="icon-xs">
<TbStar
data-favorite={isFavorite}
className={css({
"&[data-favorite=true]": {
fill: "blue.600",
stroke: "blue.600",
},
})}
/>
</InputGroupButton>
</InputGroupAddon>
</InputGroup>
<InputGroup>
<InputGroupInput placeholder="Type to search..." />
<InputGroupAddon align="inline-end">
<InputGroupButton variant="secondary">Search</InputGroupButton>
</InputGroupAddon>
</InputGroup>
</styled.div>
);
}
Tooltip
Add tooltips to provide additional context or help.
import { LuCircleHelp, LuInfo } from "react-icons/lu";
import { styled } from "styled-system/jsx";
import {
InputGroup,
InputGroupAddon,
InputGroupButton,
InputGroupInput,
} from "@/components/ui/input-group";
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
export default function InputGroupTooltip() {
return (
<styled.div css={{ display: "grid", w: "full", maxW: "sm", gap: "4" }}>
<InputGroup>
<InputGroupInput placeholder="Enter password" type="password" />
<InputGroupAddon align="inline-end">
<Tooltip>
<TooltipTrigger asChild>
<InputGroupButton variant="ghost" aria-label="Info" size="icon-xs">
<LuInfo />
</InputGroupButton>
</TooltipTrigger>
<TooltipContent>
<p>Password must be at least 8 characters</p>
</TooltipContent>
</Tooltip>
</InputGroupAddon>
</InputGroup>
<InputGroup>
<InputGroupInput placeholder="Your email address" />
<InputGroupAddon align="inline-end">
<Tooltip>
<TooltipTrigger asChild>
<InputGroupButton variant="ghost" aria-label="Help" size="icon-xs">
<LuCircleHelp />
</InputGroupButton>
</TooltipTrigger>
<TooltipContent>
<p>We'll use this to send you notifications</p>
</TooltipContent>
</Tooltip>
</InputGroupAddon>
</InputGroup>
<InputGroup>
<InputGroupInput placeholder="Enter API key" />
<Tooltip>
<TooltipTrigger asChild>
<InputGroupAddon>
<InputGroupButton variant="ghost" aria-label="Help" size="icon-xs">
<LuCircleHelp />
</InputGroupButton>
</InputGroupAddon>
</TooltipTrigger>
<TooltipContent side="left">
<p>Click for help with API keys</p>
</TooltipContent>
</Tooltip>
</InputGroup>
</styled.div>
);
}
Textarea
Input groups also work with textarea components. Use block-start or block-end for alignment.
import { TbBrandJavascript, TbCopy, TbCornerDownLeft, TbRefresh } from "react-icons/tb";
import { styled } from "styled-system/jsx";
import {
InputGroup,
InputGroupAddon,
InputGroupButton,
InputGroupText,
InputGroupTextarea,
} from "@/components/ui/input-group";
export default function InputGroupTextareaExample() {
return (
<styled.div css={{ display: "grid", w: "full", maxW: "md", gap: "4" }}>
<InputGroup>
<InputGroupTextarea
id="textarea-code-32"
placeholder="console.log('Hello, world!');"
css={{ minH: "200px" }}
/>
<InputGroupAddon align="block-end" css={{ borderTopWidth: "1px" }}>
<InputGroupText>Line 1, Column 1</InputGroupText>
<InputGroupButton size="sm" css={{ ml: "auto" }}>
Run <TbCornerDownLeft />
</InputGroupButton>
</InputGroupAddon>
<InputGroupAddon align="block-start" css={{ borderBottomWidth: "1px" }}>
<InputGroupText css={{ fontFamily: "mono", fontWeight: "medium" }}>
<TbBrandJavascript />
script.js
</InputGroupText>
<InputGroupButton css={{ ml: "auto" }} size="icon-xs">
<TbRefresh />
</InputGroupButton>
<InputGroupButton variant="ghost" size="icon-xs">
<TbCopy />
</InputGroupButton>
</InputGroupAddon>
</InputGroup>
</styled.div>
);
}
Spinner
Show loading indicators while processing input.
import { LuLoader } from "react-icons/lu";
import { css } from "styled-system/css";
import { styled } from "styled-system/jsx";
import {
InputGroup,
InputGroupAddon,
InputGroupInput,
InputGroupText,
} from "@/components/ui/input-group";
import { Spinner } from "@/components/ui/spinner";
export default function InputGroupSpinner() {
return (
<styled.div css={{ display: "grid", w: "full", maxW: "sm", gap: "4" }}>
<InputGroup data-disabled>
<InputGroupInput placeholder="Searching..." disabled />
<InputGroupAddon align="inline-end">
<Spinner />
</InputGroupAddon>
</InputGroup>
<InputGroup data-disabled>
<InputGroupInput placeholder="Processing..." disabled />
<InputGroupAddon>
<Spinner />
</InputGroupAddon>
</InputGroup>
<InputGroup data-disabled>
<InputGroupInput placeholder="Saving changes..." disabled />
<InputGroupAddon align="inline-end">
<InputGroupText>Saving...</InputGroupText>
<Spinner />
</InputGroupAddon>
</InputGroup>
<InputGroup data-disabled>
<InputGroupInput placeholder="Refreshing data..." disabled />
<InputGroupAddon>
<LuLoader className={css({ animation: "spin" })} />
</InputGroupAddon>
<InputGroupAddon align="inline-end">
<InputGroupText css={{ color: "muted.fg" }}>Please wait...</InputGroupText>
</InputGroupAddon>
</InputGroup>
</styled.div>
);
}
Label
Add labels within input groups to improve accessibility.
import { LuInfo } from "react-icons/lu";
import { styled } from "styled-system/jsx";
import {
InputGroup,
InputGroupAddon,
InputGroupButton,
InputGroupInput,
} from "@/components/ui/input-group";
import { Label } from "@/components/ui/label";
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
export default function InputGroupLabel() {
return (
<styled.div css={{ display: "grid", w: "full", maxW: "sm", gap: "4" }}>
<InputGroup>
<InputGroupInput id="email" placeholder="shadcn" />
<InputGroupAddon>
<Label htmlFor="email">@</Label>
</InputGroupAddon>
</InputGroup>
<InputGroup>
<InputGroupInput id="email-2" placeholder="[email protected]" />
<InputGroupAddon align="block-start">
<Label htmlFor="email-2" css={{ color: "fg" }}>
Email
</Label>
<Tooltip>
<TooltipTrigger asChild>
<InputGroupButton
variant="ghost"
aria-label="Help"
css={{ ml: "auto", rounded: "full" }}
size="icon-xs"
>
<LuInfo />
</InputGroupButton>
</TooltipTrigger>
<TooltipContent>
<p>We'll use this to send you notifications</p>
</TooltipContent>
</Tooltip>
</InputGroupAddon>
</InputGroup>
</styled.div>
);
}
Dropdown
Pair input groups with dropdown menus for complex interactions.
import { LuChevronDown, LuEllipsis } from "react-icons/lu";
import { css } from "styled-system/css";
import { styled } from "styled-system/jsx";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import {
InputGroup,
InputGroupAddon,
InputGroupButton,
InputGroupInput,
} from "@/components/ui/input-group";
export default function InputGroupDropdown() {
return (
<styled.div css={{ display: "grid", w: "full", maxW: "sm", gap: "4" }}>
<InputGroup>
<InputGroupInput placeholder="Enter file name" />
<InputGroupAddon align="inline-end">
<DropdownMenu>
<DropdownMenuTrigger asChild>
<InputGroupButton variant="ghost" aria-label="More" size="icon-xs">
<LuEllipsis />
</InputGroupButton>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem>Settings</DropdownMenuItem>
<DropdownMenuItem>Copy path</DropdownMenuItem>
<DropdownMenuItem>Open location</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</InputGroupAddon>
</InputGroup>
<InputGroup>
<InputGroupInput placeholder="Enter search query" />
<InputGroupAddon align="inline-end">
<DropdownMenu>
<DropdownMenuTrigger asChild>
<InputGroupButton variant="ghost" css={{ pr: "1.5!", textStyle: "xs" }}>
Search In... <LuChevronDown className={css({ w: "3", h: "3" })} />
</InputGroupButton>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem>Documentation</DropdownMenuItem>
<DropdownMenuItem>Blog Posts</DropdownMenuItem>
<DropdownMenuItem>Changelog</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</InputGroupAddon>
</InputGroup>
</styled.div>
);
}
Button Group
Wrap input groups with button groups to create prefixes and suffixes.
import { LuLink2 } from "react-icons/lu";
import { styled } from "styled-system/jsx";
import { ButtonGroup, ButtonGroupText } from "@/components/ui/button-group";
import { InputGroup, InputGroupAddon, InputGroupInput } from "@/components/ui/input-group";
import { Label } from "@/components/ui/label";
export default function InputGroupButtonGroup() {
return (
<styled.div css={{ display: "grid", w: "full", maxW: "sm", gap: "6" }}>
<ButtonGroup>
<ButtonGroupText asChild>
<Label htmlFor="url">https://</Label>
</ButtonGroupText>
<InputGroup>
<InputGroupInput id="url" />
<InputGroupAddon align="inline-end">
<LuLink2 />
</InputGroupAddon>
</InputGroup>
<ButtonGroupText>.com</ButtonGroupText>
</ButtonGroup>
</styled.div>
);
}
Custom Input
Add the data-slot="input-group-control" attribute to your custom input for automatic behavior and focus state handling.
No style is applied to the custom input. Apply your own styles using the className prop.
"use client";
import { styled } from "styled-system/jsx";
import { InputGroup, InputGroupAddon, InputGroupButton } from "@/components/ui/input-group";
export default function InputGroupCustom() {
return (
<styled.div css={{ display: "grid", w: "full", maxW: "sm", gap: "6" }}>
<InputGroup>
<styled.textarea
placeholder="Autoresize textarea..."
css={{
display: "flex",
fieldSizing: "content",
minH: "16",
w: "full",
resize: "none",
rounded: "md",
bg: "transparent",
px: "3",
py: "2.5",
textStyle: "md",
transition: "common",
outline: "none",
md: { textStyle: "sm" },
}}
/>
<InputGroupAddon align="block-end">
<InputGroupButton css={{ ml: "auto" }} size="sm">
Submit
</InputGroupButton>
</InputGroupAddon>
</InputGroup>
</styled.div>
);
}
import TextareaAutosize from "react-textarea-autosize";
import { InputGroup, InputGroupAddon } from "@/component/ui/input-group";
export function InputGroupCustom() {
return (
<InputGroup>
<TextareaAutosize
data-slot="input-group-control"
className="dark:bg-input/30 flex field-sizing-content min-h-16 w-full resize-none rounded-md bg-transparent px-3 py-2 text-base transition-[color,box-shadow] outline-none"
placeholder="Autoresize textarea..."
/>
<InputGroupAddon align="block-end">how</InputGroupAddon>
</InputGroup>
);
}
API Reference
InputGroup
The main component that wraps inputs and addons.
| Prop | Type | Default |
|---|---|---|
css | SystemStyleObject |
<InputGroup>
<InputGroupInput />
<InputGroupAddon />
</InputGroup>
InputGroupAddon
Displays icons, text, buttons, or other content alongside inputs.
| Prop | Type | Default |
|---|---|---|
align | "inline-start" | "inline-end" | "block-start" | "block-end" | "inline-start" |
css | SystemStyleObject |
<InputGroupAddon align="inline-end">
<SearchIcon />
</InputGroupAddon>
For <InputGroupInput />, use the inline-start or inline-end alignment. For <InputGroupTextarea />, use the block-start or block-end alignment.
The InputGroupAddon component can have multiple InputGroupButton components and icons.
<InputGroupAddon>
<InputGroupButton>Button</InputGroupButton>
<InputGroupButton>Button</InputGroupButton>
</InputGroupAddon>
InputGroupButton
Displays buttons within input groups.
| Prop | Type | Default |
|---|---|---|
size | "xs" | "icon-xs" | "sm" | "icon-sm" | "xs" |
variant | "default" | "destructive" | "outline" | "secondary" | "ghost" | "link" | "ghost" |
css | SystemStyleObject |
<InputGroupButton>Button</InputGroupButton>
<InputGroupButton size="icon-xs" aria-label="Copy">
<CopyIcon />
</InputGroupButton>
InputGroupInput
Replacement for <Input /> when building input groups. This component has the input group styles pre-applied and uses the unified data-slot="input-group-control" for focus state handling.
| Prop | Type | Default |
|---|---|---|
css | SystemStyleObject |
All other props are passed through to the underlying <Input /> component.
<InputGroup>
<InputGroupInput placeholder="Enter text..." />
<InputGroupAddon>
<SearchIcon />
</InputGroupAddon>
</InputGroup>
InputGroupTextarea
Replacement for <Textarea /> when building input groups. This component has the textarea group styles pre-applied and uses the unified data-slot="input-group-control" for focus state handling.
| Prop | Type | Default |
|---|---|---|
css | SystemStyleObject |
All other props are passed through to the underlying <Textarea /> component.
<InputGroup>
<InputGroupTextarea placeholder="Enter message..." />
<InputGroupAddon align="block-end">
<InputGroupButton>Send</InputGroupButton>
</InputGroupAddon>
</InputGroup>