Grup input
Tambahkan ikon, tombol, dan petunjuk keyboard ke kolom input untuk bilah pencarian, kolom URL, dan jendela penulisan pesan obrolan — dengan bingkai dan bayangan bergaya neobrutalis.
import { Search } from "lucide-react"
import {Input group membungkus input atau textarea bersama addon-nya — ikon, teks sebaris, tombol, petunjuk pintasan keyboard — di dalam satu kontrol bergaris tepi. Ini komposisi internal dari Input, Textarea, dan Button milik pustaka ini, ditata dengan resep neobrutalis: garis tepi tebal, bayangan pekat, dan tipografi tegas.
Gunakan komponen ini saat:
- Kolom pencarian — ikon pencarian di depan plus petunjuk
⌘Katau tombol hapus di belakang. - Input berprefiks dan bersufiks — teks tetap
https://,@, atau.comsehingga pengguna hanya mengetik bagian yang berubah. - Komposer chat dan prompt — textarea dengan toolbar tombol di posisi
block-enddan aksi kirim.
Instalasi#
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
Salin dan tempel kode berikut ke dalam proyek Anda.
"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,
}
Perbarui jalur impor agar sesuai dengan pengaturan proyek Anda.
Penggunaan#
import {
InputGroup,
InputGroupAddon,
InputGroupButton,
InputGroupInput,
InputGroupText,
InputGroupTextarea,
} from "@/components/ui/input-group"<InputGroup>
<InputGroupInput placeholder="Search..." />
<InputGroupAddon>
<SearchIcon />
</InputGroupAddon>
</InputGroup>Komposisi#
Gunakan komposisi berikut untuk membangun InputGroup:
InputGroup
├── InputGroupInput or InputGroupTextarea
├── InputGroupAddon
├── InputGroupButton
└── InputGroupTextPerataan#
Gunakan prop align pada InputGroupAddon untuk memosisikan addon relatif terhadap input.
Untuk pengelolaan fokus yang tepat, InputGroupAddon harus selalu ditempatkan
setelah InputGroupInput atau InputGroupTextarea di DOM. Gunakan prop
align untuk memosisikan addon secara visual.
inline-start#
Gunakan align="inline-start" untuk memosisikan addon di awal input. Ini adalah nilai default.
import { SearchIcon } from "lucide-react"
import {inline-end#
Gunakan align="inline-end" untuk memosisikan addon di akhir input.
import { EyeOffIcon } from "lucide-react"
import {block-start#
Gunakan align="block-start" untuk memosisikan addon di atas input.
import { CopyIcon, FileCodeIcon } from "lucide-react"
import {block-end#
Gunakan align="block-end" untuk memosisikan addon di bawah input.
import {
Field,
FieldDescription,Contoh#
Ikon#
import {
CheckIcon,
CreditCardIcon,Teks#
import {
InputGroup,
InputGroupAddon,Tombol#
"use client"
import * as React from "react"Kbd#
import { SearchIcon } from "lucide-react"
import {Menu Dropdown#
"use client"
import { ChevronDownIcon, MoreHorizontal } from "lucide-react"Spinner#
import { LoaderIcon } from "lucide-react"
import {Textarea#
import {
IconBrandJavascript,
IconCopy,Input Kustom#
Tambahkan atribut data-slot="input-group-control" ke input kustom Anda untuk penanganan status fokus otomatis.
Berikut contoh textarea kustom yang dapat diubah ukurannya dari pustaka pihak ketiga.
"use client"
import TextareaAutosize from "react-textarea-autosize"RTL#
Untuk mengaktifkan dukungan RTL di Neobrutalism, lihat panduan konfigurasi RTL.
"use client"
import * as React from "react"Aksesibilitas#
Tidak ada pola widget ARIA untuk input group — pembungkusnya merender role="group" agar teknologi asistif mengaitkan addon dengan kontrolnya, dan akses keyboard bersifat natif: Tab menjangkau input dan setiap InputGroupButton sesuai urutan DOM, itulah sebabnya addon sebaiknya diletakkan setelah input (diposisikan secara visual dengan align). Dua hal tetap menjadi tanggung jawab Anda: teks addon seperti https:// bersifat visual, bukan label programatik, jadi input tetap memerlukan <Label> atau aria-label; dan tombol yang hanya berisi ikon memerlukan aria-label — mengklik addon memang memfokuskan input, tetapi itu kenyamanan pointer, bukan jalur keyboard.
Referensi API#
InputGroup#
Komponen utama yang membungkus input dan addon.
| Prop | Type | Default |
|---|---|---|
className | string |
<InputGroup>
<InputGroupInput />
<InputGroupAddon />
</InputGroup>InputGroupAddon#
Menampilkan ikon, teks, tombol, atau konten lain di samping input.
Untuk navigasi fokus yang tepat, komponen InputGroupAddon harus ditempatkan
setelah input. Setel prop align untuk memosisikan addon.
| Prop | Type | Default |
|---|---|---|
align | "inline-start" | "inline-end" | "block-start" | "block-end" | "inline-start" |
className | string |
<InputGroupAddon align="inline-end">
<SearchIcon />
</InputGroupAddon>Untuk <InputGroupInput />, gunakan perataan inline-start atau inline-end. Untuk <InputGroupTextarea />, gunakan perataan block-start atau block-end.
Komponen InputGroupAddon dapat memiliki beberapa komponen InputGroupButton dan ikon.
<InputGroupAddon>
<InputGroupButton>Button</InputGroupButton>
<InputGroupButton>Button</InputGroupButton>
</InputGroupAddon>InputGroupButton#
Menampilkan tombol di dalam grup input.
| 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#
Pengganti <Input /> saat membangun grup input. Komponen ini sudah menerapkan gaya grup input dan menggunakan data-slot="input-group-control" terpadu untuk penanganan status fokus.
| Prop | Type | Default |
|---|---|---|
className | string |
Semua prop lainnya diteruskan ke komponen <Input /> yang mendasarinya.
<InputGroup>
<InputGroupInput placeholder="Enter text..." />
<InputGroupAddon>
<SearchIcon />
</InputGroupAddon>
</InputGroup>InputGroupTextarea#
Pengganti <Textarea /> saat membangun grup input. Komponen ini sudah menerapkan gaya grup textarea dan menggunakan data-slot="input-group-control" terpadu untuk penanganan status fokus.
| Prop | Type | Default |
|---|---|---|
className | string |
Semua prop lainnya diteruskan ke komponen <Textarea /> yang mendasarinya.
<InputGroup>
<InputGroupTextarea placeholder="Enter message..." />
<InputGroupAddon align="block-end">
<InputGroupButton>Send</InputGroupButton>
</InputGroupAddon>
</InputGroup>