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.
Adds the component and everything it depends on to your project.
npx shadcn add https://ui.beste.co/component/r-base/inspector-tagsNew here? Read the installation guide.
The import and the props worth knowing about, in one place.
import { InspectorTags } from "@/components/beste/component/inspector-tags";
// Uncontrolled
<InspectorTags label="Keywords" defaultValue={["saas", "pricing"]} placeholder="Add a keyword" />
// Controlled
<InspectorTags label="Keywords" value={keywords} onValueChange={setKeywords} />
// A ceiling, which also puts a count beside the label
<InspectorTags label="Tags" max={6} value={tags} onValueChange={setTags} />
<InspectorTags
label="Utility classes"
icon={BracesIcon} // optional leading icon
tone="outline" // "muted" (default) | "outline" | "ghost"
size="sm" // "sm" | "default" | "lg"
maxLength={24} // longest a single tag may be
caseSensitive // "React" and "react" are then two tags
allowDuplicates // keep repeats
value={classes}
onValueChange={setClasses}
/>
// Each tag submits under the same name, so the list arrives as a list
<InspectorTags label="Keywords" name="keywords" defaultValue={["a", "b"]} />
// Pasting "one, two, three" adds three tags; typing a comma ends the one in progress
<InspectorTags label="Keywords" onValueChange={(value) => console.log(value)} />
// Folded away when it is not being written. Closed, the block measures exactly what
// a row of the family measures, and the header still counts what is in there.
<InspectorTags label="Keywords" 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 { InspectorTags } from "@/components/beste/component/inspector-tags";
<InspectorTags
label="Keywords"
className="w-72"
collapsible
defaultValue={["landing page","saas","pricing"]}
placeholder="Add a keyword"
/>Every one of these is in the component already. They are listed because a props table cannot mention a gesture, so nothing else on this page can tell you they exist.
| Enter | Add what is in the field. It does not submit the surrounding form. |
|---|---|
| Comma, or a paste | Splits into several tags at once, trimmed and deduped against the list. |
| Backspace on an empty field | Reach back and take the last tag off. |
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 tags, 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 whenever a tag is added or removed, never mid-typing. |
placeholder | string | — | Ghost text shown in the field while there is room for another tag. |
max | number | — | Most tags the block may hold. Setting it also puts a count beside the label, since a limit nobody can see is a trap. |
maxLength | number | — | Longest a single tag may be. |
collapsible | boolean | — | Let the block be folded away, leaving its header. A long list of tags takes several rows of a drawer and is usually written once, so the panel around it is otherwise mostly a scroll past something nobody is editing. Folded, the header still counts what is in there. |
open | boolean | — | Controlled open state. Pair it with `onOpenChange`. |
defaultOpen | boolean | true | Whether it starts open in uncontrolled mode. |
onOpenChange | (open: boolean) => void | — | |
allowDuplicates | boolean | — | Keep repeats. By default a tag already in the list is dropped. |
caseSensitive | boolean | — | Treat tags differing only in case as different. Off by default, so pasting `React, react` leaves one tag. |
disabled | boolean | — | Block interaction and dim the block. |
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 the tags submit under. Each tag becomes its own hidden input, which is how a list reaches a server without being flattened into one string first. |
id | string | — | Id of the field. One is generated when it is left out. |
className | string | — | |
aria-label | string | — | Accessible name for the field. Falls back to the visible label. |
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.
The columns of a grid as a list of lengths: the row shows the CSS it comes to, the editor holds one length per line with a picture of the shares above them. There is no reordering on purpose, because a track's place in the list is its place in the grid.
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 row for the four edges of a box: the row carries a CSS-shorthand summary, and opens a cross where each field sits on the edge it sets, tied together or not.
Settings row for a list too long to read: pressing it opens a width-matched list with a search field, the keyboard walking the results while focus stays in the field. Options carry groups, descriptions, swatches and search keywords, and `multiple` turns the row into a counted multi-select.