Input Group
Composes an input with leading/trailing addons, text labels, and icon buttons.
Examples
Basic
Input fields wrapped in an InputGroup, composed with Field for labels and disabled states.
import { Field, FieldGroup, FieldLabel } from "@codefast/ui/field";
import { Input } from "@codefast/ui/input";
import { InputGroup, InputGroupInput } from "@codefast/ui/input-group";
export function InputGroupBasic() {
return (
<FieldGroup>
<Field>
<FieldLabel htmlFor="input-default-01">Default (No Input Group)</FieldLabel>
<Input placeholder="Placeholder" id="input-default-01" />
</Field>
<Field>
<FieldLabel htmlFor="input-group-02">Input Group</FieldLabel>
<InputGroup>
<InputGroupInput id="input-group-02" placeholder="Placeholder" />
</InputGroup>
</Field>
<Field data-disabled="true">
<FieldLabel htmlFor="input-disabled-03">Disabled</FieldLabel>
<InputGroup>
<InputGroupInput id="input-disabled-03" placeholder="This field is disabled" disabled />
</InputGroup>
</Field>
<Field data-invalid="true">
<FieldLabel htmlFor="input-invalid-04">Invalid</FieldLabel>
<InputGroup>
<InputGroupInput id="input-invalid-04" placeholder="This field is invalid" aria-invalid="true" />
</InputGroup>
</Field>
</FieldGroup>
);
}
block-end
Use align='block-end' to position the addon below the input.
Footer positioned below the input.
Footer positioned below the textarea.
import { Field, FieldDescription, FieldGroup, FieldLabel } from "@codefast/ui/field";
import {
InputGroup,
InputGroupAddon,
InputGroupButton,
InputGroupInput,
InputGroupText,
InputGroupTextarea,
} from "@codefast/ui/input-group";
export function InputGroupBlockEnd() {
return (
<FieldGroup className="max-w-sm">
<Field>
<FieldLabel htmlFor="block-end-input">Input</FieldLabel>
<InputGroup className="h-auto">
<InputGroupInput id="block-end-input" placeholder="Enter amount" />
<InputGroupAddon align="block-end">
<InputGroupText>USD</InputGroupText>
</InputGroupAddon>
</InputGroup>
<FieldDescription>Footer positioned below the input.</FieldDescription>
</Field>
<Field>
<FieldLabel htmlFor="block-end-textarea">Textarea</FieldLabel>
<InputGroup>
<InputGroupTextarea id="block-end-textarea" placeholder="Write a comment..." />
<InputGroupAddon align="block-end">
<InputGroupText>0/280</InputGroupText>
<InputGroupButton variant="default" size="sm" className="ms-auto">
Post
</InputGroupButton>
</InputGroupAddon>
</InputGroup>
<FieldDescription>Footer positioned below the textarea.</FieldDescription>
</Field>
</FieldGroup>
);
}
block-start
Use align='block-start' to position the addon above the input.
Header positioned above the input.
Header positioned above the textarea.
import { Field, FieldDescription, FieldGroup, FieldLabel } from "@codefast/ui/field";
import {
InputGroup,
InputGroupAddon,
InputGroupButton,
InputGroupInput,
InputGroupText,
InputGroupTextarea,
} from "@codefast/ui/input-group";
import { CopyIcon, FileCodeIcon } from "lucide-react";
export function InputGroupBlockStart() {
return (
<FieldGroup className="max-w-sm">
<Field>
<FieldLabel htmlFor="block-start-input">Input</FieldLabel>
<InputGroup className="h-auto">
<InputGroupInput id="block-start-input" placeholder="Enter your name" />
<InputGroupAddon align="block-start">
<InputGroupText>Full Name</InputGroupText>
</InputGroupAddon>
</InputGroup>
<FieldDescription>Header positioned above the input.</FieldDescription>
</Field>
<Field>
<FieldLabel htmlFor="block-start-textarea">Textarea</FieldLabel>
<InputGroup>
<InputGroupTextarea
id="block-start-textarea"
placeholder="console.log('Hello, world!');"
className="font-mono text-sm"
/>
<InputGroupAddon align="block-start">
<FileCodeIcon className="text-muted-foreground" />
<InputGroupText className="font-mono">script.js</InputGroupText>
<InputGroupButton size="icon-xs" className="ms-auto">
<CopyIcon />
<span className="sr-only">Copy</span>
</InputGroupButton>
</InputGroupAddon>
</InputGroup>
<FieldDescription>Header positioned above the textarea.</FieldDescription>
</Field>
</FieldGroup>
);
}
Custom Input
Here's an example of a custom resizable textarea from a third-party library.
import { InputGroup, InputGroupAddon, InputGroupButton } from "@codefast/ui/input-group";
export function InputGroupCustom() {
return (
<div className="grid w-full max-w-sm gap-6">
<InputGroup>
<textarea
data-slot="input-group-control"
className="flex field-sizing-content min-h-16 w-full resize-none rounded-md bg-transparent px-3 py-2.5 text-base transition-[color,box-shadow] outline-none md:text-sm"
aria-label="Autoresize textarea"
placeholder="Autoresize textarea..."
/>
<InputGroupAddon align="block-end">
<InputGroupButton className="ms-auto" size="sm" variant="default">
Submit
</InputGroupButton>
</InputGroupAddon>
</InputGroup>
</div>
);
}
Dropdown
Add addons, buttons, and helper content to inputs.
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuGroup,
DropdownMenuItem,
DropdownMenuTrigger,
} from "@codefast/ui/dropdown-menu";
import { InputGroup, InputGroupAddon, InputGroupButton, InputGroupInput } from "@codefast/ui/input-group";
import { ChevronDownIcon, MoreHorizontal } from "lucide-react";
export function InputGroupDropdown() {
return (
<div className="grid w-full max-w-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">
<MoreHorizontal />
</InputGroupButton>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuGroup>
<DropdownMenuItem>Settings</DropdownMenuItem>
<DropdownMenuItem>Copy path</DropdownMenuItem>
<DropdownMenuItem>Open location</DropdownMenuItem>
</DropdownMenuGroup>
</DropdownMenuContent>
</DropdownMenu>
</InputGroupAddon>
</InputGroup>
<InputGroup className="[--radius:1rem]">
<InputGroupInput placeholder="Enter search query" />
<InputGroupAddon align="inline-end">
<DropdownMenu>
<DropdownMenuTrigger asChild>
<InputGroupButton variant="ghost" className="pe-1.5! text-xs">
Search In... <ChevronDownIcon className="size-3" />
</InputGroupButton>
</DropdownMenuTrigger>
<DropdownMenuContent align="end" className="[--radius:0.95rem]">
<DropdownMenuGroup>
<DropdownMenuItem>Documentation</DropdownMenuItem>
<DropdownMenuItem>Blog Posts</DropdownMenuItem>
<DropdownMenuItem>Changelog</DropdownMenuItem>
</DropdownMenuGroup>
</DropdownMenuContent>
</DropdownMenu>
</InputGroupAddon>
</InputGroup>
</div>
);
}
Icon
Add addons, buttons, and helper content to inputs.
import { InputGroup, InputGroupAddon, InputGroupInput } from "@codefast/ui/input-group";
import { CheckIcon, CreditCardIcon, InfoIcon, MailIcon, SearchIcon, StarIcon } from "lucide-react";
export function InputGroupIcon() {
return (
<div className="grid w-full max-w-sm gap-6">
<InputGroup>
<InputGroupInput placeholder="Search..." />
<InputGroupAddon>
<SearchIcon />
</InputGroupAddon>
</InputGroup>
<InputGroup>
<InputGroupInput type="email" placeholder="Enter your email" />
<InputGroupAddon>
<MailIcon />
</InputGroupAddon>
</InputGroup>
<InputGroup>
<InputGroupInput placeholder="Card number" />
<InputGroupAddon>
<CreditCardIcon />
</InputGroupAddon>
<InputGroupAddon align="inline-end">
<CheckIcon />
</InputGroupAddon>
</InputGroup>
<InputGroup>
<InputGroupInput placeholder="Card number" />
<InputGroupAddon align="inline-end">
<StarIcon />
<InfoIcon />
</InputGroupAddon>
</InputGroup>
</div>
);
}
In Card
Use input groups inside a Card form with fields, addons and a textarea.
import { Button } from "@codefast/ui/button";
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "@codefast/ui/card";
import { Field, FieldGroup, FieldLabel } from "@codefast/ui/field";
import {
InputGroup,
InputGroupAddon,
InputGroupInput,
InputGroupText,
InputGroupTextarea,
} from "@codefast/ui/input-group";
import { ExternalLinkIcon, MailIcon } from "lucide-react";
export function InputGroupInCard() {
return (
<Card className="w-full">
<CardHeader>
<CardTitle>Card with Input Group</CardTitle>
<CardDescription>This is a card with an input group.</CardDescription>
</CardHeader>
<CardContent>
<FieldGroup>
<Field>
<FieldLabel htmlFor="email-input">Email Address</FieldLabel>
<InputGroup>
<InputGroupInput id="email-input" type="email" placeholder="you@example.com" />
<InputGroupAddon align="inline-end">
<MailIcon />
</InputGroupAddon>
</InputGroup>
</Field>
<Field>
<FieldLabel htmlFor="website-input">Website URL</FieldLabel>
<InputGroup>
<InputGroupAddon>
<InputGroupText>https://</InputGroupText>
</InputGroupAddon>
<InputGroupInput id="website-input" placeholder="example.com" />
<InputGroupAddon align="inline-end">
<ExternalLinkIcon />
</InputGroupAddon>
</InputGroup>
</Field>
<Field>
<FieldLabel htmlFor="feedback-textarea">Feedback & Comments</FieldLabel>
<InputGroup>
<InputGroupTextarea id="feedback-textarea" placeholder="Share your thoughts..." className="min-h-25" />
<InputGroupAddon align="block-end">
<InputGroupText>0/500 characters</InputGroupText>
</InputGroupAddon>
</InputGroup>
</Field>
</FieldGroup>
</CardContent>
<CardFooter className="justify-end gap-2">
<Button variant="outline">Cancel</Button>
<Button>Submit</Button>
</CardFooter>
</Card>
);
}
inline-end
Use align='inline-end' to position the addon at the end of the input.
Icon positioned at the end.
import { Field, FieldDescription, FieldLabel } from "@codefast/ui/field";
import { InputGroup, InputGroupAddon, InputGroupInput } from "@codefast/ui/input-group";
import { EyeOffIcon } from "lucide-react";
export function InputGroupInlineEnd() {
return (
<Field className="max-w-sm">
<FieldLabel htmlFor="inline-end-input">Input</FieldLabel>
<InputGroup>
<InputGroupInput id="inline-end-input" type="password" placeholder="Enter password" />
<InputGroupAddon align="inline-end">
<EyeOffIcon />
</InputGroupAddon>
</InputGroup>
<FieldDescription>Icon positioned at the end.</FieldDescription>
</Field>
);
}
inline-start
Use align='inline-start' to position the addon at the start of the input. This is the default.
Icon positioned at the start.
import { Field, FieldDescription, FieldLabel } from "@codefast/ui/field";
import { InputGroup, InputGroupAddon, InputGroupInput } from "@codefast/ui/input-group";
import { SearchIcon } from "lucide-react";
export function InputGroupInlineStart() {
return (
<Field className="max-w-sm">
<FieldLabel htmlFor="inline-start-input">Input</FieldLabel>
<InputGroup>
<InputGroupInput id="inline-start-input" placeholder="Search..." />
<InputGroupAddon align="inline-start">
<SearchIcon className="text-muted-foreground" />
</InputGroupAddon>
</InputGroup>
<FieldDescription>Icon positioned at the start.</FieldDescription>
</Field>
);
}
Kbd
Add addons, buttons, and helper content to inputs.
import { InputGroup, InputGroupAddon, InputGroupInput } from "@codefast/ui/input-group";
import { Kbd } from "@codefast/ui/kbd";
import { SearchIcon } from "lucide-react";
export function InputGroupKbd() {
return (
<InputGroup className="max-w-sm">
<InputGroupInput placeholder="Search..." />
<InputGroupAddon>
<SearchIcon className="text-muted-foreground" />
</InputGroupAddon>
<InputGroupAddon align="inline-end">
<Kbd>⌘K</Kbd>
</InputGroupAddon>
</InputGroup>
);
}
With Label
Add a Label inside an InputGroupAddon to title the input.
import { InputGroup, InputGroupAddon, InputGroupButton, InputGroupInput } from "@codefast/ui/input-group";
import { Label } from "@codefast/ui/label";
import { Tooltip, TooltipContent, TooltipTrigger } from "@codefast/ui/tooltip";
import { InfoIcon } from "lucide-react";
export function InputGroupLabel() {
return (
<div className="grid w-full max-w-sm gap-4">
<InputGroup>
<InputGroupInput id="email" placeholder="codefast" />
<InputGroupAddon>
<Label htmlFor="email">@</Label>
</InputGroupAddon>
</InputGroup>
<InputGroup>
<InputGroupInput id="email-2" placeholder="hello@codefastlabs.com" />
<InputGroupAddon align="block-start">
<Label htmlFor="email-2" className="text-foreground">
Email
</Label>
<Tooltip>
<TooltipTrigger asChild>
<InputGroupButton variant="ghost" aria-label="Help" className="ms-auto rounded-full" size="icon-xs">
<InfoIcon />
</InputGroupButton>
</TooltipTrigger>
<TooltipContent>
<p>We'll use this to send you notifications</p>
</TooltipContent>
</Tooltip>
</InputGroupAddon>
</InputGroup>
</div>
);
}
RTL
Right-to-left layout support for languages such as Arabic and Hebrew.
Translations are AI-generated for demonstration and may be imperfect.
تذييل موضع أسفل منطقة النص.
import { Field, FieldDescription, FieldGroup, FieldLabel } from "@codefast/ui/field";
import {
InputGroup,
InputGroupAddon,
InputGroupButton,
InputGroupInput,
InputGroupText,
InputGroupTextarea,
} from "@codefast/ui/input-group";
import { Spinner } from "@codefast/ui/spinner";
import { Search } from "lucide-react";
import type { Translations } from "#/features/components-catalog/components/detail/language";
import { useTranslation } from "#/features/components-catalog/components/detail/language-context";
const translations: Translations = {
en: {
dir: "ltr",
values: {
placeholder: "Search...",
results: "12 results",
searching: "Searching...",
saving: "Saving...",
savingChanges: "Saving changes...",
textareaLabel: "Textarea",
textareaPlaceholder: "Write a comment...",
characterCount: "0/280",
post: "Post",
textareaDescription: "Footer positioned below the textarea.",
},
},
ar: {
dir: "rtl",
values: {
placeholder: "بحث...",
results: "١٢ نتيجة",
searching: "جاري البحث...",
saving: "جاري الحفظ...",
savingChanges: "جاري حفظ التغييرات...",
textareaLabel: "منطقة النص",
textareaPlaceholder: "اكتب تعليقًا...",
characterCount: "٠/٢٨٠",
post: "نشر",
textareaDescription: "تذييل موضع أسفل منطقة النص.",
},
},
he: {
dir: "rtl",
values: {
placeholder: "חפש...",
results: "12 תוצאות",
searching: "מחפש...",
saving: "שומר...",
savingChanges: "שומר שינויים...",
textareaLabel: "אזור טקסט",
textareaPlaceholder: "כתוב תגובה...",
characterCount: "0/280",
post: "פרסם",
textareaDescription: "כותרת תחתונה ממוקמת מתחת לאזור הטקסט.",
},
},
};
export function InputGroupRtl() {
const { dir, t } = useTranslation(translations, "ar");
return (
<div className="grid w-full max-w-sm gap-6">
<InputGroup className="max-w-xs" dir={dir}>
<InputGroupInput placeholder={t.placeholder} />
<InputGroupAddon>
<Search />
</InputGroupAddon>
<InputGroupAddon align="inline-end">{t.results}</InputGroupAddon>
</InputGroup>
<InputGroup dir={dir}>
<InputGroupInput placeholder={t.searching} />
<InputGroupAddon align="inline-end">
<Spinner />
</InputGroupAddon>
</InputGroup>
<InputGroup dir={dir}>
<InputGroupInput placeholder={t.savingChanges} />
<InputGroupAddon align="inline-end">
<InputGroupText>{t.saving}</InputGroupText>
<Spinner />
</InputGroupAddon>
</InputGroup>
<FieldGroup className="max-w-sm" dir={dir}>
<Field>
<FieldLabel htmlFor="rtl-textarea">{t.textareaLabel}</FieldLabel>
<InputGroup dir={dir}>
<InputGroupTextarea id="rtl-textarea" placeholder={t.textareaPlaceholder} />
<InputGroupAddon align="block-end">
<InputGroupText>{t.characterCount}</InputGroupText>
<InputGroupButton variant="default" size="sm" className="ms-auto">
{t.post}
</InputGroupButton>
</InputGroupAddon>
</InputGroup>
<FieldDescription>{t.textareaDescription}</FieldDescription>
</Field>
</FieldGroup>
</div>
);
}
Spinner
Add addons, buttons, and helper content to inputs.
import { InputGroup, InputGroupAddon, InputGroupInput, InputGroupText } from "@codefast/ui/input-group";
import { Spinner } from "@codefast/ui/spinner";
import { LoaderIcon } from "lucide-react";
export function InputGroupSpinner() {
return (
<div className="grid w-full max-w-sm gap-4">
<InputGroup>
<InputGroupInput placeholder="Searching..." />
<InputGroupAddon align="inline-end">
<Spinner />
</InputGroupAddon>
</InputGroup>
<InputGroup>
<InputGroupInput placeholder="Processing..." />
<InputGroupAddon>
<Spinner />
</InputGroupAddon>
</InputGroup>
<InputGroup>
<InputGroupInput placeholder="Saving changes..." />
<InputGroupAddon align="inline-end">
<InputGroupText>Saving...</InputGroupText>
<Spinner />
</InputGroupAddon>
</InputGroup>
<InputGroup>
<InputGroupInput placeholder="Refreshing data..." />
<InputGroupAddon>
<LoaderIcon className="animate-spin" />
</InputGroupAddon>
<InputGroupAddon align="inline-end">
<InputGroupText className="text-muted-foreground">Please wait...</InputGroupText>
</InputGroupAddon>
</InputGroup>
</div>
);
}
Text
Add addons, buttons, and helper content to inputs.
import {
InputGroup,
InputGroupAddon,
InputGroupInput,
InputGroupText,
InputGroupTextarea,
} from "@codefast/ui/input-group";
export function InputGroupTextExample() {
return (
<div className="grid w-full max-w-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" className="ps-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 className="text-xs text-muted-foreground">120 characters left</InputGroupText>
</InputGroupAddon>
</InputGroup>
</div>
);
}
Textarea
Add addons, buttons, and helper content to inputs.
import {
InputGroup,
InputGroupAddon,
InputGroupButton,
InputGroupText,
InputGroupTextarea,
} from "@codefast/ui/input-group";
import { CodeIcon, CopyIcon, CornerDownLeftIcon, RefreshCcwIcon } from "lucide-react";
export function InputGroupTextareaExample() {
return (
<div className="grid w-full max-w-md gap-4">
<InputGroup>
<InputGroupTextarea id="textarea-code-32" placeholder="console.log('Hello, world!');" className="min-h-50" />
<InputGroupAddon align="block-end" className="border-t">
<InputGroupText>Line 1, Column 1</InputGroupText>
<InputGroupButton size="sm" className="ms-auto" variant="default">
Run <CornerDownLeftIcon />
</InputGroupButton>
</InputGroupAddon>
<InputGroupAddon align="block-start" className="border-b">
<InputGroupText className="font-mono font-medium">
<CodeIcon />
script.js
</InputGroupText>
<InputGroupButton className="ms-auto" size="icon-xs">
<RefreshCcwIcon />
</InputGroupButton>
<InputGroupButton variant="ghost" size="icon-xs">
<CopyIcon />
</InputGroupButton>
</InputGroupAddon>
</InputGroup>
</div>
);
}
Textarea Variations
More InputGroupTextarea compositions with toolbars, labels and actions.
This is a description of the input group.
This is a description of the input group.
This is a description of the input group.
This is a description of the input group.
import { Field, FieldDescription, FieldGroup, FieldLabel } from "@codefast/ui/field";
import {
InputGroup,
InputGroupAddon,
InputGroupButton,
InputGroupText,
InputGroupTextarea,
} from "@codefast/ui/input-group";
import { Textarea } from "@codefast/ui/textarea";
import { ArrowUpIcon, CodeIcon, CopyIcon, InfoIcon, RefreshCwIcon } from "lucide-react";
export function InputGroupTextareaExamples() {
return (
<FieldGroup>
<Field>
<FieldLabel htmlFor="textarea-header-footer-12">Default Textarea (No Input Group)</FieldLabel>
<Textarea id="textarea-header-footer-12" placeholder="Enter your text here..." />
</Field>
<Field>
<FieldLabel htmlFor="textarea-header-footer-13">Input Group</FieldLabel>
<InputGroup>
<InputGroupTextarea id="textarea-header-footer-13" placeholder="Enter your text here..." />
</InputGroup>
<FieldDescription>This is a description of the input group.</FieldDescription>
</Field>
<Field data-invalid="true">
<FieldLabel htmlFor="textarea-header-footer-14">Invalid</FieldLabel>
<InputGroup>
<InputGroupTextarea
id="textarea-header-footer-14"
placeholder="Enter your text here..."
aria-invalid="true"
/>
</InputGroup>
<FieldDescription>This is a description of the input group.</FieldDescription>
</Field>
<Field data-disabled="true">
<FieldLabel htmlFor="textarea-header-footer-15">Disabled</FieldLabel>
<InputGroup>
<InputGroupTextarea id="textarea-header-footer-15" placeholder="Enter your text here..." disabled />
</InputGroup>
<FieldDescription>This is a description of the input group.</FieldDescription>
</Field>
<Field>
<FieldLabel htmlFor="prompt-31">Addon (block-start)</FieldLabel>
<InputGroup>
<InputGroupTextarea id="prompt-31" />
<InputGroupAddon align="block-start">
<InputGroupText>Ask, Search or Chat...</InputGroupText>
<InfoIcon className="ms-auto text-muted-foreground" />
</InputGroupAddon>
</InputGroup>
<FieldDescription>This is a description of the input group.</FieldDescription>
</Field>
<Field>
<FieldLabel htmlFor="textarea-header-footer-30">Addon (block-end)</FieldLabel>
<InputGroup>
<InputGroupTextarea id="textarea-header-footer-30" placeholder="Enter your text here..." />
<InputGroupAddon align="block-end">
<InputGroupText>0/280 characters</InputGroupText>
<InputGroupButton variant="default" size="icon-xs" className="ms-auto rounded-full">
<ArrowUpIcon />
<span className="sr-only">Send</span>
</InputGroupButton>
</InputGroupAddon>
</InputGroup>
</Field>
<Field>
<FieldLabel htmlFor="textarea-comment-31">Addon (Buttons)</FieldLabel>
<InputGroup>
<InputGroupTextarea id="textarea-comment-31" placeholder="Share your thoughts..." className="min-h-30" />
<InputGroupAddon align="block-end">
<InputGroupButton variant="ghost" className="ms-auto" size="sm">
Cancel
</InputGroupButton>
<InputGroupButton variant="default" size="sm">
Post Comment
</InputGroupButton>
</InputGroupAddon>
</InputGroup>
</Field>
<Field>
<FieldLabel htmlFor="textarea-code-32">Code Editor</FieldLabel>
<InputGroup>
<InputGroupTextarea
id="textarea-code-32"
placeholder="console.log('Hello, world!');"
className="min-h-75 py-3"
/>
<InputGroupAddon align="block-start" className="border-b">
<InputGroupText className="font-mono font-medium">
<CodeIcon />
script.js
</InputGroupText>
<InputGroupButton size="icon-xs" className="ms-auto">
<RefreshCwIcon />
</InputGroupButton>
<InputGroupButton size="icon-xs" variant="ghost">
<CopyIcon />
</InputGroupButton>
</InputGroupAddon>
<InputGroupAddon align="block-end" className="border-t">
<InputGroupText>Line 1, Column 1</InputGroupText>
<InputGroupText className="ms-auto">JavaScript</InputGroupText>
</InputGroupAddon>
</InputGroup>
</Field>
</FieldGroup>
);
}
With Tooltip
Show contextual help on input group buttons with Tooltip.
import { InputGroup, InputGroupAddon, InputGroupButton, InputGroupInput } from "@codefast/ui/input-group";
import { Tooltip, TooltipContent, TooltipTrigger } from "@codefast/ui/tooltip";
import { HelpCircle, InfoIcon } from "lucide-react";
export function InputGroupTooltip() {
return (
<div className="grid w-full max-w-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">
<InfoIcon />
</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">
<HelpCircle />
</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">
<HelpCircle />
</InputGroupButton>
</InputGroupAddon>
</TooltipTrigger>
<TooltipContent side="left">
<p>Click for help with API keys</p>
</TooltipContent>
</Tooltip>
</InputGroup>
</div>
);
}
Addons
Inline icons, text and buttons as input group addons.
This is a description of the input group.
import { Field, FieldDescription, FieldGroup, FieldLabel } from "@codefast/ui/field";
import {
InputGroup,
InputGroupAddon,
InputGroupButton,
InputGroupInput,
InputGroupText,
} from "@codefast/ui/input-group";
import { toast } from "@codefast/ui/sonner";
import { CopyIcon, EyeOffIcon, InfoIcon, MicIcon, RadioIcon, SearchIcon, StarIcon } from "lucide-react";
export function InputGroupWithAddons() {
return (
<FieldGroup>
<Field>
<FieldLabel htmlFor="input-icon-left-05">Addon (inline-start)</FieldLabel>
<InputGroup>
<InputGroupInput id="input-icon-left-05" />
<InputGroupAddon>
<SearchIcon className="text-muted-foreground" />
</InputGroupAddon>
</InputGroup>
</Field>
<Field>
<FieldLabel htmlFor="input-icon-right-07">Addon (inline-end)</FieldLabel>
<InputGroup>
<InputGroupInput id="input-icon-right-07" />
<InputGroupAddon align="inline-end">
<EyeOffIcon />
</InputGroupAddon>
</InputGroup>
</Field>
<Field>
<FieldLabel htmlFor="input-icon-both-09">Addon (inline-start and inline-end)</FieldLabel>
<InputGroup>
<InputGroupInput id="input-icon-both-09" />
<InputGroupAddon>
<MicIcon className="text-muted-foreground" />
</InputGroupAddon>
<InputGroupAddon align="inline-end">
<RadioIcon className="animate-pulse text-red-500" />
</InputGroupAddon>
</InputGroup>
</Field>
<Field>
<FieldLabel htmlFor="input-addon-20">Addon (block-start)</FieldLabel>
<InputGroup className="h-auto">
<InputGroupInput id="input-addon-20" />
<InputGroupAddon align="block-start">
<InputGroupText>First Name</InputGroupText>
<InfoIcon className="ms-auto text-muted-foreground" />
</InputGroupAddon>
</InputGroup>
</Field>
<Field>
<FieldLabel htmlFor="input-addon-21">Addon (block-end)</FieldLabel>
<InputGroup className="h-auto">
<InputGroupInput id="input-addon-21" />
<InputGroupAddon align="block-end">
<InputGroupText>20/240 characters</InputGroupText>
<InfoIcon className="ms-auto text-muted-foreground" />
</InputGroupAddon>
</InputGroup>
</Field>
<Field>
<FieldLabel htmlFor="input-icon-both-10">Multiple Icons</FieldLabel>
<InputGroup>
<InputGroupInput id="input-icon-both-10" />
<InputGroupAddon align="inline-end">
<StarIcon />
<InputGroupButton size="icon-xs" onClick={() => toast("Copied to clipboard")}>
<CopyIcon />
</InputGroupButton>
</InputGroupAddon>
<InputGroupAddon>
<RadioIcon className="animate-pulse text-red-500" />
</InputGroupAddon>
</InputGroup>
</Field>
<Field>
<FieldLabel htmlFor="input-description-10">Description</FieldLabel>
<InputGroup>
<InputGroupInput id="input-description-10" />
<InputGroupAddon align="inline-end">
<InfoIcon />
</InputGroupAddon>
</InputGroup>
<FieldDescription>This is a description of the input group.</FieldDescription>
</Field>
<Field>
<FieldLabel htmlFor="input-label-10">Label</FieldLabel>
<InputGroup>
<InputGroupAddon>
<FieldLabel htmlFor="input-label-10">Label</FieldLabel>
</InputGroupAddon>
<InputGroupInput id="input-label-10" />
</InputGroup>
<InputGroup>
<InputGroupInput id="input-optional-12" aria-label="Optional" />
<InputGroupAddon align="inline-end">
<InputGroupText>(optional)</InputGroupText>
</InputGroupAddon>
</InputGroup>
</Field>
</FieldGroup>
);
}
With Kbd
Show keyboard shortcuts inside an input group with Kbd.
This username is available.
This is a description of the input group.
import { Field, FieldDescription, FieldGroup, FieldLabel } from "@codefast/ui/field";
import { InputGroup, InputGroupAddon, InputGroupInput } from "@codefast/ui/input-group";
import { Kbd, KbdGroup } from "@codefast/ui/kbd";
import { Spinner } from "@codefast/ui/spinner";
import { CheckIcon, InfoIcon, SearchIcon, SparklesIcon } from "lucide-react";
export function InputGroupWithKbd() {
return (
<FieldGroup>
<Field>
<FieldLabel htmlFor="input-kbd-22">Input Group with Kbd</FieldLabel>
<InputGroup>
<InputGroupInput id="input-kbd-22" />
<InputGroupAddon>
<Kbd>⌘K</Kbd>
</InputGroupAddon>
</InputGroup>
<InputGroup>
<InputGroupInput id="input-kbd-23" />
<InputGroupAddon align="inline-end">
<Kbd>⌘K</Kbd>
</InputGroupAddon>
</InputGroup>
<InputGroup>
<InputGroupInput id="input-search-apps-24" placeholder="Search for Apps..." />
<InputGroupAddon align="inline-end">Ask AI</InputGroupAddon>
<InputGroupAddon align="inline-end">
<Kbd>Tab</Kbd>
</InputGroupAddon>
</InputGroup>
<InputGroup>
<InputGroupInput id="input-search-type-25" placeholder="Type to search..." />
<InputGroupAddon align="inline-start">
<SparklesIcon />
</InputGroupAddon>
<InputGroupAddon align="inline-end">
<KbdGroup>
<Kbd>Ctrl</Kbd>
<Kbd>C</Kbd>
</KbdGroup>
</InputGroupAddon>
</InputGroup>
</Field>
<Field>
<FieldLabel htmlFor="input-username-26">Username</FieldLabel>
<InputGroup>
<InputGroupInput id="input-username-26" defaultValue="codefast" />
<InputGroupAddon align="inline-end">
<div className="flex size-4 items-center justify-center rounded-full bg-green-500 dark:bg-green-800">
<CheckIcon className="size-3 text-white" />
</div>
</InputGroupAddon>
</InputGroup>
<FieldDescription className="text-green-700 dark:text-green-400">This username is available.</FieldDescription>
</Field>
<InputGroup>
<InputGroupInput id="input-search-docs-27" placeholder="Search documentation..." />
<InputGroupAddon>
<SearchIcon />
</InputGroupAddon>
<InputGroupAddon align="inline-end">12 results</InputGroupAddon>
</InputGroup>
<InputGroup data-disabled="true">
<InputGroupInput id="input-search-disabled-28" placeholder="Search documentation..." disabled />
<InputGroupAddon>
<SearchIcon />
</InputGroupAddon>
<InputGroupAddon align="inline-end">Disabled</InputGroupAddon>
</InputGroup>
<FieldGroup className="grid grid-cols-2 gap-4">
<Field>
<FieldLabel htmlFor="input-group-11">First Name</FieldLabel>
<InputGroup>
<InputGroupInput id="input-group-11" placeholder="First Name" />
<InputGroupAddon align="inline-end">
<InfoIcon />
</InputGroupAddon>
</InputGroup>
</Field>
<Field>
<FieldLabel htmlFor="input-group-12">Last Name</FieldLabel>
<InputGroup>
<InputGroupInput id="input-group-12" placeholder="Last Name" />
<InputGroupAddon align="inline-end">
<InfoIcon />
</InputGroupAddon>
</InputGroup>
</Field>
</FieldGroup>
<Field data-disabled="true">
<FieldLabel htmlFor="input-group-29">Loading ("data-disabled="true")</FieldLabel>
<InputGroup>
<InputGroupInput id="input-group-29" disabled defaultValue="codefast" />
<InputGroupAddon align="inline-end">
<Spinner />
</InputGroupAddon>
</InputGroup>
<FieldDescription>This is a description of the input group.</FieldDescription>
</Field>
</FieldGroup>
);
}
Tooltip and Popover
Combine input groups with tooltips, popovers and dropdown menus.
This is a description of the input group.
This is a description of the input group.
This is a description of the input group.
This is a description of the input group.
import { ButtonGroup, ButtonGroupText } from "@codefast/ui/button-group";
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@codefast/ui/dropdown-menu";
import { Field, FieldDescription, FieldGroup, FieldLabel } from "@codefast/ui/field";
import { InputGroup, InputGroupAddon, InputGroupButton, InputGroupInput } from "@codefast/ui/input-group";
import {
Popover,
PopoverContent,
PopoverDescription,
PopoverHeader,
PopoverTitle,
PopoverTrigger,
} from "@codefast/ui/popover";
import { toast } from "@codefast/ui/sonner";
import { Tooltip, TooltipContent, TooltipTrigger } from "@codefast/ui/tooltip";
import { ChevronDownIcon, InfoIcon, StarIcon } from "lucide-react";
import { useState } from "react";
export function InputGroupWithTooltip() {
const [country, setCountry] = useState("+1");
return (
<FieldGroup>
<Field>
<FieldLabel htmlFor="input-tooltip-20">Tooltip</FieldLabel>
<InputGroup>
<InputGroupInput id="input-tooltip-20" />
<InputGroupAddon align="inline-end">
<Tooltip>
<TooltipTrigger asChild>
<InputGroupButton className="rounded-full" size="icon-xs">
<InfoIcon />
</InputGroupButton>
</TooltipTrigger>
<TooltipContent>This is content in a tooltip.</TooltipContent>
</Tooltip>
</InputGroupAddon>
</InputGroup>
<FieldDescription>This is a description of the input group.</FieldDescription>
</Field>
<Field>
<FieldLabel htmlFor="input-dropdown-21">Dropdown</FieldLabel>
<InputGroup>
<InputGroupInput id="input-dropdown-21" />
<InputGroupAddon>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<InputGroupButton className="text-muted-foreground tabular-nums">
{country} <ChevronDownIcon />
</InputGroupButton>
</DropdownMenuTrigger>
<DropdownMenuContent align="start" className="min-w-16" sideOffset={10} alignOffset={-8}>
<DropdownMenuItem onClick={() => setCountry("+1")}>+1</DropdownMenuItem>
<DropdownMenuItem onClick={() => setCountry("+44")}>+44</DropdownMenuItem>
<DropdownMenuItem onClick={() => setCountry("+46")}>+46</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</InputGroupAddon>
</InputGroup>
<FieldDescription>This is a description of the input group.</FieldDescription>
</Field>
<Field>
<FieldLabel htmlFor="input-secure-19">Popover</FieldLabel>
<InputGroup>
<Popover>
<PopoverTrigger asChild>
<InputGroupAddon>
<InputGroupButton variant="secondary" size="icon-xs">
<InfoIcon />
</InputGroupButton>
</InputGroupAddon>
</PopoverTrigger>
<PopoverContent align="start">
<PopoverHeader>
<PopoverTitle>Your connection is not secure.</PopoverTitle>
<PopoverDescription>You should not enter any sensitive information on this site.</PopoverDescription>
</PopoverHeader>
</PopoverContent>
</Popover>
<InputGroupAddon className="ps-1 text-muted-foreground">https://</InputGroupAddon>
<InputGroupInput id="input-secure-19" />
<InputGroupAddon align="inline-end">
<InputGroupButton size="icon-xs" onClick={() => toast("Added to favorites")}>
<StarIcon />
</InputGroupButton>
</InputGroupAddon>
</InputGroup>
<FieldDescription>This is a description of the input group.</FieldDescription>
</Field>
<Field>
<FieldLabel htmlFor="url">Button Group</FieldLabel>
<ButtonGroup>
<ButtonGroupText>https://</ButtonGroupText>
<InputGroup>
<InputGroupInput id="url" />
<InputGroupAddon align="inline-end">
<InfoIcon />
</InputGroupAddon>
</InputGroup>
<ButtonGroupText>.com</ButtonGroupText>
</ButtonGroup>
<FieldDescription>This is a description of the input group.</FieldDescription>
</Field>
</FieldGroup>
);
}
Usage
The minimal import and composition — see Examples below for styled, real-world variants.
import { InputGroup, InputGroupAddon, InputGroupInput } from "@codefast/ui/input-group";
import { SearchIcon } from "lucide-react";
export function InputGroupUsage() {
return (
<InputGroup>
<InputGroupInput placeholder="Search..." />
<InputGroupAddon>
<SearchIcon />
</InputGroupAddon>
</InputGroup>
);
}
Anatomy
How the parts nest — every slot the component exposes, in composition order.
Features
- Clicking anywhere in an InputGroupAddon (not on a button) focuses the sibling input/textarea automatically — no manual onClick wiring needed.
- Four addon positions — inline-start (default), inline-end, block-start, block-end — for icons, prefixes, or a toolbar row above/below the field.
- Works with InputGroupInput or InputGroupTextarea as the control, and composes with InputGroupButton, InputGroupText, Kbd, and Tooltip as addon content.
API reference
Props for each part of the component. All native element props are also forwarded.
InputGroup
A wrapper that frames the input with addons.
childrenReactNodeAn InputGroupInput (or InputGroupTextarea) plus one or more InputGroupAddon.
InputGroupInput
The text input control, styled borderless to sit flush inside the group. Forwards all Input props.
typestringNative input type, e.g. text, email, or number.
Default
"text"
InputGroupAddon
align"inline-start" | "inline-end" | "block-start" | …Where the addon sits relative to the input.
InputGroupText
A static, non-interactive label inside an addon, e.g. a unit or protocol prefix.
childrenReactNodeThe label text or icon.
InputGroupButton
An interactive button inside an addon.
size"xs" | "sm" | "icon-xs" | "icon-sm"Compact sizes scaled to fit inside an addon.
Default
"xs"variantButtonProps['variant']Any Button variant; defaults to ghost to stay visually quiet inside the group.
Default
"ghost"childrenReactNodeThe button's label or icon.
Accessibility
Built to be keyboard-navigable and screen-reader friendly out of the box.
- The whole group shows one focus ring, but the input is the focusable element.
- Label the input with a Label or aria-label; addons are not the field’s name.
- Give icon-only addon buttons an aria-label.
Guidelines
Conventions that keep usage consistent across an app.
Do
- Use for units, prefixes, and inline actions tied to a field.
- Keep addons short and scannable.
Don’t
- Don’t overload a field with many addons.
- Don’t hide the field’s real label.
Explore further
Ready to integrate?
Follow the Getting Started guide to install @codefast/ui, or browse the full component gallery.