Invoergroep
Voeg pictogrammen, knoppen en toetsenbordhints toe aan invoervelden voor zoekbalken, URL-velden en chatvensters — met neobrutalistische randen en schaduwen.
import { Search } from "lucide-react"
import {De input group verpakt een input of textarea samen met zijn add-ons — iconen, inline tekst, knoppen, sneltoetshints — in één omrand besturingselement. Het is een eigen compositie van Input, Textarea en Button uit deze bibliotheek, vormgegeven volgens het neobrutalistische recept: dikke randen, scherpe schaduwen en vette typografie.
Ideaal voor:
- Zoekvelden — een zoekicoon vooraan plus een
⌘K-hint of wisknop achteraan. - Inputs met prefix of suffix — vaste tekst als
https://,@of.com, zodat gebruikers alleen het variabele deel typen. - Chat- en promptcomposers — een textarea met een
block-end-werkbalk met knoppen en een verzendactie.
Installatie#
pnpm dlx shadcn@latest add https://neobrutalism.com/r/base/input-group.jsonnpx shadcn@latest add https://neobrutalism.com/r/base/input-group.jsonyarn dlx shadcn@latest add https://neobrutalism.com/r/base/input-group.jsonbunx --bun shadcn@latest add https://neobrutalism.com/r/base/input-group.json
Kopieer en plak de volgende code in je project.
"use client"
import * as React from "react"
import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "@/lib/utils"
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-8 w-full min-w-0 items-center rounded border-2 bg-input shadow-sm 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]:outline-2 has-[[data-slot=input-group-control]:focus-visible]:outline-offset-2 has-[[data-slot=input-group-control]:focus-visible]:outline-primary has-[[data-slot][aria-invalid=true]]:border-destructive 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 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 outline-none focus-visible:outline-none disabled:bg-transparent aria-invalid:outline-none 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 outline-none focus-visible:outline-none disabled:bg-transparent aria-invalid:outline-none dark:bg-transparent dark:disabled:bg-transparent",
className
)}
{...props}
/>
)
}
export {
InputGroup,
InputGroupAddon,
InputGroupButton,
InputGroupText,
InputGroupInput,
InputGroupTextarea,
}
Werk de importpaden bij zodat ze overeenkomen met je projectconfiguratie.
Gebruik#
import {
InputGroup,
InputGroupAddon,
InputGroupButton,
InputGroupInput,
InputGroupText,
InputGroupTextarea,
} from "@/components/ui/input-group"<InputGroup>
<InputGroupInput placeholder="Search..." />
<InputGroupAddon>
<SearchIcon />
</InputGroupAddon>
</InputGroup>Compositie#
Gebruik de volgende compositie om een InputGroup te bouwen:
InputGroup
├── InputGroupInput or InputGroupTextarea
├── InputGroupAddon
├── InputGroupButton
└── InputGroupTextUitlijning#
Gebruik de align-prop op InputGroupAddon om de add-on te positioneren ten opzichte van het invoerveld.
Voor correct focusbeheer moet InputGroupAddon altijd na InputGroupInput of
InputGroupTextarea in de DOM worden geplaatst. Gebruik de align-prop om de
add-on visueel te positioneren.
inline-start#
Gebruik align="inline-start" om de add-on aan het begin van het invoerveld te positioneren. Dit is de standaard.
import { SearchIcon } from "lucide-react"
import {inline-end#
Gebruik align="inline-end" om de add-on aan het einde van het invoerveld te positioneren.
import { EyeOffIcon } from "lucide-react"
import {block-start#
Gebruik align="block-start" om de add-on boven het invoerveld te positioneren.
import { CopyIcon, FileCodeIcon } from "lucide-react"
import {block-end#
Gebruik align="block-end" om de add-on onder het invoerveld te positioneren.
import {
Field,
FieldDescription,Voorbeelden#
Pictogram#
import {
CheckIcon,
CreditCardIcon,Tekst#
import {
InputGroup,
InputGroupAddon,Knop#
"use client"
import * as React from "react"Kbd#
import { SearchIcon } from "lucide-react"
import {Dropdownmenu#
"use client"
import { ChevronDownIcon, MoreHorizontal } from "lucide-react"Spinner#
import { LoaderIcon } from "lucide-react"
import {Textarea#
import {
IconBrandJavascript,
IconCopy,Aangepast invoerveld#
Voeg het data-slot="input-group-control"-attribuut toe aan je aangepaste invoerveld voor automatische afhandeling van de focusstaat.
Hier is een voorbeeld van een aangepaste, in grootte aanpasbare textarea uit een externe bibliotheek.
"use client"
import TextareaAutosize from "react-textarea-autosize"RTL#
Om RTL-ondersteuning in Neobrutalism in te schakelen, zie de RTL-configuratiegids.
"use client"
import * as React from "react"Toegankelijkheid#
Er bestaat geen ARIA-widgetpatroon voor input groups — de wrapper rendert role="group" zodat ondersteunende technologie de add-ons aan het besturingselement koppelt, en de toetsenbordtoegang is native: Tab bereikt de input en elke InputGroupButton in DOM-volgorde — precies waarom add-ons ná de input horen (visueel gepositioneerd met align). Twee dingen blijven aan jou: add-on-tekst zoals https:// is visueel, geen programmatisch label, dus de input heeft nog steeds een <Label> of aria-label nodig; en knoppen met alleen een icoon hebben een aria-label nodig — klikken op een add-on geeft de input focus, maar dat is een gemak voor de aanwijzer, geen toetsenbordroute.
API-referentie#
InputGroup#
Het hoofdcomponent dat invoervelden en add-ons omhult.
| Prop | Type | Default |
|---|---|---|
className | string |
<InputGroup>
<InputGroupInput />
<InputGroupAddon />
</InputGroup>InputGroupAddon#
Toont pictogrammen, tekst, knoppen of andere inhoud naast invoervelden.
Voor correcte focusnavigatie moet het InputGroupAddon-component na het
invoerveld worden geplaatst. Stel de align-prop in om de add-on te
positioneren.
| Prop | Type | Default |
|---|---|---|
align | "inline-start" | "inline-end" | "block-start" | "block-end" | "inline-start" |
className | string |
<InputGroupAddon align="inline-end">
<SearchIcon />
</InputGroupAddon>Gebruik voor <InputGroupInput /> de uitlijning inline-start of inline-end. Gebruik voor <InputGroupTextarea /> de uitlijning block-start of block-end.
Het InputGroupAddon-component kan meerdere InputGroupButton-componenten en pictogrammen bevatten.
<InputGroupAddon>
<InputGroupButton>Button</InputGroupButton>
<InputGroupButton>Button</InputGroupButton>
</InputGroupAddon>InputGroupButton#
Toont knoppen binnen invoergroepen.
| Prop | Type | Default |
|---|---|---|
size | "xs" | "icon-xs" | "sm" | "icon-sm" | "xs" |
variant | "default" | "destructive" | "outline" | "secondary" | "ghost" | "link" | "ghost" |
className | string |
<InputGroupButton>Button</InputGroupButton>
<InputGroupButton size="icon-xs" aria-label="Copy">
<CopyIcon />
</InputGroupButton>InputGroupInput#
Vervanging voor <Input /> bij het bouwen van invoergroepen. Dit component heeft de invoergroepstijlen vooraf toegepast en gebruikt de uniforme data-slot="input-group-control" voor de afhandeling van de focusstaat.
| Prop | Type | Default |
|---|---|---|
className | string |
Alle andere props worden doorgegeven aan het onderliggende <Input />-component.
<InputGroup>
<InputGroupInput placeholder="Enter text..." />
<InputGroupAddon>
<SearchIcon />
</InputGroupAddon>
</InputGroup>InputGroupTextarea#
Vervanging voor <Textarea /> bij het bouwen van invoergroepen. Dit component heeft de textarea-groepstijlen vooraf toegepast en gebruikt de uniforme data-slot="input-group-control" voor de afhandeling van de focusstaat.
| Prop | Type | Default |
|---|---|---|
className | string |
Alle andere props worden doorgegeven aan het onderliggende <Textarea />-component.
<InputGroup>
<InputGroupTextarea placeholder="Enter message..." />
<InputGroupAddon align="block-end">
<InputGroupButton>Send</InputGroupButton>
</InputGroupAddon>
</InputGroup>