The pairs nobody could have put in the panel in advance: data-* hooks, an ARIA relationship, a custom property, an og: name. Two text fields per line and nothing else, with no reordering and no per-row editor. Duplicate names are marked rather than refused, since an edit passes through one on its way.
Adds the component and everything it depends on to your project.
npx shadcn add https://ui.beste.co/component/r-base/inspector-attributesNew here? Read the installation guide.
The import and the props worth knowing about, in one place.
import {
InspectorAttributes,
attributesToRecord,
} from "@/components/beste/component/inspector-attributes";
<InspectorAttributes
label="Attributes"
defaultValue={[{ key: "data-analytics-id", value: "hero-cta" }]}
// Pressable chips under the pairs: one press adds the name with an empty value,
// and a name already in the list drops out of the row
suggestions={["data-testid", "data-analytics-id", "aria-describedby", "rel"]}
/>
<InspectorAttributes
label="Meta tags"
value={attributes} // [{ key, value }]
onValueChange={setAttributes}
onOpenChange={(open) => console.log("editor open:", open)}
keyPlaceholder="property"
valuePlaceholder="content"
max={12}
tone="outline" // "muted" (default) | "outline" | "ghost"
size="sm" // "sm" | "default" | "lg"
/>
// The pairs as the object they are for. Empty names are dropped: a half-typed
// line is not an attribute yet.
<div {...attributesToRecord(attributes)} />Turn the props on the right; the snippet under them is what you would write to get what you see.
import { InspectorAttributes } from "@/components/beste/component/inspector-attributes";
<InspectorAttributes
label="Attributes"
className="w-72"
defaultValue={[{"key":"data-analytics-id","value":"hero-cta"},{"key":"aria-describedby","value":"hero-note"}]}
suggestions={["data-testid","data-analytics-id","aria-describedby","rel"]}
/>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 / Space | Open the editor from the row |
|---|---|
| Tab | Walks name, value, remove, then the next line |
| Typing a name | Drops anything that cannot be in one, spaces included |
Read from the component's own type, so this cannot drift from what it accepts.
| Prop | Type | Default | Description |
|---|---|---|---|
label* | string | — | Label rendered on the left, inside the row. |
icon | LucideIcon | — | Optional leading icon shown before the label. |
value | Attribute[] | — | Controlled value. Pair it with `onValueChange`. |
defaultValue | Attribute[] | — | Initial value in uncontrolled mode. |
onValueChange | (value: Attribute[]) => void | — | |
onOpenChange | (open: boolean) => void | — | Fires when the editor opens or closes. |
keyPlaceholder | string | — | Ghost text for the two fields. |
valuePlaceholder | string | — | |
suggestions | string[] | — | Names worth offering, shown as pressable chips under the pairs: one press adds that name with an empty value. Names already in the list drop out of the row, so it also says what is left. They stay suggestions rather than becoming a menu of allowed names — the point of this row is the names nobody anticipated. |
max | number | — | Ceiling on how many pairs there may be. |
disabled | boolean | — | Block interaction and dim the row. |
tone | "muted" | "outline" | "ghost" | "muted" | Surface treatment: filled (default), hairline outline, or bare until hover. |
size | "sm" | "default" | "lg" | "default" | Row height preset. |
className | string | — | |
aria-label | string | — | Accessible name. Falls back to `label`. |
The line of prose that belongs between rows: what a setting is for, why one is switched off, what a value will cost. Four tones, each with its own mark, and the same text size as the rows around it.
Settings row for a ratio: the row prints it and draws it, and opens an editor with the ratios worth a name plus both sides always editable. It is kept as two numbers rather than as their quotient, since 16:9 is what a reader recognises.
The choice that has to be seen to be made: a template, a layout, a chart type. Each option is a picture, normally a rendered screenshot passed as a URL, or markup for the choices no photograph would show. The row is the trigger, the grid is width-matched to it, and the captions under the pictures can be dropped for a set whose pictures say everything.
A number and the unit that gives it meaning, in one row: px, rem, a percentage, or a keyword like auto that is the whole value on its own. Each unit may bring its own range, so a percentage stops at 100 while pixels do not.