diff --git a/packages/studio/src/components/editor/propertyPanelFlatStyleSections.test.tsx b/packages/studio/src/components/editor/propertyPanelFlatStyleSections.test.tsx
index 6b47c7ddb..936473245 100644
--- a/packages/studio/src/components/editor/propertyPanelFlatStyleSections.test.tsx
+++ b/packages/studio/src/components/editor/propertyPanelFlatStyleSections.test.tsx
@@ -277,3 +277,60 @@ describe("FlatStyleSection — Shadow and Blend", () => {
act(() => root.unmount());
});
});
+
+describe("FlatStyleSection — blur sliders", () => {
+ it("renders Layer blur and Backdrop sliders and commits through onSetStyle", () => {
+ const onSetStyle = vi.fn();
+ const host = document.createElement("div");
+ document.body.append(host);
+ const root = createRoot(host);
+ act(() => {
+ root.render(
+ ,
+ );
+ });
+ expect(host.textContent).toContain("Layer blur");
+ expect(host.textContent).toContain("Backdrop");
+ expect(host.textContent).toContain("4px");
+ const track = host.querySelectorAll('[data-flat-slider-track="true"]')[0];
+ Object.defineProperty(track, "getBoundingClientRect", {
+ value: () => ({ left: 0, width: 100, top: 0, height: 2, right: 100, bottom: 2 }),
+ });
+ act(() => {
+ track.dispatchEvent(new MouseEvent("pointerdown", { bubbles: true, clientX: 50 }));
+ });
+ // filterBlurValue=4 -> max=Math.max(40, 4)=40; clientX=50 of width 100 -> ratio 0.5 -> 20px.
+ expect(onSetStyle).toHaveBeenCalledWith("filter", "blur(20px)");
+ act(() => root.unmount());
+ });
+
+ it("renders the Backdrop slider from backdrop-filter and commits a new blur value on track click", () => {
+ const { host, root, onSetStyle } = renderSection({ "backdrop-filter": "blur(6px)" });
+ expect(host.textContent).toContain("Backdrop");
+ expect(host.textContent).toContain("6px");
+ const tracks = host.querySelectorAll('[data-flat-slider-track="true"]');
+ const backdropTrack = tracks[tracks.length - 1];
+ Object.defineProperty(backdropTrack, "getBoundingClientRect", {
+ value: () => ({ left: 0, width: 100, top: 0, height: 2, right: 100, bottom: 2 }),
+ });
+ act(() => {
+ backdropTrack.dispatchEvent(new MouseEvent("pointerdown", { bubbles: true, clientX: 50 }));
+ });
+ // backdropBlurValue=6 -> max=Math.max(60, 6)=60; clientX=50 of width 100 -> ratio 0.5 -> 30px.
+ expect(onSetStyle).toHaveBeenCalledWith("backdrop-filter", "blur(30px)");
+ act(() => root.unmount());
+ });
+
+ it("does not render a fill/knob highlight for a zero-value blur (default tier)", () => {
+ const { host, root } = renderSection({});
+ expect(host.querySelectorAll('[data-flat-slider-fill="true"]')).toHaveLength(0);
+ act(() => root.unmount());
+ });
+});
diff --git a/packages/studio/src/components/editor/propertyPanelFlatStyleSections.tsx b/packages/studio/src/components/editor/propertyPanelFlatStyleSections.tsx
index b2f45548d..ca9241cba 100644
--- a/packages/studio/src/components/editor/propertyPanelFlatStyleSections.tsx
+++ b/packages/studio/src/components/editor/propertyPanelFlatStyleSections.tsx
@@ -12,13 +12,20 @@ import {
extractBackgroundImageUrl,
formatNumericValue,
formatPxMetricValue,
+ getCssFilterFunctionPx,
inferBoxShadowPreset,
normalizePanelPxValue,
parseNumericValue,
parsePxMetricValue,
+ setCssFilterFunctionPx,
type BoxShadowPreset,
} from "./propertyPanelHelpers";
-import { FlatRow, FlatSegmentedRow, FlatSelectRow } from "./propertyPanelFlatPrimitives";
+import {
+ FlatRow,
+ FlatSegmentedRow,
+ FlatSelectRow,
+ FlatSlider,
+} from "./propertyPanelFlatPrimitives";
import { resolveValueTier } from "./propertyPanelValueTier";
import { ColorField } from "./propertyPanelColor";
import { GradientField, ImageFillField } from "./propertyPanelFill";
@@ -317,6 +324,55 @@ function FlatShadowBlendRows({
);
}
+/* ------------------------------------------------------------------ */
+/* Flat Layer blur + Backdrop sliders */
+/* ------------------------------------------------------------------ */
+
+function FlatBlurSliders({
+ styles,
+ disabled,
+ onSetStyle,
+}: {
+ styles: Record;
+ disabled: boolean;
+ onSetStyle: (prop: string, value: string) => void | Promise;
+}) {
+ const filterBlurValue = getCssFilterFunctionPx(styles.filter, "blur");
+ const backdropBlurValue = getCssFilterFunctionPx(styles["backdrop-filter"], "blur");
+
+ return (
+ <>
+ 0 ? "explicitCustom" : "default"}
+ displayValue={`${formatNumericValue(filterBlurValue)}px`}
+ disabled={disabled}
+ onCommit={(next) =>
+ void onSetStyle("filter", setCssFilterFunctionPx(styles.filter, "blur", next))
+ }
+ />
+ 0 ? "explicitCustom" : "default"}
+ displayValue={`${formatNumericValue(backdropBlurValue)}px`}
+ disabled={disabled}
+ onCommit={(next) =>
+ void onSetStyle(
+ "backdrop-filter",
+ setCssFilterFunctionPx(styles["backdrop-filter"], "blur", next),
+ )
+ }
+ />
+ >
+ );
+}
+
export function FlatStyleSection({
projectId,
element,
@@ -357,6 +413,7 @@ export function FlatStyleSection({
disabled={styleEditingDisabled}
onSetStyle={onSetStyle}
/>
+
);
}