CSS-only Christmas Lights
Create a beautiful string of animated, glowing Christmas lights using only HTML and CSS — no JavaScript required.
CSS-only Christmas Lights
In this episode we will build a festive string of glowing Christmas lights using pure CSS. The lights will glow, flicker, and animate with staggered timing — all without a single line of JavaScript.
The HTML Structure
<ul class="lights">
<li></li>
<li></li>
<li></li>
<li></li>
<li></li>
<li></li>
<li></li>
</ul>
Each <li> represents one light bulb. The <ul> acts as the wire connecting them all.
The Wire — Styling the UL
.lights {
display: flex;
justify-content: center;
gap: 40px;
padding: 20px;
list-style: none;
position: relative;
}
/* The wire connecting the lights */
.lights::before {
content: "";
position: absolute;
top: 0;
left: 5%;
right: 5%;
height: 4px;
background: #333;
border-radius: 2px;
}
The Bulbs — Styling Each LI
.lights li {
width: 20px;
height: 30px;
border-radius: 50% 50% 50% 50% / 60% 60% 40% 40%;
position: relative;
margin-top: 20px;
}
/* The cap on top of each bulb */
.lights li::before {
content: "";
position: absolute;
top: -8px;
left: 50%;
transform: translateX(-50%);
width: 12px;
height: 12px;
background: #444;
border-radius: 3px 3px 0 0;
}
/* The string connecting to the wire */
.lights li::after {
content: "";
position: absolute;
top: -20px;
left: 50%;
width: 2px;
height: 12px;
background: #333;
}
Assigning Colors with nth-child
.lights li:nth-child(1) { background: #ff4136; }
.lights li:nth-child(2) { background: #ffdc00; }
.lights li:nth-child(3) { background: #2ecc40; }
.lights li:nth-child(4) { background: #0074d9; }
.lights li:nth-child(5) { background: #ff851b; }
.lights li:nth-child(6) { background: #b10dc9; }
.lights li:nth-child(7) { background: #ff4136; }
Each bulb gets a different Christmas color. The pattern repeats for longer strings using higher nth-child values or using formulas like nth-child(7n+1).
The Glow Animation
@keyframes glow {
0%, 100% {
filter: brightness(1);
box-shadow: 0 0 5px currentColor,
0 0 10px currentColor;
}
50% {
filter: brightness(1.5);
box-shadow: 0 0 15px currentColor,
0 0 30px currentColor,
0 0 45px currentColor;
}
}
.lights li {
animation: glow 2s ease-in-out infinite alternate;
}
The currentColor keyword automatically picks up each bulb's background color for the glow, so we only need one animation for all colors.
Staggered Timing with nth-child
.lights li:nth-child(1) { animation-delay: 0s; }
.lights li:nth-child(2) { animation-delay: 0.3s; }
.lights li:nth-child(3) { animation-delay: 0.6s; }
.lights li:nth-child(4) { animation-delay: 0.9s; }
.lights li:nth-child(5) { animation-delay: 1.2s; }
.lights li:nth-child(6) { animation-delay: 1.5s; }
.lights li:nth-child(7) { animation-delay: 1.8s; }
Each bulb starts its animation slightly after the previous one, creating a cascading glow effect that travels along the string.
Adding a Subtle Swing
@keyframes swing {
0%, 100% { transform: rotate(-3deg); }
50% { transform: rotate(3deg); }
}
.lights li {
transform-origin: top center;
animation: glow 2s ease-in-out infinite alternate,
swing 4s ease-in-out infinite alternate;
}
We chain a second animation to make each bulb gently swing like it is hanging from a wire. The transform-origin: top center ensures it pivots from the cap at the top.
Dark Background for Maximum Effect
body {
background: #111;
min-height: 100vh;
display: flex;
align-items: flex-start;
justify-content: center;
padding-top: 80px;
}
A dark background makes the glowing lights pop. The flex layout centers the string horizontally near the top of the page.
Key Takeaways
nth-childselectors let you assign unique colors and stagger animation delays to each bulbcurrentColorinbox-shadowautomatically adapts the glow to each bulb's background color- Chaining multiple animations (glow + swing) on a single element creates rich, layered effects
- Pseudo-elements (
::beforeand::after) are used to build the bulb caps and wire strings without extra HTML - A dark background is essential to showcase glow and
box-shadoweffects properly