Skip to content

Backgrounds, Borders, and Shadows

beginner11 min read

The Visual Surface of Every Element

Backgrounds, borders, and shadows form the visual surface that gives UI elements depth, hierarchy, and personality. They seem simple -- until you discover that box-shadow can tank your scroll performance, gradients can replace images entirely, and multiple backgrounds can eliminate wrapper divs you thought you needed.

Master these properties and you'll ship fewer DOM elements, smaller payloads, and smoother rendering.

Mental Model

Think of an element as a stack of transparencies on an overhead projector. The lowest layer is the background-color, above it are background-image layers (stacked first-listed on top), then the border, and finally the box-shadow floats outside (or inside with inset). Each layer composites over the one below it. Understanding this stacking order is essential when combining multiple backgrounds and borders.

Multiple Backgrounds

CSS supports multiple background layers, separated by commas. The first listed is on top:

.hero {
  background:
    /* Layer 3 (top): semi-transparent overlay */
    linear-gradient(rgba(0, 0, 0, 0.4), rgba(0, 0, 0, 0.7)),
    /* Layer 2 (middle): decorative pattern */
    url('/pattern.svg') repeat center / 200px,
    /* Layer 1 (bottom): hero image */
    url('/hero.jpg') no-repeat center / cover;
}

Each layer can have its own position, size, repeat, and origin:

.card {
  background-image: url('/icon.svg'), linear-gradient(to bottom, white, #f5f5f5);
  background-position: 1rem center, center;
  background-size: 24px 24px, 100%;
  background-repeat: no-repeat, no-repeat;
}

Gradient Techniques for Production UI

Linear Gradients

/* Hard color stop — creates a sharp line, not a fade */
.divider {
  background: linear-gradient(
    to right,
    transparent 0%,
    #e5e7eb 10%,
    #e5e7eb 90%,
    transparent 100%
  );
  height: 1px;
}

/* Stripe pattern — no images needed */
.stripes {
  background: repeating-linear-gradient(
    45deg,
    #f0f0f0 0px,
    #f0f0f0 10px,
    #ffffff 10px,
    #ffffff 20px
  );
}

Radial Gradients

/* Spotlight effect */
.spotlight {
  background: radial-gradient(
    ellipse at 30% 20%,
    rgba(255, 255, 255, 0.2) 0%,
    transparent 50%
  );
}

/* Dot pattern */
.dots {
  background: radial-gradient(circle, #333 1px, transparent 1px);
  background-size: 20px 20px;
}

Conic Gradients

/* Pie chart — pure CSS */
.pie {
  background: conic-gradient(
    #3b82f6 0% 35%,
    #ef4444 35% 60%,
    #22c55e 60% 100%
  );
  border-radius: 50%;
  width: 200px;
  height: 200px;
}

/* Color wheel */
.wheel {
  background: conic-gradient(red, yellow, lime, aqua, blue, magenta, red);
  border-radius: 50%;
}

Border Techniques

border-radius Subtleties

/* Pill shape — use a large value, not 50% */
.pill { border-radius: 9999px; }

/* 50% on a non-square element creates an ellipse, not a circle */
.card { border-radius: 50%; /* Ellipse if width ≠ height */ }

/* Asymmetric corners */
.card {
  border-radius: 16px 16px 4px 4px; /* Rounded top, sharp bottom */
}

/* Nested border-radius: inner radius = outer - border-width */
.outer {
  border-radius: 16px;
  border: 4px solid black;
  /* Inner content corners are effectively 12px */
}

Borders That Don't Affect Layout

/* outline doesn't affect layout or box model */
.focused { outline: 2px solid blue; outline-offset: 2px; }

/* box-shadow as a border — no layout impact */
.card { box-shadow: inset 0 0 0 2px #e5e7eb; }

/* Border that doesn't shift content */
.tab {
  border-bottom: 3px solid transparent;
}
.tab.active {
  border-bottom-color: blue;
  /* No layout shift because border was always there */
}
Common Trap

outline now follows border-radius in modern browsers (Chrome 94+, Firefox 88+, Safari 16.4+). But if you need to support older browsers, or want more control, box-shadow is a reliable alternative: box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.5);. This respects border-radius everywhere and doesn't affect layout.

box-shadow: Technique and Performance

This property is more powerful than most developers realize -- and more dangerous.

Multiple Shadows for Depth

/* Layered shadows for realistic elevation (Material Design style) */
.card-elevated {
  box-shadow:
    0 1px 2px rgba(0, 0, 0, 0.05),
    0 4px 8px rgba(0, 0, 0, 0.08),
    0 12px 24px rgba(0, 0, 0, 0.06);
}

/* Inner glow */
.input:focus {
  box-shadow:
    0 0 0 3px rgba(59, 130, 246, 0.3),  /* Focus ring */
    inset 0 1px 2px rgba(0, 0, 0, 0.08); /* Subtle inner shadow */
}

box-shadow Performance

Here's the thing nobody tells you until you've already shipped a janky page: box-shadow is painted on the main thread. During scroll or animation, it can cause expensive repaints:

/* Slow: Animating box-shadow directly */
.card:hover {
  box-shadow: 0 20px 40px rgba(0, 0, 0, 0.3); /* Repaint every frame */
  transition: box-shadow 0.3s; /* Forces repaint during transition */
}

/* Fast: Use a pseudo-element with opacity */
.card {
  position: relative;
}
.card::after {
  content: '';
  position: absolute;
  inset: 0;
  border-radius: inherit;
  box-shadow: 0 20px 40px rgba(0, 0, 0, 0.3);
  opacity: 0;
  transition: opacity 0.3s; /* Composited — GPU accelerated */
  pointer-events: none;
}
.card:hover::after {
  opacity: 1;
}
Why opacity is faster than box-shadow animation

opacity is a compositing property — the browser can change it on the GPU without repainting the element. box-shadow requires the browser to repaint the element's entire shadow layer on the CPU for every frame. For a list of 50 cards all animating box-shadow simultaneously, this difference is the gap between smooth 60fps and janky scroll.

filter vs box-shadow for Drop Shadows

These look similar but behave very differently.

/* box-shadow: rectangle behind the box */
.img-box { box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); }

/* filter: drop-shadow follows the element's shape */
.img-transparent { filter: drop-shadow(0 4px 12px rgba(0, 0, 0, 0.15)); }

filter: drop-shadow() follows the alpha channel of the element — it wraps around transparent PNGs, SVGs, and irregular shapes. box-shadow always draws behind the bounding rectangle.

Execution Trace
Background color
Lowest layer: solid color fills the padding box
background-color: white
Background images
Stack images bottom to top (last listed is lowest)
Gradients count as images
Border
Drawn on top of background
border sits on the edge of the padding box
Box shadow
Drawn outside the border (or inside with inset)
Does not affect layout — purely visual
Outline
Drawn outside box-shadow
Does not affect layout. Follows border-radius in modern browsers (Chrome 94+, Firefox 88+, Safari 16.4+)
What developers doWhat they should do
Animating box-shadow directly for hover effects
box-shadow animation triggers CPU repaints every frame. Opacity is GPU-composited and far cheaper.
Pre-render the shadow on a pseudo-element and animate its opacity
Using box-shadow on transparent images expecting it to follow the shape
box-shadow always draws behind the bounding rectangle, not the visible shape
Use filter: drop-shadow() for shape-conforming shadows
Using border for focus indicators (causes layout shift)
Adding/removing a border changes the element's size. Outline and box-shadow don't.
Use outline or box-shadow for focus indicators — no layout impact
Creating image assets for simple gradients and patterns
CSS gradients have zero HTTP requests, are resolution-independent, and are easy to modify
Use CSS gradients — linear, radial, conic, and repeating variants
Quiz
You animate box-shadow on 50 cards during scroll. Performance is poor. What's the best fix?
Quiz
In a multiple background declaration, which layer is visually on top?
Quiz
You need a focus ring that follows border-radius and doesn't affect layout. Which approach works?
Key Rules
  1. 1Multiple backgrounds stack first-listed on top — use commas to layer
  2. 2CSS gradients eliminate image assets — use them for overlays, patterns, and decorative effects
  3. 3Never animate box-shadow directly — use the pseudo-element + opacity technique
  4. 4Use filter: drop-shadow for shadows that follow transparent/irregular shapes
  5. 5Use box-shadow or outline for focus indicators — borders cause layout shift