Shadowboxing
I've been a fan of Jakub Krehel's approach to shadows for a while. He uses a few layers of box-shadow where you'd normally use a border, and because shadows are transparent they work on any background colour, which borders don't. I've started noticing it everywhere since.
I started using it in my own work too. It's basically three shadow layers stacked up, one as an outline, one that adds some direction and depth, and a softer one underneath for a bit of atmosphere.
The light
At some point I started wondering what would happen if you could move the light, and use the cursor as the light itself.
Each element registers its centre with a light engine. Every frame the engine works out the direction from the cursor to each element, then offsets and scales the shadow layers to match. Elements closer to the light get slightly sharper shadows, which is kind of what Jakub does on hover, just continuous.
const LAYERS = [
{ offset: 0, blur: 0, spread: 1, opacityScale: 1 }, // outline
{ offset: 1, blur: 2, spread: -1, opacityScale: 1 }, // directional
{ offset: 2, blur: 4, spread: 0, opacityScale: 0.66 }, // ambient
];
// Vector from light to element — shadow casts in this direction
const dx = elCx - light.x;
const dy = elCy - light.y;
const dist = Math.sqrt(dx * dx + dy * dy);
// Closer to light = slightly sharper
const proximity = 1 / (1 + dist * 1.5);The engine runs on requestAnimationFrame and writes straight to the DOM, so React never re-renders when the mouse moves. Each element that gets a shadow just registers a ref and the engine handles the rest.
Try it
Move your mouse around to move the light. In the controls panel you can change the shadow intensity, opacity, spread and colours, and switch on an inset, all live.
Posted on 23 March 2026
Permalink, All Playground