Components
Input Group
Add addons, buttons, and helper content to inputs.
Installation#
Install the dependencies
npm install class-variance-authority cnCopy the source into your project
components/ui/input-group.tsx"use client"import * as React from "react"import { cva, type VariantProps } from "class-variance-authority"import { cn } from "cn"import { Button } from "@/components/ui/button"import { Input } from "@/components/ui/input"import { Textarea } from "@/components/ui/textarea"function InputGroup({ className, ...props }: React.ComponentProps<"div">) { return ( <div data-slot="input-group" role="group" className={cn( "group/input-group relative flex h-10 w-full min-w-0 items-center rounded-lg border border-input bg-background shadow-xs transition-colors outline-none in-data-[slot=combobox-content]:focus-within:border-inherit in-data-[slot=combobox-content]:focus-within:ring-0 has-disabled:bg-input/50 has-disabled:opacity-50 has-[[data-slot=input-group-control]:focus-visible]:border-ring has-[[data-slot=input-group-control]:focus-visible]:ring-3 has-[[data-slot=input-group-control]:focus-visible]:ring-ring/50 has-[[data-slot][aria-invalid=true]]:border-destructive has-[[data-slot][aria-invalid=true]]:ring-3 has-[[data-slot][aria-invalid=true]]:ring-destructive/20 has-[>[data-align=block-end]]:h-auto has-[>[data-align=block-end]]:flex-col has-[>[data-align=block-start]]:h-auto has-[>[data-align=block-start]]:flex-col has-[>textarea]:h-auto dark:bg-input/30 dark:has-disabled:bg-input/80 dark:has-[[data-slot][aria-invalid=true]]:ring-destructive/40 has-[>[data-align=block-end]]:[&>input]:pt-3 has-[>[data-align=block-start]]:[&>input]:pb-3 has-[>[data-align=inline-end]]:[&>input]:pr-1.5 has-[>[data-align=inline-start]]:[&>input]:pl-1.5", className )} {...props} /> )}const inputGroupAddonVariants = cva( "flex h-auto cursor-text items-center justify-center gap-2 py-1.5 text-sm font-medium text-muted-foreground select-none group-data-[disabled=true]/input-group:opacity-50 [&>kbd]:rounded-[calc(var(--radius)-5px)] [&>svg:not([class*='size-'])]:size-4", { variants: { align: { "inline-start": "order-first pl-2 has-[>button]:ml-[-0.3rem] has-[>kbd]:ml-[-0.15rem]", "inline-end": "order-last pr-2 has-[>button]:mr-[-0.3rem] has-[>kbd]:mr-[-0.15rem]", "block-start": "order-first w-full justify-start px-2.5 pt-2 group-has-[>input]/input-group:pt-2 [.border-b]:pb-2", "block-end": "order-last w-full justify-start px-2.5 pb-2 group-has-[>input]/input-group:pb-2 [.border-t]:pt-2", }, }, defaultVariants: { align: "inline-start", }, })function InputGroupAddon({ className, align = "inline-start", ...props}: React.ComponentProps<"div"> & VariantProps<typeof inputGroupAddonVariants>) { return ( <div role="group" data-slot="input-group-addon" data-align={align} className={cn(inputGroupAddonVariants({ align }), className)} onClick={(e) => { if ((e.target as HTMLElement).closest("button")) { return } e.currentTarget.parentElement?.querySelector("input")?.focus() }} {...props} /> )}const inputGroupButtonVariants = cva( "flex items-center gap-2 text-sm shadow-none", { variants: { size: { xs: "h-6 gap-1 rounded-[calc(var(--radius)-3px)] px-1.5 [&>svg:not([class*='size-'])]:size-3.5", sm: "", "icon-xs": "size-6 rounded-[calc(var(--radius)-3px)] p-0 has-[>svg]:p-0", "icon-sm": "size-8 p-0 has-[>svg]:p-0", }, }, defaultVariants: { size: "xs", }, })function InputGroupButton({ className, type = "button", variant = "ghost", size = "xs", ...props}: Omit<React.ComponentProps<typeof Button>, "size"> & VariantProps<typeof inputGroupButtonVariants>) { return ( <Button type={type} data-size={size} variant={variant} className={cn(inputGroupButtonVariants({ size }), className)} {...props} /> )}function InputGroupText({ className, ...props }: React.ComponentProps<"span">) { return ( <span className={cn( "flex items-center gap-2 text-sm text-muted-foreground [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4", className )} {...props} /> )}function InputGroupInput({ className, ...props}: React.ComponentProps<"input">) { return ( <Input data-slot="input-group-control" className={cn( "flex-1 rounded-none border-0 bg-transparent shadow-none ring-0 focus-visible:ring-0 disabled:bg-transparent aria-invalid:ring-0 dark:bg-transparent dark:disabled:bg-transparent", className )} {...props} /> )}function InputGroupTextarea({ className, ...props}: React.ComponentProps<"textarea">) { return ( <Textarea data-slot="input-group-control" className={cn( "flex-1 resize-none rounded-none border-0 bg-transparent py-2 shadow-none ring-0 focus-visible:ring-0 disabled:bg-transparent aria-invalid:ring-0 dark:bg-transparent dark:disabled:bg-transparent", className )} {...props} /> )}export { InputGroup, InputGroupAddon, InputGroupButton, InputGroupText, InputGroupInput, InputGroupTextarea,}Make sure the foundation is in place
Components read their colors, radius and surfaces from the tokens in
app/globals.css. See Theming.
Usage#
import { InputGroup, InputGroupAddon, InputGroupButton, InputGroupText, InputGroupInput, InputGroupTextarea,} from "@/components/ui/input-group"Examples#
Basic
Block End
Footer positioned below the input.
Footer positioned below the textarea.
Block Start
Header positioned above the input.
Header positioned above the textarea.
Button
Button Group
Custom
Dropdown
Icon
In Card
Inline End
Icon positioned at the end.
Inline Start
Icon positioned at the start.
Kbd
Label
Spinner
Text
Textarea
Textarea Examples
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.
Tooltip
With Addons
This is a description of the input group.
With Buttons
With Kbd
This username is available.
This is a description of the input group.
With Tooltip
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.