# Edge Blur

> Progressive edge blur for React and shadcn/ui that stacks masked backdrop-filter layers to fade content under headers and footers.

Source: https://www.cult-ui.com/docs/components/edge-blur

## Example

```tsx title="edge-blur-demo.tsx"
"use client"

import { BottomBlur, EdgeBlur, TopBlur } from "@/components/ui/edge-blur"

const SCROLL_PARAGRAPH_KEYS = [
  "s-a",
  "s-b",
  "s-c",
  "s-d",
  "s-e",
  "s-f",
  "s-g",
  "s-h",
  "s-i",
  "s-j",
  "s-k",
  "s-l",
] as const

const SMALL_ROW_KEYS = [
  "b-1",
  "b-2",
  "b-3",
  "b-4",
  "b-5",
  "b-6",
  "b-7",
  "b-8",
] as const

export default function EdgeBlurDemo() {
  return (
    <div className="flex flex-col items-center gap-10 rounded-md px-4 py-6 md:px-0 dark:bg-stone-950">
      <div className="max-w-xl space-y-2 text-center">
        <h2 className="text-foreground text-2xl font-bold tracking-tight">
          Edge blur
        </h2>
        <p className="text-muted-foreground text-sm">
          Stacked backdrop-blur layers with a gradient mask. Blurs sit{" "}
          <em>outside</em> the scroll layer so they stay pinned to the frame;
          only the inner panel scrolls.{" "}
          <code className="bg-muted rounded px-1 py-0.5 text-xs">
            transform-gpu
          </code>{" "}
          on the frame keeps{" "}
          <code className="bg-muted rounded px-1 py-0.5 text-xs">fixed</code>{" "}
          blurs inside this preview instead of the viewport.
        </p>
      </div>

      <div className="bg-muted/30 relative isolate h-[420px] w-full max-w-lg transform-gpu overflow-hidden rounded-xl border shadow-sm">
        <div className="absolute inset-0 z-0 overflow-y-auto">
          <div className="from-background via-muted/40 to-background min-h-[700px] space-y-3 bg-gradient-to-b p-6 pt-8 pb-28">
            {SCROLL_PARAGRAPH_KEYS.map((key, i) => (
              <p
                key={key}
                className="text-foreground/90 text-sm leading-relaxed"
              >
                Paragraph {i + 1}. Layers use{" "}
                <code className="bg-background/80 rounded px-1 py-0.5 text-xs">
                  pointer-events-none
                </code>{" "}
                so the scroll container still receives wheel and touch events.
              </p>
            ))}
          </div>
        </div>
        <EdgeBlur position="top" height={72} />
        <EdgeBlur position="bottom" height={96} />
      </div>

      <div className="grid w-full max-w-2xl grid-cols-1 gap-4 sm:grid-cols-2">
        <div className="bg-card relative isolate h-44 transform-gpu overflow-hidden rounded-lg border">
          <section
            aria-label="Demo panel with bottom edge blur"
            className="text-muted-foreground absolute inset-0 z-0 overflow-y-auto p-4 text-xs"
          >
            {SMALL_ROW_KEYS.map((key, i) => (
              <p key={key} className="mb-2">
                Bottom blur row {i + 1}
              </p>
            ))}
          </section>
          <BottomBlur height={56} />
        </div>
        <div className="bg-card relative isolate h-44 transform-gpu overflow-hidden rounded-lg border">
          <section
            aria-label="Demo panel with top edge blur"
            className="text-muted-foreground absolute inset-0 z-0 overflow-y-auto p-4 pt-8 text-xs"
          >
            {SMALL_ROW_KEYS.map((key, i) => (
              <p key={`top-${key}`} className="mb-2">
                Top blur row {i + 1}
              </p>
            ))}
          </section>
          <TopBlur height={56} />
        </div>
      </div>
    </div>
  )
}
```

Edge Blur is a React component for shadcn/ui built on CSS backdrop-filter and mask-image. Use it under fixed headers, above bottom navigation, or at the edges of long scrolling pages.

## Installation

### CLI

```bash
npx shadcn@latest add @cult-ui/edge-blur
```

### Manual

**Copy and paste the following code into your project.**

```tsx title="edge-blur.tsx"
"use client"

interface EdgeBlurProps {
  position?: "top" | "bottom"
  height?: number
}

export function EdgeBlur({ position = "bottom", height = 75 }: EdgeBlurProps) {
  const blurLayers = [1, 2, 3, 6, 12]

  const isTop = position === "top"

  return (
    <div
      className={`pointer-events-none fixed inset-x-0 isolate z-40 ${isTop ? "top-0" : "bottom-0"}`}
      style={{ height }}
    >
      {blurLayers.map((blur) => (
        <div
          key={blur}
          className="absolute inset-0"
          style={{
            backdropFilter: `blur(${blur}px)`,
            WebkitBackdropFilter: `blur(${blur}px)`,
            maskImage: `linear-gradient(to ${isTop ? "bottom" : "top"}, black, transparent)`,
            WebkitMaskImage: `linear-gradient(to ${isTop ? "bottom" : "top"}, black, transparent)`,
          }}
        />
      ))}
    </div>
  )
}

// Convenience exports for specific positions
export function TopBlur({ height = 75 }: { height?: number }) {
  return <EdgeBlur position="top" height={height} />
}

export function BottomBlur({ height = 75 }: { height?: number }) {
  return <EdgeBlur position="bottom" height={height} />
}
```

**Update the import paths to match your project setup.**

## Usage

```tsx
import { BottomBlur, EdgeBlur, TopBlur } from "@/components/ui/edge-blur";
```

### Full viewport (typical)

Place near the root of your page or layout. The component uses `position: fixed` and spans the width of the viewport.

```tsx
export function PageChrome() {
  return (
    <>
      <main>{/* … */}</main>
      <BottomBlur height={80} />
    </>
  );
}
```

### Inside a card, modal, or preview

If an ancestor has a `transform`, `filter`, or `perspective` (other than `none`), `fixed` descendants are positioned relative to that ancestor instead of the viewport. Use that to contain the blur inside a rounded panel.

Put **`EdgeBlur` outside the scrolling element**. If scroll lives on the same parent as the blur, the blur becomes part of the scrollable overflow and will move with the content.

```tsx
<div className="relative isolate h-96 transform-gpu overflow-hidden rounded-xl border">
  <div className="absolute inset-0 overflow-y-auto">
    <div className="min-h-[800px] p-4">{/* long content */}</div>
  </div>
  <EdgeBlur position="bottom" height={72} />
</div>
```

### API

| Export       | Description                                     |
| ------------ | ----------------------------------------------- |
| `EdgeBlur`   | `position` `"top"` \| `"bottom"`, `height` (px) |
| `TopBlur`    | Top edge; optional `height`                     |
| `BottomBlur` | Bottom edge; optional `height`                  |

## Notes

- Uses repeated `backdrop-filter` layers with increasing blur radius and a CSS mask for a smooth falloff.
- Does not block pointer events (`pointer-events-none`).
