InputGroup
Add addons, buttons, and helper content to inputs.
Code
import { InputGroup, InputGroupAddon, InputGroupInput, SearchIcon,} from "@falcon/ui-kit";
export function Example() { return ( <InputGroup className="tw:max-w-xs"> <InputGroupInput aria-label="Search products" placeholder="Search..." /> <InputGroupAddon> <SearchIcon /> </InputGroupAddon> <InputGroupAddon align="inline-end">12 results</InputGroupAddon> </InputGroup> );}Composition
Section titled “Composition”Use the following composition to build an InputGroup:
InputGroup├── InputGroupInput or InputGroupTextarea├── InputGroupAddon├── InputGroupButton└── InputGroupTextUse the align prop on InputGroupAddon to position the addon relative to the
input.
For proper focus management, InputGroupAddon should always be placed after
InputGroupInput or InputGroupTextarea in the DOM. Use the align prop to
visually position the addon.
inline-start
Section titled “inline-start”Use align="inline-start" to position the addon at the start of the input. This
is the default.
Code
import { Field, FieldDescription, FieldLabel, InputGroup, InputGroupAddon, InputGroupInput, SearchIcon,} from "@falcon/ui-kit";
export function Example() { return ( <Field className="tw:max-w-sm"> <FieldLabel htmlFor="inline-start-input">Product</FieldLabel> <InputGroup> <InputGroupInput id="inline-start-input" placeholder="Search..." /> <InputGroupAddon align="inline-start"> <SearchIcon className="tw:text-muted-foreground" /> </InputGroupAddon> </InputGroup> <FieldDescription>Icon positioned at the start.</FieldDescription> </Field> );}inline-end
Section titled “inline-end”Use align="inline-end" to position the addon at the end of the input.
Code
import { EyeOffIcon, Field, FieldDescription, FieldLabel, InputGroup, InputGroupAddon, InputGroupInput,} from "@falcon/ui-kit";
export function Example() { return ( <Field className="tw:max-w-sm"> <FieldLabel htmlFor="inline-end-input">Account code</FieldLabel> <InputGroup> <InputGroupInput id="inline-end-input" type="password" placeholder="Enter account code" /> <InputGroupAddon align="inline-end"> <EyeOffIcon /> </InputGroupAddon> </InputGroup> <FieldDescription>Icon positioned at the end.</FieldDescription> </Field> );}block-start
Section titled “block-start”Use align="block-start" to position the addon above the input.
Code
import { CopyIcon, Field, FieldDescription, FieldGroup, FieldLabel, FileTextIcon, InputGroup, InputGroupAddon, InputGroupButton, InputGroupInput, InputGroupText, InputGroupTextarea,} from "@falcon/ui-kit";
export function Example() { return ( <FieldGroup className="tw:max-w-sm"> <Field> <FieldLabel htmlFor="block-start-input">Input</FieldLabel> <InputGroup className="tw:h-auto"> <InputGroupInput id="block-start-input" placeholder="Enter customer name" /> <InputGroupAddon align="block-start"> <InputGroupText>Customer 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="Leave delivery instructions" className="tw:text-sm" /> <InputGroupAddon align="block-start"> <FileTextIcon className="tw:text-muted-foreground" /> <InputGroupText>Delivery instructions</InputGroupText> <InputGroupButton aria-label="Copy delivery instructions" size="icon-xs" className="tw:ml-auto" > <CopyIcon /> <span className="tw:sr-only">Copy</span> </InputGroupButton> </InputGroupAddon> </InputGroup> <FieldDescription> Header positioned above the textarea. </FieldDescription> </Field> </FieldGroup> );}block-end
Section titled “block-end”Use align="block-end" to position the addon below the input.
Code
import { Field, FieldDescription, FieldGroup, FieldLabel, InputGroup, InputGroupAddon, InputGroupButton, InputGroupInput, InputGroupText, InputGroupTextarea,} from "@falcon/ui-kit";
export function Example() { return ( <FieldGroup className="tw:max-w-sm"> <Field> <FieldLabel htmlFor="block-end-input">Input</FieldLabel> <InputGroup className="tw: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 an order note..." /> <InputGroupAddon align="block-end"> <InputGroupText>0/280</InputGroupText> <InputGroupButton variant="default" size="sm" className="tw:ml-auto" > Post </InputGroupButton> </InputGroupAddon> </InputGroup> <FieldDescription> Footer positioned below the textarea. </FieldDescription> </Field> </FieldGroup> );}Code
import { CheckIcon, CreditCardIcon, InfoIcon, InputGroup, InputGroupAddon, InputGroupInput, MailIcon, SearchIcon, StarIcon,} from "@falcon/ui-kit";
export function Example() { return ( <div className="tw:grid tw:w-full tw:max-w-sm tw:gap-6"> <InputGroup> <InputGroupInput aria-label="Search products" placeholder="Search..." /> <InputGroupAddon> <SearchIcon /> </InputGroupAddon> </InputGroup> <InputGroup> <InputGroupInput aria-label="Contact email" type="email" placeholder="Enter contact email" /> <InputGroupAddon> <MailIcon /> </InputGroupAddon> </InputGroup> <InputGroup> <InputGroupInput aria-label="Card number" placeholder="Card number" /> <InputGroupAddon> <CreditCardIcon /> </InputGroupAddon> <InputGroupAddon align="inline-end"> <CheckIcon /> </InputGroupAddon> </InputGroup> <InputGroup> <InputGroupInput aria-label="Account number" placeholder="Account number" /> <InputGroupAddon align="inline-end"> <StarIcon /> <InfoIcon /> </InputGroupAddon> </InputGroup> </div> );}Code
import { InputGroup, InputGroupAddon, InputGroupInput, InputGroupText, InputGroupTextarea,} from "@falcon/ui-kit";
export function Example() { return ( <div className="tw:grid tw:w-full tw:max-w-sm tw:gap-6"> <InputGroup> <InputGroupInput aria-label="Amount" placeholder="0.00" /> <InputGroupAddon> <InputGroupText>$</InputGroupText> </InputGroupAddon> <InputGroupAddon align="inline-end"> <InputGroupText>USD</InputGroupText> </InputGroupAddon> </InputGroup> <InputGroup> <InputGroupInput aria-label="Customer portal" placeholder="customer-portal" className="tw:pl-0.5!" /> <InputGroupAddon> <InputGroupText>https://</InputGroupText> </InputGroupAddon> <InputGroupAddon align="inline-end"> <InputGroupText>.com</InputGroupText> </InputGroupAddon> </InputGroup> <InputGroup> <InputGroupInput aria-label="Username" placeholder="Enter username" /> <InputGroupAddon align="inline-end"> <InputGroupText>@retailer.com</InputGroupText> </InputGroupAddon> </InputGroup> <InputGroup> <InputGroupTextarea aria-label="Message" placeholder="Enter message" /> <InputGroupAddon align="block-end"> <InputGroupText className="tw:text-xs tw:text-muted-foreground"> 120 characters left </InputGroupText> </InputGroupAddon> </InputGroup> </div> );}Button
Section titled “Button”Code
import { useState } from "react";import { CheckIcon, CopyIcon, InfoIcon, InputGroup, InputGroupAddon, InputGroupButton, InputGroupInput, Popover, PopoverContent, PopoverDescription, PopoverTitle, PopoverTrigger, StarIcon,} from "@falcon/ui-kit";
export function Example() { const [isCopied, setIsCopied] = useState(false); const [isFavorite, setIsFavorite] = useState(false);
return ( <div className="tw:grid tw:w-full tw:max-w-sm tw:gap-6"> <InputGroup> <InputGroupInput aria-label="Customer portal URL" placeholder="https://portal.example.com" readOnly /> <InputGroupAddon align="inline-end"> <InputGroupButton aria-label="Copy" title="Copy" size="icon-xs" onClick={() => { void navigator.clipboard.writeText("https://portal.example.com"); setIsCopied(true); }} > {isCopied ? <CheckIcon /> : <CopyIcon />} </InputGroupButton> </InputGroupAddon> </InputGroup> <InputGroup className="tw:[--radius:9999px]"> <InputGroupInput id="customer-portal-address" aria-label="Portal address" /> <InputGroupAddon> <Popover> <PopoverTrigger render={ <InputGroupButton aria-label="Connection information" variant="secondary" size="icon-xs" /> } aria-label="Connection information" > <InfoIcon /> </PopoverTrigger> <PopoverContent align="start" className="tw:flex tw:flex-col tw:gap-1 tw:rounded-xl tw:text-sm" > <PopoverTitle>Connection information</PopoverTitle> <PopoverDescription> Confirm the customer portal address before sharing account data. </PopoverDescription> </PopoverContent> </Popover> </InputGroupAddon> <InputGroupAddon className="tw:pl-1.5 tw:text-muted-foreground"> https:// </InputGroupAddon> <InputGroupAddon align="inline-end"> <InputGroupButton aria-label={isFavorite ? "Remove favorite" : "Add favorite"} aria-pressed={isFavorite} onClick={() => setIsFavorite(!isFavorite)} size="icon-xs" > <StarIcon data-favorite={isFavorite} className="tw:data-[favorite=true]:fill-primary tw:data-[favorite=true]:stroke-primary" /> </InputGroupButton> </InputGroupAddon> </InputGroup> <InputGroup> <InputGroupInput aria-label="Search products" placeholder="Type to search..." /> <InputGroupAddon align="inline-end"> <InputGroupButton variant="secondary">Search</InputGroupButton> </InputGroupAddon> </InputGroup> </div> );}Code
import { InputGroup, InputGroupAddon, InputGroupInput, SearchIcon,} from "@falcon/ui-kit";
export function Example() { return ( <InputGroup className="tw:max-w-sm"> <InputGroupInput aria-label="Search products" placeholder="Search..." /> <InputGroupAddon> <SearchIcon className="tw:text-muted-foreground" /> </InputGroupAddon> <InputGroupAddon align="inline-end"> <kbd className="tw:rounded-sm tw:border tw:border-border tw:bg-muted tw:px-1.5 tw:py-0.5 tw:text-xs tw:text-muted-foreground"> Ctrl K </kbd> </InputGroupAddon> </InputGroup> );}Dropdown
Section titled “Dropdown”Code
import { ChevronDownIcon, DropdownMenu, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuTrigger, EllipsisIcon, InputGroup, InputGroupAddon, InputGroupButton, InputGroupInput,} from "@falcon/ui-kit";
export function Example() { return ( <div className="tw:grid tw:w-full tw:max-w-sm tw:gap-4"> <InputGroup> <InputGroupInput aria-label="Document name" placeholder="Enter document name" /> <InputGroupAddon align="inline-end"> <DropdownMenu> <DropdownMenuTrigger render={ <InputGroupButton variant="ghost" aria-label="More document actions" size="icon-xs" /> } > <EllipsisIcon /> </DropdownMenuTrigger> <DropdownMenuContent align="end" sideOffset={8} alignOffset={-4}> <DropdownMenuGroup> <DropdownMenuItem>Settings</DropdownMenuItem> <DropdownMenuItem>Copy reference</DropdownMenuItem> <DropdownMenuItem>Open location</DropdownMenuItem> </DropdownMenuGroup> </DropdownMenuContent> </DropdownMenu> </InputGroupAddon> </InputGroup> <InputGroup> <InputGroupInput aria-label="Search query" placeholder="Enter search query" /> <InputGroupAddon align="inline-end"> <DropdownMenu> <DropdownMenuTrigger render={ <InputGroupButton variant="ghost" className="tw:pr-1.5! tw:text-xs" /> } > Search in... <ChevronDownIcon className="tw:size-3" /> </DropdownMenuTrigger> <DropdownMenuContent align="end" sideOffset={8} alignOffset={-4}> <DropdownMenuGroup> <DropdownMenuItem>Products</DropdownMenuItem> <DropdownMenuItem>Customers</DropdownMenuItem> <DropdownMenuItem>Orders</DropdownMenuItem> </DropdownMenuGroup> </DropdownMenuContent> </DropdownMenu> </InputGroupAddon> </InputGroup> </div> );}Spinner
Section titled “Spinner”Code
import { InputGroup, InputGroupAddon, InputGroupInput, InputGroupText, LoaderIcon, Spinner,} from "@falcon/ui-kit";
export function Example() { return ( <div className="tw:grid tw:w-full tw:max-w-sm tw:gap-4"> <InputGroup> <InputGroupInput aria-label="Product search" placeholder="Searching..." /> <InputGroupAddon align="inline-end"> <Spinner aria-label="Searching products" /> </InputGroupAddon> </InputGroup> <InputGroup> <InputGroupInput aria-label="Order import" placeholder="Processing..." /> <InputGroupAddon> <Spinner aria-label="Processing order import" /> </InputGroupAddon> </InputGroup> <InputGroup> <InputGroupInput aria-label="Order changes" placeholder="Saving changes..." /> <InputGroupAddon align="inline-end"> <InputGroupText>Saving...</InputGroupText> <Spinner aria-hidden="true" /> </InputGroupAddon> </InputGroup> <InputGroup> <InputGroupInput aria-label="Inventory data" placeholder="Refreshing data..." /> <InputGroupAddon> <LoaderIcon className="tw:animate-spin" aria-hidden="true" /> </InputGroupAddon> <InputGroupAddon align="inline-end"> <InputGroupText className="tw:text-muted-foreground"> Please wait... </InputGroupText> </InputGroupAddon> </InputGroup> </div> );}Textarea
Section titled “Textarea”Code
import { ClipboardIcon, CornerDownLeftIcon, FileTextIcon, InputGroup, InputGroupAddon, InputGroupButton, InputGroupText, InputGroupTextarea, RefreshCwIcon,} from "@falcon/ui-kit";
export function Example() { return ( <div className="tw:grid tw:w-full tw:max-w-md tw:gap-4"> <InputGroup> <InputGroupTextarea id="delivery-instructions" aria-label="Delivery instructions" placeholder="Enter delivery instructions" className="tw:min-h-[200px]" /> <InputGroupAddon align="block-end" className="tw:border-t"> <InputGroupText>Line 1, column 1</InputGroupText> <InputGroupButton size="sm" className="tw:ml-auto" variant="default"> Save <CornerDownLeftIcon /> </InputGroupButton> </InputGroupAddon> <InputGroupAddon align="block-start" className="tw:border-b"> <InputGroupText className="tw:font-medium"> <FileTextIcon /> Delivery instructions </InputGroupText> <InputGroupButton aria-label="Reset delivery instructions" className="tw:ml-auto" size="icon-xs" > <RefreshCwIcon /> </InputGroupButton> <InputGroupButton aria-label="Copy delivery instructions" variant="ghost" size="icon-xs" > <ClipboardIcon /> </InputGroupButton> </InputGroupAddon> </InputGroup> </div> );}Custom Input
Section titled “Custom Input”Add the data-slot="input-group-control" attribute to your custom input for
automatic focus state handling.
Here’s an example of a custom auto-resizing textarea using native CSS.
Code
import { InputGroup, InputGroupAddon, InputGroupButton } from "@falcon/ui-kit";
export function Example() { return ( <div className="tw:grid tw:w-full tw:max-w-sm tw:gap-6"> <InputGroup> <textarea data-slot="input-group-control" aria-label="Order note" className="tw:flex tw:field-sizing-content tw:min-h-16 tw:w-full tw:resize-none tw:rounded-md tw:bg-transparent tw:px-3 tw:py-2.5 tw:text-base tw:transition-[color,box-shadow] tw:outline-none tw:md:text-sm" placeholder="Enter an order note..." /> <InputGroupAddon align="block-end"> <InputGroupButton className="tw:ml-auto" size="sm" variant="default"> Submit </InputGroupButton> </InputGroupAddon> </InputGroup> </div> );}InputGroup
Section titled “InputGroup”Groups a form control with contextual addons. Renders a <div> element with
role="group" and accepts standard <div> props.
InputGroupAddon
Section titled “InputGroupAddon”Contains contextual content beside a form control. Renders a <div> element
with role="group", accepts standard <div> props, and supports align.
align?: "inline-end" | "inline-start" | "block-start" | "block-end" | null = "inline-start"Controls the addon’s position relative to the form control. Defaults to
"inline-start".
InputGroupButton
Section titled “InputGroupButton”A Button for use in an InputGroup addon. Renders a <button> element by
default and accepts standard <button> props. Use render to replace the
element. type defaults to "button", variant to "ghost", and size to
"xs".
className
Section titled “className”className?: stringCSS class applied to the button.
focusableWhenDisabled
Section titled “focusableWhenDisabled”focusableWhenDisabled?: booleanWhether a disabled button stays focusable. Defaults to false.
nativeButton
Section titled “nativeButton”nativeButton?: booleanWhether the element passed to render is a native button. Defaults to
true.
render
Section titled “render”render?: React.ReactElement<any, string | React.JSXElementConstructor<any>> | ComponentRenderFn<HTMLProps, ButtonState>Allows replacing the button element or composing it with another component.
size?: "xs" | "sm" | "icon-xs" | "icon-sm" | null = "xs"Controls the button’s size. The icon sizes are square and require an
accessible name. Defaults to "xs".
style?: React.CSSProperties | ((state: ButtonState) => React.CSSProperties | undefined)Style applied to the button, or a function based on its state.
type?: "button" | "submit" | "reset" = "button"The button type. Defaults to "button".
variant
Section titled “variant”variant?: "outline" | "link" | "secondary" | "default" | "ghost" | "destructive" | null = "ghost"Controls the button’s appearance only. Defaults to "ghost".
InputGroupInput
Section titled “InputGroupInput”An Input for use in an InputGroup. Renders an <input> element, accepts
standard <input> props, and supports render to replace the element.
className
Section titled “className”className?: stringCSS class applied to the input.
defaultValue
Section titled “defaultValue”defaultValue?: string | number | readonly string[]The initial value of an uncontrolled input.
onValueChange
Section titled “onValueChange”onValueChange?: ((value: string, eventDetails: Input.ChangeEventDetails) => void)Callback fired when the value changes. Use with value for a controlled
input.
render
Section titled “render”render?: React.ReactElement<any, string | React.JSXElementConstructor<any>> | ComponentRenderFn<HTMLProps, InputState>Allows replacing the input element or composing it with another component.
style?: React.CSSProperties | ((state: InputState) => React.CSSProperties | undefined)Style applied to the input, or a function based on its state.
value?: string | number | readonly string[]The value of a controlled input.
InputGroupText
Section titled “InputGroupText”Provides inline text in an InputGroup addon. Renders a <span> element and
accepts standard <span> props.
InputGroupTextarea
Section titled “InputGroupTextarea”A Textarea for use in an InputGroup. Renders a <textarea> element and
accepts standard <textarea> props.