For two years, every card on every project I shipped had a box-shadow. 0 4px 16px rgba(0,0,0,0.08) was the default. Then I noticed something: the shadows were doing nothing the borders weren’t already doing — and the borders were doing it more cheaply.
The case for a 1-pixel border
A 1-pixel border is:
- Cheaper to render than a blur. Browsers can’t cheaply blur large areas at 60fps.
- Honest about hierarchy. It says “this is a container” without competing with the content inside.
- Accessible by default. No contrast issues against any background the user can choose.
A shadow, by contrast, implies depth — and depth implies something physical, like a card lifted off the page. Most of the time, that metaphor is wrong. The card isn’t floating. It’s just a different region.
When shadows still earn their keep
I’m not a purist. Shadows are right when:
- The element genuinely has elevation — a popover, a modal, a dragged handle.
- The shadow is the only signal separating the element from its surroundings (over a busy background).
- You’re showing interaction state — a button being pressed, a card being dragged.
For static content cards on a quiet background? Border. Always border.
The harder part: saying no to yourself
The reason this was hard wasn’t technical. It was aesthetic. Shadows look nice. They feel finished. But the job of a card is to hold content, not to perform. The content is the performance.