Inspector
<Inspector>, <InspectorButton>, and <Configurable> form a live-edit system that lets end users customize parts of the UI at runtime. Users toggle "edit mode" via the <InspectorButton> (typically placed in the app header). Once edit mode is on, every <Configurable> element shows a hover affordance and a cogwheel that opens its own editor inside the floating <Inspector> panel. Changes persist in the Store, so user choices survive reloads.
Installation
pnpm dlx shadcn@latest add @shadmin/inspectorUsage
Add a single <Inspector> near the root of your application, a <InspectorButton> somewhere the user can click, and wrap each customizable element in a <Configurable>.
import {
Admin,
Configurable,
FieldsSelector,
Inspector,
InspectorButton,
Layout,
} from "@/components/admin";
import { Resource } from "ra-core";
const MyLayout = ({ children }: { children: React.ReactNode }) => (
<Layout appBarChildren={<InspectorButton />}>
{children}
<Inspector />
</Layout>
);
export const App = () => (
<Admin dataProvider={dataProvider} layout={MyLayout}>
<Resource name="books" list={BookList} />
</Admin>
);For the editor element of a <Configurable>, you can pass any React element. The most common is <FieldsSelector>, which presents a list of fields with toggles and drag-handles, and persists the user's selection.
const BookList = () => (
<List>
<Configurable preferenceKey="books.datatable" editor={<FieldsSelector />}>
<DataTable>
<DataTable.Col source="title" />
<DataTable.Col source="author" />
<DataTable.Col source="year" />
</DataTable>
</Configurable>
</List>
);When the user clicks the cogwheel that appears next to the configurable element, the <Inspector> opens and shows the editor element, with its preferenceKey injected so it reads and writes preferences scoped to preferences.${preferenceKey}.
How preferences are stored
<Configurable> namespaces all preferences under preferences.${preferenceKey}. Inside that namespace, editor components like <FieldsSelector> write smaller keys (e.g. columns, availableColumns, omit). Reading those values back from anywhere inside the same configurable subtree uses usePreference from ra-core.
The Inspector's trash icon calls useRemoveItemsFromStore under the current namespace, which wipes all preferences for the active configurable and re-mounts the editor so the user sees the defaults.
Props
Each component in the inspector family has its own props table. See the per-component sections below:
<InspectorButton>—label, button-styling props (forwarded to<Button>)<Inspector>—className<InspectorRoot>— no props (fallback content)<Configurable>—children,editor,preferenceKey,openButtonLabel,className<FieldsSelector>—name,availableName
<InspectorButton>
A ghost icon button that toggles edit mode on or off.
import { InspectorButton } from "@/components/admin";
const AppBar = () => (
<header>
<InspectorButton />
</header>
);| Prop | Type | Default | Description |
|---|---|---|---|
| asChild | boolean | — | |
| icon | ReactNode | — | Custom icon node. Defaults to the Settings icon. |
| label | string | — | Translation key for the tooltip and aria-label. |
| ref | Ref<HTMLButtonElement> | — | Allows getting a ref to the component instance. Once the component unmounts, React will set `ref.current` to `null` (or call the ref with `null` if you passed a callback ref). |
| size | "default" | "sm" | "xs" | "lg" | "icon" | "icon-xs" | "icon-sm" | "icon-lg" | null | — | |
| variant | "link" | "default" | "secondary" | "destructive" | "outline" | "ghost" | null | — |
Additional props are forwarded to the underlying <Button>.
<Inspector>
The floating panel that displays the currently selected editor. Render exactly one <Inspector> in your application (typically alongside the layout's children).
import { Inspector } from "@/components/admin";
const MyLayout = ({ children }) => (
<>
{children}
<Inspector />
</>
);Inspector are typed in the component source.The inspector panel renders in a fixed position at the top-right of the viewport. It auto-hides when edit mode is off.
<InspectorRoot>
The default content of the inspector when no configurable element is selected — a translated invitation to hover the UI.
You do not normally render <InspectorRoot> directly; it is the fallback rendered inside <Inspector> when its editor is empty.
<Configurable>
Wraps a UI element to make it editable.
import { Configurable, FieldsSelector } from "@/components/admin";
const BookList = () => (
<Configurable preferenceKey="books.list" editor={<FieldsSelector />}>
<BooksDataTable />
</Configurable>
);Configurable are typed in the component source.When the <PreferencesEditorContextProvider> is not present in the tree, <Configurable> renders its children unchanged.
<FieldsSelector>
Reusable editor body for the inspector — a switchable, reorderable list of fields backed by preferences.
import { Configurable, FieldsSelector } from "@/components/admin";
const ProductGrid = () => (
<Configurable preferenceKey="products.grid" editor={<FieldsSelector />}>
<Grid />
</Configurable>
);| Prop | Type | Default | Description |
|---|---|---|---|
| availableName | string | — | Preference key for the array of available fields. |
| name | string | — | Preference key for the array of selected field indexes. |
<FieldsSelector> reads its data from preferences. The component reading from this preference is expected to populate preferences.${preferenceKey}.${availableName} with an array of { index, source, label? } entries on first render.
See also
<SimpleFormConfigurable>for a ready-made configurable form<ColumnsButton>for a non-inspector approach to column visibility