funky-shadow

A React component that renders dithered, gradient-colored shadows behind any element. Under the hood it draws to a hidden canvas using Bayer matrix thresholding and Oklab color interpolation, then composites the result behind your content.

$npm install funky-shadow

Zero runtime dependencies. React 18+ is an optional peer dep if you want the component.

Quick start

Import the FunkyShadow component and wrap the element you want to shadow. You need to tell it the element's width, height, and border radius so it can size the canvas correctly. Everything else has sensible defaults.

Card.tsx
import { FunkyShadow } from 'funky-shadow'

function Card() {
  return (
    <FunkyShadow
      width={280}
      height={180}
      radius={16}
      preset="galaxy"
      pixelScale={3}
      offsetY={20}
      blur={30}
      opacity={0.7}
    >
      <div style={{
        width: 280,
        height: 180,
        borderRadius: 16,
        background: '#fff',
      }}>
        Your content here
      </div>
    </FunkyShadow>
  )
}

Presets

The package ships with 12 built-in gradient palettes. Pass a preset name as a string or use a numeric index. Each preset defines a series of RGB color stops that the renderer interpolates through. You can also define your own stops with the colors prop.

CRT Night
Thermal
Plasma
Deep Scan
Sunset Strip
Vaporwave
Galaxy
Magma
Ocean
Synthwave
Forest
Noir

Pixel scale

The pixelScale prop controls the size of each rendered dot. At 1 you get a smooth gradient. At 3 or 4 the individual pixels become visible and you start getting that retro texture. Higher values render faster because the canvas is smaller, so cranking it up is also a free performance win.

3px
Pixel scale

Dithering

Dithering uses a Bayer matrix to break up color banding. The matrix size determines how fine the dithering pattern is. 4x4 is the default and works well for most sizes. Use 8x8 for larger elements where you want finer grain, or turn it off entirely if you prefer clean color bands.

Bayer 4x4
Matrix size

Shape

The gradient can be radial, spreading outward from the center, or linear along an angle you control. Radial tends to look more like a natural light source. Linear is useful when you want the color to sweep in a specific direction across the shadow.

radial
Gradient shape

Custom colors

If none of the presets fit, pass an array of RGB triplets to the colors prop. The renderer interpolates between them using Oklab color space by default, which produces smoother transitions than sRGB, especially through yellows and cyans. Set oklab={false} to fall back to sRGB interpolation.

CustomColors.tsx
<FunkyShadow
  width={200}
  height={140}
  radius={12}
  colors={[
    [255, 0, 100],   // hot pink
    [0, 200, 255],   // cyan
    [255, 255, 0],   // yellow
  ]}
  pixelScale={3}
  offsetY={20}
  blur={30}
>
  {children}
</FunkyShadow>

Canvas API

If you are not using React, or you want to manage the canvas yourself, import renderShadow directly. It takes a canvas element, the element dimensions, border radius, and the same config object the React component uses. It draws the shadow and returns immediately. Call it again whenever the parameters change.

vanilla.js
import { renderShadow } from 'funky-shadow'

const canvas = document.querySelector('canvas')

renderShadow(canvas, 280, 180, 16, {
  preset: 'galaxy',
  pixelScale: 3,
  offsetY: 20,
  blur: 30,
  opacity: 0.7,
})

Props

Everything the FunkyShadow component accepts. Only width and height are required. Everything else falls back to sensible defaults.

PropTypeDefaultDescription
width*numberWidth of the element in px
height*numberHeight of the element in px
radiusnumber0Border radius
presetstring | number'galaxy'Gradient preset name or index
colors[r,g,b][]Custom RGB stops (overrides preset)
pixelScalenumber3Dot size, higher = chunkier
dither'8x8'|'4x4'|'2x2'|'off''4x4'Bayer matrix size
shape'line'|'radial''radial'Gradient shape
anglenumber45Angle in degrees (line mode)
offsetXnumber-15Shadow X offset
offsetYnumber20Shadow Y offset
blurnumber30Shadow blur radius
opacitynumber0.7Shadow opacity (0–1)
spreadnumber120Gradient spread (0–200)
oklabbooleantrueOklab color interpolation
contrastnumber130Contrast (50–200)
brightnessnumber0Brightness (-50–50)
quantLevelsnumber4Quantization levels (2–7)
classNamestringWrapper class name
styleCSSPropertiesWrapper styles
childrenReactNodeContent above the shadow