Primitives
Textarea
Multi-line field styled as a data readout.
● LIVE
"use client";import { Label } from "@/registry/okibi/ui/label";import { Textarea } from "@/registry/okibi/ui/textarea";export default function TextareaDemo() { return ( <div className="grid w-full max-w-xs gap-2"> <Label htmlFor="textarea-demo-log">Mission Log</Label> <Textarea id="textarea-demo-log" placeholder="Enter log entry" /> </div> );}npx shadcn@latest add https://okibi.cndr.dev/r/textarea.jsonInstallation
Install the theme first, then add the component:
npx shadcn@latest add https://okibi.cndr.dev/r/textarea.jsonCopy the source from the Code tab above into components/ui/textarea.tsx. It uses the cn helper and the okibi theme tokens — install the theme first if you haven't.
Usage
import { Textarea } from "@/components/ui/textarea"
<Textarea placeholder="Log entry…" />Examples
With label
A Label above the field names the control and gives the placeholder context.
● LIVE
import { Label } from "@/registry/okibi/ui/label";import { Textarea } from "@/registry/okibi/ui/textarea";export default function TextareaWithLabelDemo() { return ( <div className="w-72 space-y-2"> <Label htmlFor="textarea-with-label-log">Mission log</Label> <Textarea id="textarea-with-label-log" placeholder="Enter log entry" /> </div> );}With button
Stack a Button under the field for a message composer or log-entry box.
● LIVE
import { Button } from "@/registry/okibi/ui/button";import { Textarea } from "@/registry/okibi/ui/textarea";export default function TextareaWithButtonDemo() { return ( <div className="flex w-72 flex-col gap-2"> <Textarea placeholder="Compose transmission" /> <Button variant="signal" className="self-end"> Transmit </Button> </div> );}Disabled
The native disabled attribute blocks interaction and dims the field.
● LIVE
import { Textarea } from "@/registry/okibi/ui/textarea";export default function TextareaDisabledDemo() { return ( <Textarea disabled placeholder="Channel sealed // read-only" className="w-72" /> );}Invalid
Set aria-invalid to flip the keyline and inset focus ring to the destructive color, with a mono error line beneath.
● LIVE
SEC // payload failed integrity check.
import { Label } from "@/registry/okibi/ui/label";import { Textarea } from "@/registry/okibi/ui/textarea";export default function TextareaInvalidDemo() { return ( <div className="w-72 space-y-2"> <Label htmlFor="textarea-invalid">Transmission</Label> <Textarea id="textarea-invalid" aria-invalid defaultValue="// signal corrupted at offset 0x1F" /> <p className="font-mono text-xs text-destructive-spark"> SEC // payload failed integrity check. </p> </div> );}Accessibility
- Renders a native
textarea— multiline keyboard editing, selection, and scroll come for free. - It ships no label — bind a
LabelviahtmlFor/idso the control is named. field-sizing-contentauto-grows the box with the text; nativerowsstill sets the starting height.- Set
aria-invalidto surface an error: border, faint fill, and focus ring swap to destructive. - Focus-visible draws a 2px inset signal ring;
disableddrops opacity to 60% and blocks pointer events.
Guidelines
Do
- Pair it with a
Labelbound byhtmlForand keep the placeholder as a hint, not the name. - Let
field-sizing-contentgrow the field; setrowsonly to seed the initial height. - Flag validation failures with
aria-invalidand link the message viaaria-describedby. - Reserve it for free-form multiline entry — use
Inputfor single-line values.
Don't
- Don't fight the auto-grow with a fixed
height— adjustmin-horrowsinstead. - Don't depend on the uppercase placeholder to name the field.
- Don't expect a built-in character counter or error slot — render those yourself.
- Don't paint an error border without also setting
aria-invalid.