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.
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 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.
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.
Width and height on one row, with the lock between them: while it is closed, editing one scales the other by the ratio the pair had when the lock closed. Both fields are as wide as the largest number they can hold, so typing never shifts the row.
Settings row that names the chosen icon and shows it on the right; pressing the row opens a searchable palette below it, width-matched to the row, with clearing offered at the foot of it. The icon set is passed in, so the row never bundles an icon library of its own.