diff --git a/packages/ui/src/index.ts b/packages/ui/src/index.ts
index 30e17913..f7356cf6 100644
--- a/packages/ui/src/index.ts
+++ b/packages/ui/src/index.ts
@@ -14,6 +14,7 @@ export * from './lazy-image'
export * from './mobile-tab'
export * from './modal'
export * from './portal'
+export * from './progressive-blur'
export * from './prompts'
export * from './scroll-areas'
export * from './segment'
diff --git a/packages/ui/src/progressive-blur/index.tsx b/packages/ui/src/progressive-blur/index.tsx
new file mode 100644
index 00000000..9341097d
--- /dev/null
+++ b/packages/ui/src/progressive-blur/index.tsx
@@ -0,0 +1,80 @@
+import * as React from 'react'
+
+interface LinearBlurProps extends React.HTMLAttributes
{
+ strength?: number
+ steps?: number
+ falloffPercentage?: number
+ tint?: string
+ side?: 'left' | 'right' | 'top' | 'bottom'
+}
+
+const oppositeSide = {
+ left: 'right',
+ right: 'left',
+ top: 'bottom',
+ bottom: 'top',
+}
+
+export function LinearBlur({
+ strength = 64,
+ steps = 8,
+ falloffPercentage = 100,
+ tint = 'transparent',
+ side = 'top',
+ ...props
+}: LinearBlurProps) {
+ const actualSteps = Math.max(1, steps)
+ const step = falloffPercentage / actualSteps
+
+ const factor = 0.5
+
+ const base = Math.pow(strength / factor, 1 / (actualSteps - 1))
+
+ const mainPercentage = 100 - falloffPercentage
+
+ const getBackdropFilter = (i: number) => `blur(${factor * base ** (actualSteps - i - 1)}px)`
+
+ return (
+
+
+ {/* Full blur at 100-falloffPercentage% */}
+ {actualSteps > 1 && (
+
+ )}
+ {actualSteps > 2 &&
+ Array.from({ length: actualSteps - 2 }).map((_, i) => (
+
+ ))}
+
+
+
+ )
+}