Redact
Covers one or more rectangles with an opaque fill and a blur or pixelate effect on top. The opaque fill always renders, so the region is covered even if the effect layer doesn't.
Installation
npx shadcn add https://www.reelcn.dev/r/redact.jsonFiles land in src/reelcn/ with the items it builds on. Dependencies: remotion.
Usage
<BrowserWindow url="app.dev">
<Dashboard />
<Redact rects={[{ x: 62, y: 18, width: 22, height: 6 }]} mode="pixelate" />
</BrowserWindow>Props
PropTypeDescription
delaynumberFrames to wait before entering.
durationnumberEnter duration in frames. Defaults to 0.6s. Some items reuse
duration for an unrelated local timing. See that item's own prop docs.exitnumber | booleanExit at the end of the parent `<Sequence>`.
false keeps it on screen, a number sets the exit length in frames.motionMotionPersonalityOverride the theme's motion personality.
posterbooleanRenders fully entered, with no exit: for a still frame or thumbnail. Defaults to
false.holdFramesnumberDelays the start of the exit window by this many frames, for a minimum on-screen hold. Defaults to
0.rects*CalloutRect[]Areas to cover, in % of canvas, same shape
callout's target uses. At most 8 (`redactRectBudget`).mode"blur" | "pixelate" Default
"blur".cellSizenumberBlur radius, or pixelate cell size, in design units. Default
18.styleCSSProperties
classNamestring
Built from
Use
- Hiding an API key, email or account number in a real screenshot or screen recording
- Covering a sensitive panel in a dashboard demo without cropping it out
Avoid
- A region that should stay fully visible. This always covers, never partially
Guides
Components take their colors from a theme, their timing from motion props and their layout from the canvas.
Source
registry/items/redact.tsx
/**
* @title Redact
* @category product
* @description Covers one or more rectangles with an opaque fill and a blur or pixelate effect on top. The opaque fill always renders, so the region is covered even if the effect layer doesn't.
* @duration 1
* @use Hiding an API key, email or account number in a real screenshot or screen recording
* @use Covering a sensitive panel in a dashboard demo without cropping it out
* @avoid A region that should stay fully visible. This always covers, never partially
* @tags redact, blur, pixelate, privacy, censor
* @example
* <BrowserWindow url="app.dev">
* <Dashboard />
* <Redact rects={[{ x: 62, y: 18, width: 22, height: 6 }]} mode="pixelate" />
* </BrowserWindow>
*/
import type React from "react";
import { useId } from "react";
import { AbsoluteFill } from "remotion";
import type { CalloutRect } from "./callout";
import { type MotionProps, redactRectBudget, useMotion, useTheme, useViewport } from "./core";
export type RedactProps = MotionProps & {
/** Areas to cover, in % of canvas, same shape `callout`'s `target` uses. At most 8 (`redactRectBudget`). */
rects: CalloutRect[];
mode?: "pixelate" | "blur";
/** Blur radius, or pixelate cell size, in design units. */
cellSize?: number;
style?: React.CSSProperties;
className?: string;
};
export function Redact({ rects, mode = "blur", cellSize = 18, style, className, ...motion }: RedactProps) {
redactRectBudget(rects.length);
const theme = useTheme();
const { u, width, height } = useViewport();
const m = useMotion(motion);
const filterId = `redact-pixelate-${useId().replace(/[^a-zA-Z0-9_-]/g, "")}`;
const cellPx = u(cellSize);
return (
<AbsoluteFill className={className} style={{ pointerEvents: "none", opacity: m.presence, ...style }}>
{mode === "pixelate" ? (
<svg aria-hidden="true" width={0} height={0} style={{ position: "absolute" }}>
<defs>
<filter id={filterId} x="-20%" y="-20%" width="140%" height="140%">
<feFlood x="0" y="0" width={cellPx} height={cellPx} />
<feComposite width={cellPx * 2} height={cellPx * 2} />
<feTile result="grid" />
<feComposite in="SourceGraphic" in2="grid" operator="in" />
<feMorphology operator="dilate" radius={cellPx / 2} />
</filter>
</defs>
</svg>
) : null}
{rects.map((rect, i) => (
<div
key={i}
style={{
position: "absolute",
left: (rect.x / 100) * width,
top: (rect.y / 100) * height,
width: (rect.width / 100) * width,
height: (rect.height / 100) * height,
overflow: "hidden",
}}
>
{/* Fail-opaque base: always rendered, always fully opaque. See Deviation 6 for the z-order spike
this needs before the effect layer below can be trusted to show real content through it. */}
<div style={{ position: "absolute", inset: 0, background: theme.colors.background }} />
<div
style={{
position: "absolute",
inset: 0,
backdropFilter: mode === "blur" ? `blur(${cellPx}px)` : `url(#${filterId})`,
WebkitBackdropFilter: mode === "blur" ? `blur(${cellPx}px)` : `url(#${filterId})`,
}}
/>
</div>
))}
</AbsoluteFill>
);
}