Inspector Filters

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.

Installation

Adds the component and everything it depends on to your project.

npx shadcn add https://ui.beste.co/component/r-base/inspector-filters

New here? Read the installation guide.

Usage

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 }} />

Playground

Turn the props on the right; the snippet under them is what you would write to get what you see.

Usage
import { InspectorFilters } from "@/components/beste/component/inspector-filters";

<InspectorFilters
  label="Filters"
  className="w-72"
  defaultValue={{"brightness":105,"contrast":110,"saturate":120,"sepia":15}}
/>

Props

Read from the component's own type, so this cannot drift from what it accepts.

PropTypeDefaultDescription
label*stringLabel rendered on the left, inside the row.
iconLucideIconOptional leading icon shown before the label.
valueFiltersValueControlled value. Pair it with `onValueChange`.
defaultValueFiltersValueInitial value in uncontrolled mode.
onValueChange(value: FiltersValue) => voidFires on every adjustment, including each frame of a drag.
onValueCommit(value: FiltersValue) => voidFires once an adjustment is finished — a slider released. Use it for work too expensive to run per frame.
onOpenChange(open: boolean) => voidFires when the editor opens or closes.
piecesFilterKey[]the nine numeric filters, and the drop shadowWhich 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.
previewstringWhat the preview is filtered. Any CSS image: pass the picture this stack will actually be applied to and the editor stops being a demonstration.
disabledbooleanBlock 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.
classNamestring
aria-labelstringAccessible name. Falls back to `label`.

More Inspector components

View all Inspector

Inspector Spacing

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.

Inspector Position

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.

Inspector Border

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.

Inspector Shadow

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.

135°

Inspector Angle

Settings row for a direction: a dial you drag round, reading in degrees the way CSS does (zero up, growing clockwise), with a range input underneath carrying the keyboard and the form.

Inspector Gradient

Settings row for a gradient: the row shows a strip of it, and opens an editor with draggable stops over the ramp, so one colour can hold a wider share than the next. The kind, the direction and the selected stop's colour and offset sit under it as rows of the family.