Settings row for a CSS filter stack, all ten functions of it: the row shows the whole stack applied to something, and opens an editor holding the nine numeric filters as rows of the family plus a drop shadow section, over a preview large enough to judge.
Adds the component and everything it depends on to your project.
npx shadcn add https://ui.beste.co/component/r-base/inspector-filtersNew here? Read the installation guide.
The import and the props worth knowing about, in one place.
import { InspectorFilters } from "@/components/beste/component/inspector-filters";
// Uncontrolled. Each piece sits at its own no-op, so an untouched stack is "none".
<InspectorFilters label="Filters" defaultValue={{ contrast: 110, saturate: 120 }} />
// Controlled, with a separate commit for expensive work
<InspectorFilters
label="Image filters"
value={filters}
onValueChange={setFilters}
onValueCommit={(value) => save(value)}
/>
// Pass the picture this stack is actually applied to and the editor stops being a
// demonstration
<InspectorFilters label="Filters" preview={`url(${image})`} value={filters} onValueChange={setFilters} />
<InspectorFilters
label="Backdrop"
icon={ApertureIcon} // optional leading icon
tone="outline" // "muted" (default) | "outline" | "ghost"
size="sm" // "sm" | "default" | "lg"
format="oklch" // notation the shadow's colour is written in
pieces={["blur", "brightness"]} // offer only these; the order stays CSS's own
value={backdrop}
onValueChange={setBackdrop}
/>
// The one filter that is not a number, and the reason it is a filter at all: it
// follows a PNG's transparency or an SVG's outline instead of the box. No spread and
// no inset, because `drop-shadow()` has neither.
<InspectorFilters
label="Logo"
pieces={["dropShadow"]}
defaultValue={{ dropShadow: { x: 0, y: 8, blur: 16, color: "#0f172a55" } }}
/>
// What the value turns into. Pieces sitting at their no-op are left out, which is
// both shorter and easier to check than a stack of brightness(100%).
const filter = [
filters.blur ? `blur(${filters.blur}px)` : null,
filters.brightness !== 100 ? `brightness(${filters.brightness}%)` : null,
filters.sepia ? `sepia(${filters.sepia}%)` : null,
filters.invert ? `invert(${filters.invert}%)` : null,
// The shadow goes last, so it falls from what the rest of the stack produced.
filters.dropShadow?.blur
? `drop-shadow(${filters.dropShadow.x}px ${filters.dropShadow.y}px ${filters.dropShadow.blur}px ${filters.dropShadow.color})`
: null,
].filter(Boolean).join(" ") || "none";
<div style={{ filter }} />
<div style={{ backdropFilter: filter }} />Turn the props on the right; the snippet under them is what you would write to get what you see.
import { InspectorFilters } from "@/components/beste/component/inspector-filters";
<InspectorFilters
label="Filters"
className="w-72"
defaultValue={{"brightness":105,"contrast":110,"saturate":120,"sepia":15}}
/>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 | FiltersValue | — | Controlled value. Pair it with `onValueChange`. |
defaultValue | FiltersValue | — | Initial value in uncontrolled mode. |
onValueChange | (value: FiltersValue) => void | — | Fires on every adjustment, including each frame of a drag. |
onValueCommit | (value: FiltersValue) => void | — | Fires once an adjustment is finished — a slider released. Use it for work too expensive to run per frame. |
onOpenChange | (open: boolean) => void | — | Fires when the editor opens or closes. |
pieces | FilterKey[] | the nine numeric filters, and the drop shadow | Which pieces to offer. Leaving one out also leaves it out of the CSS, so a caller cannot end up shipping a value nobody can edit. The order is always the one CSS applies them in, not the order given here. |
format | "hex" | "oklch" | "rgb" | — | Notation the shadow's colour is written back in. |
preview | string | — | What the preview is filtered. Any CSS image: pass the picture this stack will actually be applied to and the editor stops being a demonstration. |
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`. |
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 point on a surface: the row names the nine spots CSS names and shows the rest as two percentages, and opens a square pad the point is dragged around, with the thirds drawn on it and a pull towards them.
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 for a box shadow: the row shows the shadow itself, and opens an editor holding a larger preview with offset, blur, spread and colour under it, each one a row of the family.