—CSS Series · Post 12: Shadows, Depth & Advanced Selectors

Shadows, Depth & Advanced Selectors — CodeHerWay
CSS Series · Post 12

Shadows, Depth
& Advanced Selectors

The details that separate "built it" from "designed it." Layered shadows, pseudo-elements that don't need HTML, and precision selectors that target exactly the right element.

CSS Beginner Series: Post 12 ~12 min read

💬 "It Looks Flat"

Your layout is solid. Your typography is clean. But the page feels two-dimensional — everything is on the same level, same weight, same presence. Nothing feels like it floats or recedes.

That's a depth problem. Depth in CSS comes from shadows, layering, and intentional use of space. This post covers the tools that create visual hierarchy without changing your layout at all.

🧱 box-shadow

Box shadows add depth below or around an element. The full syntax has five values: horizontal offset, vertical offset, blur radius, spread radius, and color. Most shadows use zero horizontal offset (centered), positive vertical offset (cast downward), and a semi-transparent dark color.

no shadow

subtle

soft

deep

color glow

layered

styles.css
CSS · box-shadow
/* box-shadow: x-offset y-offset blur spread color */ /* Subtle — barely lifts the element */ .card-subtle { box-shadow: 0 2px 8px rgba(0,0,0,0.3); } /* Soft — standard card shadow */ .card-soft { box-shadow: 0 8px 24px rgba(0,0,0,0.4); } /* Deep — elevated, prominent */ .card-deep { box-shadow: 0 16px 48px rgba(0,0,0,0.6); } /* Glow — colored light source */ .card-glow { box-shadow: 0 0 24px rgba(255,105,180,0.25); } /* Inset — shadow inside the element */ .input-focused { box-shadow: inset 0 0 0 2px #b44aff; } /* Layered — multiple shadows for realism */ .card-layered { box-shadow: 0 1px 3px rgba(0,0,0,0.3), /* close, sharp */ 0 8px 24px rgba(0,0,0,0.4), /* medium, soft */ 0 0 0 1px rgba(255,255,255,0.04); /* inner border */ }
✦ The Layered Shadow Trick

Real-world shadows have multiple layers — a close sharp one near the object and a diffuse soft one further away. Combine two or three box-shadows with different blur values and you get depth that feels physical rather than flat. The inner 0 0 0 1px adds a subtle border that doesn't affect layout.

🧱 ::before and ::after — Pseudo-Elements

Pseudo-elements let you insert content before or after an element's content using pure CSS — no extra HTML needed. They're positioned like normal elements and can be styled however you want. Every element gets two free slots: ::before and ::after. The only requirement: they must have a content property (even if it's content: "").

Live pseudo-element examples
This paragraph has a pink left border from ::before — no extra HTML element.
The best way to understand CSS is to break things intentionally and then fix them.
First step in the process
Second step in the process
Third step in the process
styles.css
CSS · ::before and ::after
/* Left accent bar — no extra HTML */ .accent-line { position: relative; padding-left: 20px; } .accent-line::before { content: ""; /* required — even if empty */ position: absolute; left: 0; top: 4px; width: 3px; height: calc(100% - 8px); background: #ff69b4; border-radius: 2px; } /* Decorative quote mark */ .quote::before { content: '"'; /* actual text content */ font-size: 4rem; color: rgba(180,74,255,0.3); position: absolute; top: -8px; left: 0; } /* Add text after a link */ .external-link::after { content: ' ↗'; font-size: 0.8em; opacity: 0.6; }

🧱 Advanced Selectors

Child Selector ( > )

styles.css
CSS · > child selector
/* .parent p — targets ALL p descendants (nested too) */ .parent p { color: pink; } /* .parent > p — targets ONLY direct p children */ .parent > p { color: pink; } /* Useful for nested components that share element names */ .nav > .nav-item { font-weight: 600; } /* direct items only */

Attribute Selectors

styles.css
CSS · attribute selectors
/* [attr] — has the attribute at all */ input[required] { border-color: #ff69b4; } /* [attr="value"] — exact match */ input[type="email"] { padding-right: 40px; } /* [attr^="value"] — starts with */ a[href^="https"] { color: var(--mint-green); } /* secure links */ /* [attr$="value"] — ends with */ a[href$=".pdf"] { } /* PDF links */ /* [attr*="value"] — contains */ a[href*="github"] { } /* any GitHub link */

:nth-child() and Structural Pseudo-Classes

styles.css
CSS · structural pseudo-classes
/* :first-child / :last-child */ .list-item:first-child { border-top: none; } .list-item:last-child { border-bottom: none; } /* :nth-child(n) — every n-th element */ .table-row:nth-child(even) { background: rgba(255,255,255,0.02); } .table-row:nth-child(odd) { background: transparent; } .feature:nth-child(3n) { border-color: var(--pink); } /* every 3rd */ /* :not() — exclude specific elements */ .card:not(.card-disabled) { cursor: pointer; } p:not(.lead) { font-size: 1rem; } /* :focus-visible — keyboard focus only (accessibility) */ .button:focus-visible { outline: 2px solid #b44aff; outline-offset: 3px; }
🛠

Mini Build: Cards with Depth + Decorative ::before

Add layered shadows to your card component, and use ::before to create a decorative accent bar — no extra HTML.

01

Add layered shadows to the card. Three-layer shadow for realistic depth, with a colored glow on hover.

styles.css
CSS
.card { position: relative; /* needed for ::before */ background: #12121a; border-radius: 12px; padding: 28px; overflow: hidden; /* clips the ::before accent */ box-shadow: 0 1px 3px rgba(0,0,0,0.3), 0 8px 24px rgba(0,0,0,0.4), 0 0 0 1px rgba(255,255,255,0.04); transition: box-shadow 0.3s ease, transform 0.3s ease; } .card:hover { transform: translateY(-4px); box-shadow: 0 4px 8px rgba(0,0,0,0.3), 0 16px 40px rgba(0,0,0,0.5), 0 0 0 1px rgba(255,105,180,0.2), 0 0 40px rgba(255,105,180,0.08); }
02

Add a top accent bar with ::before. A thin color bar across the top of the card — purely decorative, zero HTML.

styles.css
CSS
.card::before { content: ""; position: absolute; top: 0; left: 0; right: 0; height: 2px; background: var(--gradient-header); opacity: 0; transition: opacity 0.3s ease; } .card:hover::before { opacity: 1; /* bar appears on hover */ } /* Now every .card gets this effect with no extra HTML */

💛 You're Building Things That Look Designed

Shadows, pseudo-elements, and precise selectors are the finishing layer. They're what makes someone look at your project and think "this looks polished" without being able to articulate exactly why.

  • box-shadow takes x, y, blur, spread, and color — stack multiple for realism
  • Colored glows use transparent RGBA with no y-offset and high blur
  • ::before and ::after insert CSS-only decorative content — always need content: ""
  • Pseudo-elements need position: absolute and a position: relative parent to be placed precisely
  • Attribute selectors target elements by their HTML attributes — great for forms
  • :nth-child() targets structural positions — alternating rows, every 3rd item
  • :not() excludes elements from a rule cleanly

Next: Clean CSS Architecture — organizing your stylesheet, naming conventions, BEM basics, and debugging with DevTools. This is the post that makes your CSS maintainable long-term.

🧾 DevFession

I used to add extra <div> elements just for decorative purposes. A thin line above a card, a colored dot before a list item, a quote mark before a blockquote — all had their own HTML elements, sitting in the markup doing nothing structural.

Then I learned ::before and ::after. Every one of those decorative elements disappeared from the HTML. The markup got cleaner, the CSS got more expressive, and I stopped feeling like I was polluting the structure with presentation.

If you have an element whose sole purpose is to look like something, it's probably a pseudo-element waiting to be freed from the HTML.

Previous
Previous

—CSS Series · Post 13: Clean CSS Architecture & DevTools

Next
Next

—CSS Series · Post 09: Responsive Design