Components
Input Group
shadcn composable input group, acrylic-styled — pair a control with icon, text, and button addons; auto-grow via InputGroupTextarea.
A composable input group, ported from shadcn/ui and
restyled with the Acrylic tokens so it matches our Input.
This one is not from the Apple macOS 26 UI Kit — it's a shadcn composable that
slots into the same theme. The group owns the chrome (border, control surface,
radius, and the focus-within accent ring); the inner control is bare.
It supersedes the old AutoTextarea: auto-grow now lives on
InputGroupTextarea via the autoResize prop, so a comment box that grows with
its content is just a textarea inside a group.
Installation
npx shadcn add https://acrylic-ui.vercel.app/r/input-group.jsonUsage
"use client"import { useState } from "react"import { Search } from "lucide-react"import { InputGroup, InputGroupAddon, InputGroupButton, InputGroupInput, InputGroupText, InputGroupTextarea,} from "@/registry/acrylic/input-group"const MAX = 280export default function InputGroupDemo() { const [value, setValue] = useState("") return ( <div className="flex w-full max-w-md flex-col gap-3 text-foreground"> {/* Auto-growing comment box: textarea grows to 5 rows, then scrolls. */} <InputGroup> <InputGroupTextarea autoResize maxRows={5} value={value} onChange={(e) => setValue(e.target.value.slice(0, MAX))} placeholder="Write a comment — it grows as you type…" /> <InputGroupAddon align="block-end"> <InputGroupText> {value.length}/{MAX} </InputGroupText> <InputGroupButton className="ml-auto" variant="default"> Send </InputGroupButton> </InputGroupAddon> </InputGroup> {/* Simple search field: leading icon addon + bare input. */} <InputGroup> <InputGroupInput placeholder="Search…" /> <InputGroupAddon align="inline-start"> <Search /> </InputGroupAddon> </InputGroup> {/* Trailing Button addon: the button hugs the edge, evenly inset. */} <InputGroup> <InputGroupInput placeholder="Type to search…" /> <InputGroupAddon align="inline-end"> <InputGroupButton variant="secondary">Search</InputGroupButton> </InputGroupAddon> </InputGroup> </div> )}import {
InputGroup,
InputGroupAddon,
InputGroupButton,
InputGroupInput,
InputGroupText,
InputGroupTextarea,
} from "@/components/acrylic/input-group"
<InputGroup>
<InputGroupTextarea autoResize maxRows={5} placeholder="Write a comment…" />
<InputGroupAddon align="block-end">
<InputGroupText>0/280</InputGroupText>
<InputGroupButton className="ml-auto" variant="default">Send</InputGroupButton>
</InputGroupAddon>
</InputGroup>Examples
Leading icon
Trailing button
Text prefix
Block-end counter
Auto-resize textarea
"use client"import { useState } from "react"import { Search } from "lucide-react"import { InputGroup, InputGroupAddon, InputGroupButton, InputGroupInput, InputGroupText, InputGroupTextarea,} from "@/registry/acrylic/input-group"const MAX = 140export default function InputGroupShowcase() { const [bio, setBio] = useState("") return ( <div className="flex w-full max-w-md flex-col gap-4 text-foreground"> {/* Leading icon addon */} <div className="flex flex-col gap-1.5"> <span className="text-xs text-muted-foreground">Leading icon</span> <InputGroup> <InputGroupInput placeholder="Search…" /> <InputGroupAddon align="inline-start"> <Search /> </InputGroupAddon> </InputGroup> </div> {/* Trailing button addon */} <div className="flex flex-col gap-1.5"> <span className="text-xs text-muted-foreground">Trailing button</span> <InputGroup> <InputGroupInput placeholder="Add a teammate by email…" /> <InputGroupAddon align="inline-end"> <InputGroupButton variant="default">Invite</InputGroupButton> </InputGroupAddon> </InputGroup> </div> {/* Text prefix */} <div className="flex flex-col gap-1.5"> <span className="text-xs text-muted-foreground">Text prefix</span> <InputGroup> <InputGroupInput placeholder="acme.dev" /> <InputGroupAddon align="inline-start"> <InputGroupText>https://</InputGroupText> </InputGroupAddon> </InputGroup> </div> {/* Block-end addon with counter + action */} <div className="flex flex-col gap-1.5"> <span className="text-xs text-muted-foreground">Block-end counter</span> <InputGroup> <InputGroupInput value={bio} onChange={(e) => setBio(e.target.value.slice(0, MAX))} placeholder="Short bio…" /> <InputGroupAddon align="block-end"> <InputGroupText> {bio.length}/{MAX} </InputGroupText> <InputGroupButton className="ml-auto" variant="default"> Send </InputGroupButton> </InputGroupAddon> </InputGroup> </div> {/* Auto-resizing textarea */} <div className="flex flex-col gap-1.5"> <span className="text-xs text-muted-foreground">Auto-resize textarea</span> <InputGroup> <InputGroupTextarea autoResize maxRows={5} placeholder="Grows from 1 row to 5, then scrolls…" /> </InputGroup> </div> </div> )}Notes
- Sub-components:
InputGroup(wrapper that carries the field chrome),InputGroupInput(bare<input>),InputGroupTextarea(bare<textarea>),InputGroupAddon(icons / text / buttons),InputGroupButton(compact Button,ghostby default), andInputGroupText(muted inline text). - Addon placement: put
InputGroupAddonafter the control in the DOM and letalignposition it. Values:inline-start(default),inline-end,block-start,block-end. Clicking an addon's empty space focuses the control. - Auto-grow: set
autoResizeonInputGroupTextareato grow from 1 row up tomaxRows(default3), then scroll with the macOS-style overlay scrollbar. Works in both controlled (value) and uncontrolled usage.