CSS Shadows & Glows
CSS provides two primary shadow properties: box-shadow for element shadows and text-shadow for text shadows. Both support multiple layered shadows, blur, spread, and color control. Shadows are essential for creating depth, emphasis, and visual hierarchy in interfaces.
Beyond simple drop shadows, CSS shadows can create glow effects, neon signs, inset shadows for depth, and long-shadow effects. The terminal aesthetic particularly relies on green glow effects — mimicking CRT monitors and terminal interfaces.
| 1 | .shadow { |
| 2 | box-shadow: 4px 4px 10px rgba(0, 0, 0, 0.5); |
| 3 | } |
| 4 | |
| 5 | .glow { |
| 6 | box-shadow: 0 0 20px rgba(0, 255, 65, 0.5); |
| 7 | } |
box-shadow adds shadow effects around an element's frame. The syntax is: inset? offset-x offset-y blur-radius spread-radius color. All values except offset-x and offset-y are optional.
| 1 | /* Syntax: box-shadow: [inset] <x> <y> <blur> <spread> <color> */ |
| 2 | |
| 3 | /* Simple drop shadow */ |
| 4 | .simple { |
| 5 | box-shadow: 4px 4px 10px rgba(0, 0, 0, 0.3); |
| 6 | } |
| 7 | |
| 8 | /* Blur only, no offset */ |
| 9 | .blur-only { |
| 10 | box-shadow: 0 0 20px rgba(0, 255, 65, 0.3); |
| 11 | } |
| 12 | |
| 13 | /* No blur */ |
| 14 | .hard { |
| 15 | box-shadow: 4px 4px 0 #00FF41; |
| 16 | } |
| 17 | |
| 18 | /* Negative offset */ |
| 19 | .negative { |
| 20 | box-shadow: -4px -4px 10px rgba(0, 0, 0, 0.3); |
| 21 | } |
| 22 | |
| 23 | /* All parameters */ |
| 24 | .full { |
| 25 | box-shadow: 2px 4px 8px 2px rgba(0, 0, 0, 0.4); |
| 26 | } |
| 27 | |
| 28 | /* Ambient glow */ |
| 29 | .ambient { |
| 30 | box-shadow: 0 0 30px rgba(0, 255, 65, 0.15); |
| 31 | } |
text-shadow adds shadows to text characters. The syntax is simpler — no spread or inset. It excels at depth, glow effects, and retro text styles.
| 1 | /* Basic text shadow */ |
| 2 | .simple { |
| 3 | text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.5); |
| 4 | } |
| 5 | |
| 6 | /* Retro hard shadow */ |
| 7 | .retro { |
| 8 | text-shadow: 3px 3px 0 #00FF41; |
| 9 | } |
| 10 | |
| 11 | /* Terminal glow */ |
| 12 | .terminal-glow { |
| 13 | text-shadow: 0 0 5px rgba(0, 255, 65, 0.5); |
| 14 | } |
| 15 | |
| 16 | /* Layered depth */ |
| 17 | .layered { |
| 18 | text-shadow: |
| 19 | 1px 1px 0 rgba(0, 0, 0, 0.3), |
| 20 | 2px 2px 0 rgba(0, 0, 0, 0.2), |
| 21 | 3px 3px 0 rgba(0, 0, 0, 0.1); |
| 22 | } |
| 23 | |
| 24 | /* Neon glow */ |
| 25 | .neon { |
| 26 | text-shadow: |
| 27 | 0 0 5px rgba(0, 255, 65, 0.5), |
| 28 | 0 0 10px rgba(0, 255, 65, 0.3), |
| 29 | 0 0 20px rgba(0, 255, 65, 0.2), |
| 30 | 0 0 40px rgba(0, 255, 65, 0.1); |
| 31 | } |
| 32 | |
| 33 | /* Embossed */ |
| 34 | .emboss { |
| 35 | color: #0D0D0D; |
| 36 | text-shadow: |
| 37 | 1px 1px 0 rgba(255, 255, 255, 0.1), |
| 38 | -1px -1px 0 rgba(0, 0, 0, 0.5); |
| 39 | } |
Both properties accept comma-separated shadow lists. Each shadow is layered front-to-back. Multiple shadows enable complex effects like dual glows, depth stacking, and pseudo-3D.
| 1 | /* Dual color glow */ |
| 2 | .dual-glow { |
| 3 | box-shadow: |
| 4 | 0 0 15px rgba(0, 255, 65, 0.4), |
| 5 | 0 0 30px rgba(59, 130, 246, 0.2); |
| 6 | } |
| 7 | |
| 8 | /* Inner + outer */ |
| 9 | .combined { |
| 10 | box-shadow: |
| 11 | inset 0 2px 4px rgba(0, 0, 0, 0.3), |
| 12 | 0 2px 8px rgba(0, 0, 0, 0.2); |
| 13 | } |
| 14 | |
| 15 | /* Elevation system */ |
| 16 | .elevation-1 { box-shadow: 0 1px 3px rgba(0,0,0,0.12), 0 1px 2px rgba(0,0,0,0.24); } |
| 17 | .elevation-2 { box-shadow: 0 3px 6px rgba(0,0,0,0.15), 0 2px 4px rgba(0,0,0,0.12); } |
| 18 | .elevation-3 { box-shadow: 0 10px 20px rgba(0,0,0,0.19), 0 6px 6px rgba(0,0,0,0.23); } |
| 19 | |
| 20 | /* Terminal window */ |
| 21 | .terminal-window { |
| 22 | box-shadow: |
| 23 | 0 0 0 1px rgba(0, 255, 65, 0.1), |
| 24 | 0 4px 24px rgba(0, 0, 0, 0.4), |
| 25 | 0 0 60px rgba(0, 255, 65, 0.05); |
| 26 | } |
blur-radius controls softness (higher = softer). spread-radius (box-shadow only) expands or contracts the shadow. Positive spread enlarges the shadow; negative shrinks it.
| 1 | /* Blur variations */ |
| 2 | .no-blur { box-shadow: 4px 4px 0 rgba(0,0,0,0.3); } |
| 3 | .small-blur { box-shadow: 4px 4px 4px rgba(0,0,0,0.3); } |
| 4 | .med-blur { box-shadow: 4px 4px 10px rgba(0,0,0,0.3); } |
| 5 | .large-blur { box-shadow: 4px 4px 30px rgba(0,0,0,0.3); } |
| 6 | |
| 7 | /* Spread variations */ |
| 8 | .no-spread { box-shadow: 4px 4px 10px 0 rgba(0,0,0,0.3); } |
| 9 | .positive { box-shadow: 4px 4px 10px 8px rgba(0,0,0,0.3); } |
| 10 | .negative { box-shadow: 4px 4px 10px -4px rgba(0,0,0,0.3); } |
| 11 | |
| 12 | /* Zero blur — hard edge */ |
| 13 | .hard-edge { |
| 14 | box-shadow: 0 4px 0 -2px #00FF41; |
| 15 | } |
The inset keyword creates shadows inside the element, giving the appearance of depth or recessed surfaces. These are commonly used for input fields, pressed buttons, and card depressions.
| 1 | /* Basic inset shadow */ |
| 2 | .inset { |
| 3 | box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.3); |
| 4 | } |
| 5 | |
| 6 | /* Recessed input */ |
| 7 | .input-field { |
| 8 | background: #0A0A0A; |
| 9 | box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.4); |
| 10 | } |
| 11 | |
| 12 | /* Pressed button state */ |
| 13 | .button:active { |
| 14 | box-shadow: inset 0 2px 8px rgba(0, 0, 0, 0.3); |
| 15 | } |
| 16 | |
| 17 | /* Inset glow */ |
| 18 | .inset-glow { |
| 19 | box-shadow: inset 0 0 20px rgba(0, 255, 65, 0.1); |
| 20 | } |
| 21 | |
| 22 | /* Multiple inset shadows */ |
| 23 | .complex-inset { |
| 24 | box-shadow: |
| 25 | inset 0 2px 4px rgba(0, 0, 0, 0.3), |
| 26 | inset 0 -1px 0 rgba(255, 255, 255, 0.05); |
| 27 | } |
| 28 | |
| 29 | /* Terminal panel inset */ |
| 30 | .terminal-inset { |
| 31 | background: #0A0A0A; |
| 32 | border: 1px solid #1A1A1A; |
| 33 | box-shadow: |
| 34 | inset 0 1px 3px rgba(0, 0, 0, 0.5), |
| 35 | inset 0 0 0 1px rgba(0, 255, 65, 0.03); |
| 36 | } |
Glow effects are achieved by using the element's color (often accent green) with large blur radius and no offset. Layered glows with decreasing opacity create more realistic neon/CRT glow effects.
| 1 | /* Basic green glow */ |
| 2 | .green-glow { |
| 3 | box-shadow: 0 0 20px rgba(0, 255, 65, 0.5); |
| 4 | } |
| 5 | |
| 6 | /* Multi-layered terminal glow */ |
| 7 | .terminal-glow { |
| 8 | box-shadow: |
| 9 | 0 0 5px rgba(0, 255, 65, 0.4), |
| 10 | 0 0 15px rgba(0, 255, 65, 0.2), |
| 11 | 0 0 30px rgba(0, 255, 65, 0.1); |
| 12 | } |
| 13 | |
| 14 | /* Neon text glow */ |
| 15 | .neon-text { |
| 16 | color: #00FF41; |
| 17 | text-shadow: |
| 18 | 0 0 4px #00FF41, |
| 19 | 0 0 11px #00FF41, |
| 20 | 0 0 19px #00FF41, |
| 21 | 0 0 40px rgba(0, 255, 65, 0.3); |
| 22 | } |
| 23 | |
| 24 | /* CRT monitor glow */ |
| 25 | .crt-glow { |
| 26 | background: #0D0D0D; |
| 27 | box-shadow: |
| 28 | 0 0 0 1px rgba(0, 255, 65, 0.05), |
| 29 | 0 0 40px rgba(0, 255, 65, 0.05), |
| 30 | 0 0 80px rgba(0, 255, 65, 0.03); |
| 31 | border: 1px solid rgba(0, 255, 65, 0.1); |
| 32 | } |
| 33 | |
| 34 | /* Status indicator glow */ |
| 35 | .status-online { |
| 36 | box-shadow: 0 0 8px rgba(0, 255, 65, 0.8); |
| 37 | } |
| 38 | |
| 39 | /* Glow with animation */ |
| 40 | @keyframes pulse-glow { |
| 41 | 0%, 100% { box-shadow: 0 0 10px rgba(0, 255, 65, 0.3); } |
| 42 | 50% { box-shadow: 0 0 25px rgba(0, 255, 65, 0.6); } |
| 43 | } |
| 44 | |
| 45 | .pulsing-glow { |
| 46 | animation: pulse-glow 2s ease-in-out infinite; |
| 47 | } |
pro tip
The CSS filter: drop-shadow() function differs from box-shadow in that it follows the element's actual outline including transparent areas and clip-path. This makes it ideal for irregular shapes and transparent PNGs.
| 1 | /* drop-shadow vs box-shadow */ |
| 2 | .box-shadow { |
| 3 | box-shadow: 4px 4px 10px rgba(0, 0, 0, 0.3); |
| 4 | } |
| 5 | |
| 6 | .drop-shadow { |
| 7 | filter: drop-shadow(4px 4px 10px rgba(0, 0, 0, 0.3)); |
| 8 | } |
| 9 | |
| 10 | /* Following the actual shape */ |
| 11 | .clipped-element { |
| 12 | clip-path: polygon(50% 0%, 100% 50%, 50% 100%, 0% 50%); |
| 13 | background: #00FF41; |
| 14 | filter: drop-shadow(0 0 15px rgba(0, 255, 65, 0.5)); |
| 15 | } |
| 16 | |
| 17 | /* Multiple drop shadows */ |
| 18 | .multi-drop { |
| 19 | filter: |
| 20 | drop-shadow(0 0 5px rgba(0, 255, 65, 0.3)) |
| 21 | drop-shadow(0 0 15px rgba(0, 255, 65, 0.1)); |
| 22 | } |
| 23 | |
| 24 | /* Combining filters */ |
| 25 | .combined { |
| 26 | filter: drop-shadow(0 2px 4px rgba(0,0,0,0.3)) brightness(1.1); |
| 27 | } |
info
Shadows can impact rendering performance, especially when animated or applied to many elements. Here are performance considerations and best practices for shadow-heavy designs.
| 1 | /* Performance tips for shadows */ |
| 2 | |
| 3 | /* 1. Prefer box-shadow over filter: drop-shadow for rectangles */ |
| 4 | /* box-shadow is GPU-accelerated; drop-shadow renders on CPU */ |
| 5 | |
| 6 | /* 2. Avoid animating box-shadow on mobile */ |
| 7 | /* Instead, animate opacity of a shadow pseudo-element */ |
| 8 | .shadow-layer { |
| 9 | position: relative; |
| 10 | } |
| 11 | .shadow-layer::after { |
| 12 | content: ""; |
| 13 | position: absolute; |
| 14 | inset: 0; |
| 15 | box-shadow: 0 10px 30px rgba(0,0,0,0.3); |
| 16 | opacity: 0; |
| 17 | transition: opacity 0.3s; |
| 18 | pointer-events: none; |
| 19 | } |
| 20 | .shadow-layer:hover::after { |
| 21 | opacity: 1; |
| 22 | } |
| 23 | |
| 24 | /* 3. Use will-change for animated shadows */ |
| 25 | .animated-shadow { |
| 26 | will-change: box-shadow; |
| 27 | transition: box-shadow 0.3s; |
| 28 | } |
| 29 | |
| 30 | /* 4. Limit number of shadow layers */ |
| 31 | /* 2-3 layers max for performance; avoid more than 5 */ |
| 32 | |
| 33 | /* 5. Use transparent borders for inset-like effects on GPU */ |
| 34 | .border-glow { |
| 35 | border: 2px solid rgba(0, 255, 65, 0.3); |
| 36 | transition: border-color 0.2s, box-shadow 0.2s; |
| 37 | } |
| Technique | Performance | Use Case |
|---|---|---|
| box-shadow | Fast | Rectangular elements, cards, buttons |
| filter: drop-shadow | Moderate | Clipped shapes, transparent PNGs |
| text-shadow | Fast | Text, small elements |
| Multiple shadows | Slower | Use 2-3 max |
| Animated shadows | Costly | Use pseudo-element opacity workaround |
best practice