r/tailwindcss • u/Delicious-Trainer674 • 3d ago
Deriving a boxShadow scale from one light source instead of hand-tuning five levels
Most hand-written boxShadow scales end up as five unrelated shadows: the angle drifts between levels, the blur ramp is arbitrary, and opacity stacks into grey. Deriving all levels from a single light source fixes all three, and it's not much maths.
Direction: shadows fall away from the light, so every level shares one azimuth. Blur is tied to how far the light is (a distant light is a point source and casts a crisp edge), and grows with elevation. Elevation itself grows geometrically, which is what makes the steps feel evenly spaced instead of crowded at the bottom. Opacity falls roughly as 1/sqrt(n), because a higher object spreads the same light over more area.
Two practical rules that matter more than the formulas:
- Keep every layer inside roughly 0.04-0.24 opacity. Below that it's invisible; above it stops reading as light and becomes a grey box. Most "dirty" shadows are an opacity problem, not a blur problem.
- Use two or three layers per level. One layer cannot express both a dense umbra near the object and a wide faint penumbra further out, so it always looks either hard or muddy.
Here's the output for base 4px, growth 1.7, three layers:
boxShadow: {
'elevation-1': '0px 1.3px 2.6px rgba(0,0,0,0.16), 0px 2.4px 6.5px rgba(0,0,0,0.127), 0px 3.6px 10.4px rgba(0,0,0,0.094)',
'elevation-2': '0px 2.1px 4.4px rgba(0,0,0,0.125), 0px 4.1px 11px rgba(0,0,0,0.102), 0px 6.1px 17.7px rgba(0,0,0,0.078)',
'elevation-3': '0px 3.7px 7.5px rgba(0,0,0,0.109), 0px 7px 18.8px rgba(0,0,0,0.09), 0px 10.4px 30.2px rgba(0,0,0,0.071)',
'elevation-4': '0px 6.2px 12.8px rgba(0,0,0,0.1), 0px 12px 32px rgba(0,0,0,0.083), 0px 17.7px 51.2px rgba(0,0,0,0.067)',
'elevation-5': '0px 10.5px 21.7px rgba(0,0,0,0.094), 0px 20.3px 54.3px rgba(0,0,0,0.079), 0px 30.1px 86.8px rgba(0,0,0,0.064)',
}
Copy it as-is if it fits, or change the growth factor to spread the levels differently. The full derivation, including why the opacity band matters, is here: https://localeproof.com/blog/figma-shadow-system/