package examples import (package examples import ( "github.com/axadrn/shadcn-templ/v2/components/icon" "github.com/axadrn/shadcn-templ/v2/components/inputgroup") templ InputGroupDemo() { @inputgroup.InputGroup(inputgroup.Props{Class: "max-w-xs"}) { @inputgroup.Input(inputgroup.InputProps{Placeholder: "Search..."}) @inputgroup.Addon() { @icon.Search() } @inputgroup.Addon(inputgroup.AddonProps{Align: inputgroup.AlignInlineEnd}) { 12 results } }}Installation
Usage
Composition
Use the following composition to build an InputGroup:
Align
Use the Align prop on inputgroup.Addon to position the addon relative to the input.
For proper focus management, inputgroup.Addon should always be placed after inputgroup.Input or inputgroup.Textarea in the DOM. Use the Align prop to visually position the addon.
inline-start
Use Align: inputgroup.AlignInlineStart to position the addon at the start of the input. This is the default.
Icon positioned at the start.
package examples import (package examples import ( "github.com/axadrn/shadcn-templ/v2/components/field" "github.com/axadrn/shadcn-templ/v2/components/icon" "github.com/axadrn/shadcn-templ/v2/components/inputgroup") templ InputGroupInlineStart() { @field.Field(field.Props{Class: "max-w-sm"}) { @field.Label(field.LabelProps{For: "inline-start-input"}) { Input } @inputgroup.InputGroup() { @inputgroup.Input(inputgroup.InputProps{ID: "inline-start-input", Placeholder: "Search..."}) @inputgroup.Addon(inputgroup.AddonProps{Align: inputgroup.AlignInlineStart}) { @icon.Search(icon.Props{Class: "text-muted-foreground"}) } } @field.Description() { Icon positioned at the start. } }}inline-end
Use Align: inputgroup.AlignInlineEnd to position the addon at the end of the input.
Icon positioned at the end.
package examples import (package examples import ( "github.com/axadrn/shadcn-templ/v2/components/field" "github.com/axadrn/shadcn-templ/v2/components/icon" "github.com/axadrn/shadcn-templ/v2/components/inputgroup") templ InputGroupInlineEnd() { @field.Field(field.Props{Class: "max-w-sm"}) { @field.Label(field.LabelProps{For: "inline-end-input"}) { Input } @inputgroup.InputGroup() { @inputgroup.Input(inputgroup.InputProps{ID: "inline-end-input", Type: "password", Placeholder: "Enter password"}) @inputgroup.Addon(inputgroup.AddonProps{Align: inputgroup.AlignInlineEnd}) { @icon.EyeOff() } } @field.Description() { Icon positioned at the end. } }}block-start
Use Align: inputgroup.AlignBlockStart to position the addon above the input.
Header positioned above the input.
Header positioned above the textarea.
package examples import (package examples import ( "github.com/axadrn/shadcn-templ/v2/components/field" "github.com/axadrn/shadcn-templ/v2/components/icon" "github.com/axadrn/shadcn-templ/v2/components/inputgroup") templ InputGroupBlockStart() { @field.Group(field.GroupProps{Class: "max-w-sm"}) { @field.Field() { @field.Label(field.LabelProps{For: "block-start-input"}) { Input } @inputgroup.InputGroup(inputgroup.Props{Class: "h-auto"}) { @inputgroup.Input(inputgroup.InputProps{ID: "block-start-input", Placeholder: "Enter your name"}) @inputgroup.Addon(inputgroup.AddonProps{Align: inputgroup.AlignBlockStart}) { @inputgroup.Text() { Full Name } } } @field.Description() { Header positioned above the input. } } @field.Field() { @field.Label(field.LabelProps{For: "block-start-textarea"}) { Textarea } @inputgroup.InputGroup() { @inputgroup.Textarea(inputgroup.TextareaProps{ID: "block-start-textarea", Placeholder: "console.log('Hello, world!');", Class: "font-mono text-sm"}) @inputgroup.Addon(inputgroup.AddonProps{Align: inputgroup.AlignBlockStart}) { @icon.FileCode(icon.Props{Class: "text-muted-foreground"}) @inputgroup.Text(inputgroup.TextProps{Class: "font-mono"}) { script.js } @inputgroup.Button(inputgroup.ButtonProps{Size: inputgroup.ButtonSizeIconXs, Class: "ml-auto"}) { @icon.Copy() <span class="sr-only">Copy</span> } } } @field.Description() { Header positioned above the textarea. } } }}block-end
Use Align: inputgroup.AlignBlockEnd to position the addon below the input.
Footer positioned below the input.
Footer positioned below the textarea.
package examples import (package examples import ( "github.com/axadrn/shadcn-templ/v2/components/button" "github.com/axadrn/shadcn-templ/v2/components/field" "github.com/axadrn/shadcn-templ/v2/components/inputgroup") templ InputGroupBlockEnd() { @field.Group(field.GroupProps{Class: "max-w-sm"}) { @field.Field() { @field.Label(field.LabelProps{For: "block-end-input"}) { Input } @inputgroup.InputGroup(inputgroup.Props{Class: "h-auto"}) { @inputgroup.Input(inputgroup.InputProps{ID: "block-end-input", Placeholder: "Enter amount"}) @inputgroup.Addon(inputgroup.AddonProps{Align: inputgroup.AlignBlockEnd}) { @inputgroup.Text() { USD } } } @field.Description() { Footer positioned below the input. } } @field.Field() { @field.Label(field.LabelProps{For: "block-end-textarea"}) { Textarea } @inputgroup.InputGroup() { @inputgroup.Textarea(inputgroup.TextareaProps{ID: "block-end-textarea", Placeholder: "Write a comment..."}) @inputgroup.Addon(inputgroup.AddonProps{Align: inputgroup.AlignBlockEnd}) { @inputgroup.Text() { 0/280 } @inputgroup.Button(inputgroup.ButtonProps{Variant: button.VariantDefault, Size: inputgroup.ButtonSizeSm, Class: "ml-auto"}) { Post } } } @field.Description() { Footer positioned below the textarea. } } }}Icon
package examples import (package examples import ( "github.com/axadrn/shadcn-templ/v2/components/icon" "github.com/axadrn/shadcn-templ/v2/components/inputgroup") templ InputGroupIcon() { <div class="grid w-full max-w-sm gap-6"> @inputgroup.InputGroup() { @inputgroup.Input(inputgroup.InputProps{Placeholder: "Search..."}) @inputgroup.Addon() { @icon.Search() } } @inputgroup.InputGroup() { @inputgroup.Input(inputgroup.InputProps{Type: "email", Placeholder: "Enter your email"}) @inputgroup.Addon() { @icon.Mail() } } @inputgroup.InputGroup() { @inputgroup.Input(inputgroup.InputProps{Placeholder: "Card number"}) @inputgroup.Addon() { @icon.CreditCard() } @inputgroup.Addon(inputgroup.AddonProps{Align: inputgroup.AlignInlineEnd}) { @icon.Check() } } @inputgroup.InputGroup() { @inputgroup.Input(inputgroup.InputProps{Placeholder: "Card number"}) @inputgroup.Addon(inputgroup.AddonProps{Align: inputgroup.AlignInlineEnd}) { @icon.Star() @icon.Info() } } </div>}Text
package examples import "github.com/axadrn/shadcn-templ/v2/components/inputgroup"package examples import "github.com/axadrn/shadcn-templ/v2/components/inputgroup" templ InputGroupText() { <div class="grid w-full max-w-sm gap-6"> @inputgroup.InputGroup() { @inputgroup.Addon() { @inputgroup.Text() { $ } } @inputgroup.Input(inputgroup.InputProps{Placeholder: "0.00"}) @inputgroup.Addon(inputgroup.AddonProps{Align: inputgroup.AlignInlineEnd}) { @inputgroup.Text() { USD } } } @inputgroup.InputGroup() { @inputgroup.Addon() { @inputgroup.Text() { https:// } } @inputgroup.Input(inputgroup.InputProps{Placeholder: "example.com", Class: "pl-0.5!"}) @inputgroup.Addon(inputgroup.AddonProps{Align: inputgroup.AlignInlineEnd}) { @inputgroup.Text() { .com } } } @inputgroup.InputGroup() { @inputgroup.Input(inputgroup.InputProps{Placeholder: "Enter your username"}) @inputgroup.Addon(inputgroup.AddonProps{Align: inputgroup.AlignInlineEnd}) { @inputgroup.Text() { { "@company.com" } } } } @inputgroup.InputGroup() { @inputgroup.Textarea(inputgroup.TextareaProps{Placeholder: "Enter your message"}) @inputgroup.Addon(inputgroup.AddonProps{Align: inputgroup.AlignBlockEnd}) { @inputgroup.Text(inputgroup.TextProps{Class: "text-xs text-muted-foreground"}) { 120 characters left } } } </div>}Button
package examples import (package examples import ( "github.com/axadrn/shadcn-templ/v2/components/icon" "github.com/axadrn/shadcn-templ/v2/components/inputgroup" "github.com/axadrn/shadcn-templ/v2/components/popover") templ InputGroupButton() { <div class="grid w-full max-w-sm gap-6" data-inputgroup-button-demo> @inputgroup.InputGroup() { @inputgroup.Input(inputgroup.InputProps{Value: "https://x.com/axadrn", Attributes: templ.Attributes{"readonly": true}}) @inputgroup.Addon(inputgroup.AddonProps{Align: inputgroup.AlignInlineEnd}) { @inputgroup.Button(inputgroup.ButtonProps{ Size: inputgroup.ButtonSizeIconXs, Attributes: templ.Attributes{"aria-label": "Copy", "title": "Copy", "data-copy-url": "https://x.com/axadrn"}, }) { <span class="[[data-copied=true]_&]:hidden"> @icon.Copy() </span> <span class="hidden [[data-copied=true]_&]:inline"> @icon.Check() </span> } } } @inputgroup.InputGroup(inputgroup.Props{Class: "[--radius:9999px]"}) { @popover.Popover() { @inputgroup.Addon(inputgroup.AddonProps{Attributes: popover.Trigger(ctx)}) { @inputgroup.Button(inputgroup.ButtonProps{ Variant: "secondary", Size: inputgroup.ButtonSizeIconXs, }) { @icon.Info() } } @popover.Content(popover.ContentProps{ Align: popover.AlignStart, Class: "flex flex-col gap-1 rounded-xl text-sm", }) { <p class="font-medium">Your connection is not secure.</p> <p>You should not enter any sensitive information on this site.</p> } } @inputgroup.Addon(inputgroup.AddonProps{Class: "pl-1.5 text-muted-foreground"}) { https:// } @inputgroup.Input(inputgroup.InputProps{ID: "input-secure-19"}) @inputgroup.Addon(inputgroup.AddonProps{Align: inputgroup.AlignInlineEnd}) { @inputgroup.Button(inputgroup.ButtonProps{ Size: inputgroup.ButtonSizeIconXs, Attributes: templ.Attributes{"data-favorite-toggle": true}, }) { @icon.Star(icon.Props{Class: "data-[favorite=true]:fill-blue-600 data-[favorite=true]:stroke-blue-600"}) } } } @inputgroup.InputGroup() { @inputgroup.Input(inputgroup.InputProps{Placeholder: "Type to search..."}) @inputgroup.Addon(inputgroup.AddonProps{Align: inputgroup.AlignInlineEnd}) { @inputgroup.Button(inputgroup.ButtonProps{Variant: "secondary"}) { Search } } } </div> <script nonce={ templ.GetNonce(ctx) }> // The vanilla pendant of the demo's copy and favorite state. document.addEventListener('click', (e) => { if (!(e.target instanceof Element)) return; const copy = e.target.closest('[data-copy-url]'); if (copy && copy.closest('[data-inputgroup-button-demo]')) { navigator.clipboard.writeText(copy.getAttribute('data-copy-url')).then(() => { copy.setAttribute('data-copied', 'true'); setTimeout(() => copy.setAttribute('data-copied', 'false'), 1500); }); } const fav = e.target.closest('[data-favorite-toggle]'); if (fav && fav.closest('[data-inputgroup-button-demo]')) { const star = fav.querySelector('svg'); star.setAttribute('data-favorite', star.getAttribute('data-favorite') === 'true' ? 'false' : 'true'); } }); </script>}Kbd
package examples import (package examples import ( "github.com/axadrn/shadcn-templ/v2/components/icon" "github.com/axadrn/shadcn-templ/v2/components/inputgroup" "github.com/axadrn/shadcn-templ/v2/components/kbd") templ InputGroupKbd() { @inputgroup.InputGroup(inputgroup.Props{Class: "max-w-sm"}) { @inputgroup.Input(inputgroup.InputProps{Placeholder: "Search..."}) @inputgroup.Addon() { @icon.Search(icon.Props{Class: "text-muted-foreground"}) } @inputgroup.Addon(inputgroup.AddonProps{Align: inputgroup.AlignInlineEnd}) { @kbd.Kbd() { ⌘K } } }}Dropdown
package examples import (package examples import ( "github.com/axadrn/shadcn-templ/v2/components/dropdownmenu" "github.com/axadrn/shadcn-templ/v2/components/icon" "github.com/axadrn/shadcn-templ/v2/components/inputgroup" "github.com/axadrn/shadcn-templ/v2/utils") templ InputGroupDropdown() { <div class="grid w-full max-w-sm gap-4"> @inputgroup.InputGroup() { @inputgroup.Input(inputgroup.InputProps{Placeholder: "Enter file name"}) @inputgroup.Addon(inputgroup.AddonProps{Align: inputgroup.AlignInlineEnd}) { @dropdownmenu.DropdownMenu() { @inputgroup.Button(inputgroup.ButtonProps{ Variant: "ghost", Size: inputgroup.ButtonSizeIconXs, Attributes: utils.MergeAttributes(dropdownmenu.Trigger(ctx), templ.Attributes{"aria-label": "More"}), }) { @icon.Ellipsis() } @dropdownmenu.Content(dropdownmenu.ContentProps{Align: dropdownmenu.AlignEnd, SideOffset: 8, AlignOffset: -4}) { @dropdownmenu.Group() { @dropdownmenu.Item() { Settings } @dropdownmenu.Item() { Copy path } @dropdownmenu.Item() { Open location } } } } } } @inputgroup.InputGroup() { @inputgroup.Input(inputgroup.InputProps{Placeholder: "Enter search query"}) @inputgroup.Addon(inputgroup.AddonProps{Align: inputgroup.AlignInlineEnd}) { @dropdownmenu.DropdownMenu() { @inputgroup.Button(inputgroup.ButtonProps{ Variant: "ghost", Class: "pr-1.5! text-xs", Attributes: dropdownmenu.Trigger(ctx), }) { Search In... @icon.ChevronDown(icon.Props{Class: "size-3"}) } @dropdownmenu.Content(dropdownmenu.ContentProps{Align: dropdownmenu.AlignEnd, SideOffset: 8, AlignOffset: -4}) { @dropdownmenu.Group() { @dropdownmenu.Item() { Documentation } @dropdownmenu.Item() { Blog Posts } @dropdownmenu.Item() { Changelog } } } } } } </div>}Spinner
package examples import (package examples import ( "github.com/axadrn/shadcn-templ/v2/components/icon" "github.com/axadrn/shadcn-templ/v2/components/inputgroup" "github.com/axadrn/shadcn-templ/v2/components/spinner") templ InputGroupSpinner() { <div class="grid w-full max-w-sm gap-4"> @inputgroup.InputGroup() { @inputgroup.Input(inputgroup.InputProps{Placeholder: "Searching..."}) @inputgroup.Addon(inputgroup.AddonProps{Align: inputgroup.AlignInlineEnd}) { @spinner.Spinner() } } @inputgroup.InputGroup() { @inputgroup.Input(inputgroup.InputProps{Placeholder: "Processing..."}) @inputgroup.Addon() { @spinner.Spinner() } } @inputgroup.InputGroup() { @inputgroup.Input(inputgroup.InputProps{Placeholder: "Saving changes..."}) @inputgroup.Addon(inputgroup.AddonProps{Align: inputgroup.AlignInlineEnd}) { @inputgroup.Text() { Saving... } @spinner.Spinner() } } @inputgroup.InputGroup() { @inputgroup.Input(inputgroup.InputProps{Placeholder: "Refreshing data..."}) @inputgroup.Addon() { @icon.Loader(icon.Props{Class: "animate-spin"}) } @inputgroup.Addon(inputgroup.AddonProps{Align: inputgroup.AlignInlineEnd}) { @inputgroup.Text(inputgroup.TextProps{Class: "text-muted-foreground"}) { Please wait... } } } </div>}Textarea
package examples import (package examples import ( "github.com/axadrn/shadcn-templ/v2/components/button" "github.com/axadrn/shadcn-templ/v2/components/icon" "github.com/axadrn/shadcn-templ/v2/components/inputgroup") templ InputGroupTextareaExample() { <div class="grid w-full max-w-md gap-4"> @inputgroup.InputGroup() { @inputgroup.Textarea(inputgroup.TextareaProps{ ID: "textarea-code", Placeholder: "console.log('Hello, world!');", Class: "min-h-[200px]", }) @inputgroup.Addon(inputgroup.AddonProps{Align: inputgroup.AlignBlockEnd, Class: "border-t"}) { @inputgroup.Text() { Line 1, Column 1 } @inputgroup.Button(inputgroup.ButtonProps{Variant: button.VariantDefault, Size: inputgroup.ButtonSizeSm, Class: "ml-auto"}) { Run @icon.CornerDownLeft() } } @inputgroup.Addon(inputgroup.AddonProps{Align: inputgroup.AlignBlockStart, Class: "border-b"}) { @inputgroup.Text(inputgroup.TextProps{Class: "font-mono font-medium"}) { @icon.FileCode() script.js } @inputgroup.Button(inputgroup.ButtonProps{Size: inputgroup.ButtonSizeIconXs, Class: "ml-auto"}) { @icon.RefreshCw() } @inputgroup.Button(inputgroup.ButtonProps{Variant: button.VariantGhost, Size: inputgroup.ButtonSizeIconXs}) { @icon.Copy() } } } </div>}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 resizable textarea.
package examples import "github.com/axadrn/shadcn-templ/v2/components/inputgroup"package examples import "github.com/axadrn/shadcn-templ/v2/components/inputgroup" templ InputGroupCustom() { <div class="grid w-full max-w-sm gap-6"> @inputgroup.InputGroup() { <textarea data-slot="input-group-control" class="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" placeholder="Autoresize textarea..." ></textarea> @inputgroup.Addon(inputgroup.AddonProps{Align: inputgroup.AlignBlockEnd}) { @inputgroup.Button(inputgroup.ButtonProps{Class: "ml-auto", Size: inputgroup.ButtonSizeSm, Variant: "default"}) { Submit } } } </div>}API Reference
InputGroup
The main component that wraps inputs and addons.
InputGroupAddon
Displays icons, text, buttons, or other content alongside inputs.
For proper focus navigation, the inputgroup.Addon component should be placed after the input. Set the Align prop to position the addon.
For inputgroup.Input, use the inline-start or inline-end alignment. For inputgroup.Textarea, use the block-start or block-end alignment.
The inputgroup.Addon component can have multiple inputgroup.Button components and icons.
InputGroupButton
Displays buttons within input groups.
InputGroupInput
Replacement for input.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.
InputGroupTextarea
Replacement for textarea.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.
InputGroupText
The inputgroup.Text component displays muted text inside the group.