The family's block row: the same surface and tones, but the label sits above a multi-line field whose height comes from the text, with a character count whenever there is a limit to see. Collapsible, so a paragraph written once can be folded down to the height of a row without leaving the form.
Adds the component and everything it depends on to your project.
npx shadcn add https://ui.beste.co/component/r-base/inspector-textareaNew here? Read the installation guide.
The import and the props worth knowing about, in one place.
import { InspectorTextarea } from "@/components/beste/component/inspector-textarea";
// Uncontrolled. The whole block is a label, so a press anywhere puts the caret in.
<InspectorTextarea label="Description" defaultValue="A short line about the section." />
// Controlled, with a separate commit for expensive work
<InspectorTextarea
label="Alt text"
value={alt}
onValueChange={setAlt} // every keystroke
onValueCommit={(value) => console.log("persist", value)} // on blur, if it changed
placeholder="Describe the image"
rows={4}
/>
// A limit brings a count with it, since a limit nobody can see is a trap
<InspectorTextarea label="Summary" maxLength={160} value={summary} onValueChange={setSummary} />
<InspectorTextarea
label="Custom CSS"
icon={Code} // optional leading icon
rows={6}
fixedHeight // stay at `rows` and scroll, rather than growing
tone="outline" // "muted" (default) | "outline" | "ghost"
size="sm" // padding preset; the height comes from `rows`
value={css}
onValueChange={setCss}
/>
// Read-only, and taking part in a form
<InspectorTextarea label="Notes" name="notes" value={notes} onValueChange={setNotes} />
<InspectorTextarea label="Generated" value={output} readOnly />
// Folded away when it is not being written. Closed, the block measures exactly what
// a row of the family measures, and any character count stays in the header.
<InspectorTextarea label="Description" collapsible defaultOpen={false} onOpenChange={(open) => console.log("open:", open)} />
Turn the props on the right; the snippet under them is what you would write to get what you see.
import { InspectorTextarea } from "@/components/beste/component/inspector-textarea";
<InspectorTextarea
label="Description"
className="w-72"
collapsible
defaultValue="A short line about the section, shown under the heading."
placeholder="Optional"
maxLength={160}
/>Read from the component's own type, so this cannot drift from what it accepts.
| Prop | Type | Default | Description |
|---|---|---|---|
label* | string | — | Label rendered above the field, inside the block. |
icon | LucideIcon | — | Optional leading icon shown before the label. |
value | string | — | Controlled value. Pair it with `onValueChange`. |
defaultValue | string | — | Initial value in uncontrolled mode. |
onValueChange | (value: string) => void | — | Fires on every keystroke. |
onValueCommit | (value: string) => void | — | Fires once the edit is finished — leaving the field — and only when the value actually changed. Use it for work too expensive to run per keystroke. |
placeholder | string | — | Ghost text shown while the field is empty. |
rows | number | 3 | Lines the field opens at. It grows past this as text is typed, where the browser supports sizing a field to its content. |
fixedHeight | boolean | — | Keep the field at `rows` and scroll instead of growing. |
collapsible | boolean | — | Let the block be folded away, leaving its header. A paragraph is several rows tall and usually written once, so the panel around it is otherwise mostly a scroll past something nobody is editing. |
open | boolean | — | Controlled open state. Pair it with `onOpenChange`. |
defaultOpen | boolean | true | Whether it starts open in uncontrolled mode. |
onOpenChange | (open: boolean) => void | — | |
maxLength | number | — | Longest the value may be. Setting it also puts a count beside the label, which is the only reason a reader can tell how much room is left. |
disabled | boolean | — | Block interaction and dim the block. |
readOnly | boolean | — | Show the value but refuse edits. |
tone | "muted" | "outline" | "ghost" | "muted" | Surface treatment: filled (default), hairline outline, or bare until focus. |
size | "sm" | "default" | "lg" | "default" | Padding preset. |
name | string | — | Name of the field, so the block can take part in a form. |
id | string | — | Id of the field. One is generated when it is not given. |
className | string | — | |
aria-label | string | — | Accessible name. Falls back to the visible label. |
Settings block for a snippet: a monospaced field with a language chip and a line count, Tab indenting instead of leaving, and a fixed height so a pasted stylesheet cannot push the rest of the drawer out of reach. Collapsible, since a snippet is the tallest thing in a drawer and the least often touched; folded, it measures exactly what a row measures and still reports its line count.
Settings block for a list of short strings: tags are typed one after another and wrap under the label, a comma or a paste splits several at once, and Backspace on an empty field reaches back into the list. Collapsible, so a long list folds down to the height of a row with the count still on it.
Settings row for a border: the row draws one exactly as described beside a summary of it, and opens an editor with the sides as icon toggles and the width, style and colour under them, each one a row of the family.
Settings row with a label on the left and a plain text field on the right, sharing the family's pill: the whole row is the label, so pressing anywhere puts the caret in the field.
Where the contents of a box sit in it: nine cells drawn in visual terms, mapped onto justify and align afterwards so the pair swapping places under a column direction is the component's problem and not the reader's. Direction, distribution and stretch sit under the pad as rows of the family, since each is a setting like any other.