Add addons, buttons, and helper content to inputs.
1import { InputGroup, InputGroupAddon, InputGroupInput } from "@/components/ui/input-group";2import { Search } from "lucide-react";34export default function Preview() {Installation
pnpm dlx sprawlify@latest add input-groupnpx sprawlify@latest add input-groupyarn sprawlify@latest add input-groupbunx --bun sprawlify@latest add input-group
Install the following dependencies:
pnpm add @sprawlify/primitives @sprawlify/reactnpm install @sprawlify/primitives @sprawlify/reactyarn add @sprawlify/primitives @sprawlify/reactbun add @sprawlify/primitives @sprawlify/react
Add the following files to your project:
1"use client";23import * as React from "react";4import { cva, type VariantProps } from "class-variance-authority";5import { cn } from "@/lib/utils";6import { Button } from "@/components/ui/button";7import { Input } from "@/components/ui/input";8import { Textarea } from "@/components/ui/textarea";9import { sprawlify } from "@sprawlify/react";1011function InputGroup({ className, ...props }: React.ComponentProps<typeof sprawlify.div>) {12 return (13 <sprawlify.div14 data-scope="input-group"15 data-part="root"16 data-slot="input-group"17 role="group"18 className={cn(19 "border-input bg-input/30 dark:bg-input/30 has-[[data-slot=input-group-control]:focus-visible]:border-ring has-[[data-slot=input-group-control]:focus-visible]:ring-ring/50 has-[[data-slot][aria-invalid=true]]:ring-destructive/20 has-[[data-slot][aria-invalid=true]]:border-destructive dark:has-[[data-slot][aria-invalid=true]]:ring-destructive/40 has-disabled:bg-input/50 dark:has-disabled:bg-input/80 h-8 rounded-lg border transition-colors in-data-[slot=combobox-content]:focus-within:border-inherit in-data-[slot=combobox-content]:focus-within:ring-0 has-disabled:opacity-50 has-[[data-slot=input-group-control]:focus-visible]:ring-3 has-[[data-slot][aria-invalid=true]]:ring-3 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-[>[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 group/input-group relative flex w-full min-w-0 items-center outline-none has-[>textarea]:h-auto",20 className,21 )}22 {...props}23 />24 );25}2627const inputGroupAddonVariants = cva(28 "text-muted-foreground h-auto gap-2 py-1.5 text-sm font-medium group-data-[disabled=true]/input-group:opacity-50 [&>kbd]:rounded-[calc(var(--radius)-5px)] [&>svg:not([class*='size-'])]:size-4 flex cursor-text items-center justify-center select-none",29 {30 variants: {31 align: {32 "inline-start": "pl-2 has-[>button]:ml-[-0.3rem] has-[>kbd]:ml-[-0.15rem] order-first",33 "inline-end": "pr-2 has-[>button]:mr-[-0.3rem] has-[>kbd]:mr-[-0.15rem] order-last",34 "block-start":35 "px-2.5 pt-2 group-has-[>input]/input-group:pt-2 [.border-b]:pb-2 order-first w-full justify-start",36 "block-end":37 "px-2.5 pb-2 group-has-[>input]/input-group:pb-2 [.border-t]:pt-2 order-last w-full justify-start",38 },39 },40 defaultVariants: {41 align: "inline-start",42 },43 },44);4546function InputGroupAddon({47 className,48 align = "inline-start",49 ...props50}: React.ComponentProps<typeof sprawlify.div> & VariantProps<typeof inputGroupAddonVariants>) {51 return (52 <sprawlify.div53 role="group"54 data-scope="input-group"55 data-part="addon"56 data-slot="input-group-addon"57 data-align={align}58 className={cn(inputGroupAddonVariants({ align }), className)}59 onClick={(e) => {60 if ((e.target as HTMLElement).closest("button")) {61 return;62 }63 e.currentTarget.parentElement?.querySelector("input")?.focus();64 }}65 {...props}66 />67 );68}6970const inputGroupButtonVariants = cva("gap-2 text-sm shadow-none flex items-center", {71 variants: {72 size: {73 xs: "h-6 gap-1 rounded-[calc(var(--radius)-3px)] px-1.5 [&>svg:not([class*='size-'])]:size-3.5",74 sm: "",75 "icon-xs": "size-6 rounded-[calc(var(--radius)-3px)] p-0 has-[>svg]:p-0",76 "icon-sm": "size-8 p-0 has-[>svg]:p-0",77 },78 },79 defaultVariants: {80 size: "xs",81 },82});8384function InputGroupButton({85 className,86 type = "button",87 variant = "ghost",88 size = "xs",89 ...props90}: Omit<React.ComponentProps<typeof Button>, "size"> &91 VariantProps<typeof inputGroupButtonVariants>) {92 return (93 <Button94 data-scope="input-group"95 data-part="button"96 type={type}97 data-size={size}98 variant={variant}99 className={cn(inputGroupButtonVariants({ size }), className)}100 {...props}101 />102 );103}104105function InputGroupText({ className, ...props }: React.ComponentProps<typeof sprawlify.span>) {106 return (107 <sprawlify.span108 data-scope="input-group"109 data-part="text"110 data-slot="input-group-text"111 className={cn(112 "text-muted-foreground gap-2 text-sm [&_svg:not([class*='size-'])]:size-4 flex items-center [&_svg]:pointer-events-none",113 className,114 )}115 {...props}116 />117 );118}119120function InputGroupInput({ className, ...props }: React.ComponentProps<typeof sprawlify.input>) {121 return (122 <Input123 data-scope="input-group"124 data-part="control"125 data-slot="input-group-control"126 className={cn(127 "rounded-none border-0 bg-transparent shadow-none ring-0 focus-visible:ring-0 disabled:bg-input/50 aria-invalid:ring-0 dark:bg-input/30 dark:disabled:bg-input/50 flex-1",128 className,129 )}130 {...props}131 />132 );133}134135function InputGroupTextarea({136 className,137 ...props138}: React.ComponentProps<typeof sprawlify.textarea>) {139 return (140 <Textarea141 data-scope="input-group"142 data-part="control"143 data-slot="input-group-control"144 className={cn(145 "rounded-none border-0 bg-transparent py-2 shadow-none ring-0 focus-visible:ring-0 disabled:bg-input/50 aria-invalid:ring-0 dark:bg-input/30 dark:disabled:bg-transparent flex-1 resize-none",146 className,147 )}148 {...props}149 />150 );151}152153export {154 InputGroup,155 InputGroupAddon,156 InputGroupButton,157 InputGroupText,158 InputGroupInput,159 InputGroupTextarea,160};161Update the import paths to match your project setup.
Usage
1import {
2 InputGroup,
3 InputGroupAddon,
4 InputGroupButton,
5 InputGroupText,
6 InputGroupInput,
7 InputGroupTextarea
8} from "@/components/ui/input-group"1<InputGroup>
2 <InputGroupInput placeholder="Search..." />
3 <InputGroupAddon>
4 <SearchIcon />
5 </InputGroupAddon>
6</InputGroup>Examples
Block End
Use align="block-end" to position the addon below the input.
1import { Field, FieldDescription, FieldGroup, FieldLabel } from "@/components/ui/field";2import {3 InputGroup,4 InputGroupAddon,Block Start
Use align="block-start" to position the addon above the input.
1import { Field, FieldDescription, FieldGroup, FieldLabel } from "@/components/ui/field";2import {3 InputGroup,4 InputGroupAddon,Dropdown
1import {2 DropdownMenu,3 DropdownMenuContent,4 DropdownMenuGroup,Icon
1import { InputGroup, InputGroupAddon, InputGroupInput } from "@/components/ui/input-group";2import { CheckIcon, CreditCardIcon, InfoIcon, MailIcon, SearchIcon, StarIcon } from "lucide-react";34export default function Preview() {Inline End
Use align="inline-end" to position the addon at the end of the input.
1import { Field, FieldDescription, FieldLabel } from "@/components/ui/field";2import { InputGroup, InputGroupAddon, InputGroupInput } from "@/components/ui/input-group";3import { EyeOffIcon } from "lucide-react";4Inline Start
Use align="inline-start" to position the addon at the start of the input. This is the default.
1import { Field, FieldDescription, FieldLabel } from "@/components/ui/field";2import { InputGroup, InputGroupAddon, InputGroupInput } from "@/components/ui/input-group";3import { SearchIcon } from "lucide-react";4Kbd
1import { InputGroup, InputGroupAddon, InputGroupInput } from "@/components/ui/input-group";2import { Kbd } from "@/components/ui/kbd";3import { SearchIcon } from "lucide-react";4Text
1import {2 InputGroup,3 InputGroupAddon,4 InputGroupInput,Textarea
1import {2 InputGroup,3 InputGroupAddon,4 InputGroupButton,On This Page
InstallationUsageExamplesBlock EndBlock StartDropdownIconInline EndInline StartKbdTextTextareaGet PRO
Need premium blocks and templates? Upgrade to PRO and get access.