Marquee
2025.12.04#showcaseInspired by the last showcase item, I wanted to look at making a movie theatre / broadway marquee equipped with moving lights 💡 using only CSS like so:
🎼CSS: The Musical 🎺
Check out the details below.
HTML makeup
<div class="marquee">
<div class="corner top-left"></div>
<div class="lights-top">
<div class="light"></div>
<!--9x light-->
</div>
<div class="corner top-right"></div>
<div class="lights-left">
<div class="light"></div>
<!--3x light-->
</div>
<div class="marquee-content">
<p>🎼CSS: The Musical 🎺</p>
</div>
<div class="lights-right">
<div class="light"></div>
<!--3x light-->
</div>
<div class="corner bottom-left"></div>
<div class="lights-bottom">
<div class="light"></div>
<!--9x light-->
</div>
<div class="corner bottom-right"></div>
</div>At its core this markup will be used in a 3x3 CSS grid manner structure with cells depicted as below:
| corner | lights | corner |
| lights | content | lights |
| corner | lights | corner |
corner- the inward curved corner to give the marquee some elegance 💅🏼lights- string of light bulb elements surrounding the marquee which are aligned with flex properties in horizontal/vertical mannerscontent- The main area making the announcement 🎥
Let's look at the CSS structure for these different pieces.
CSS on the corners
In order to get an inward-looking swoop on the marquee corners we need to transform the div element shape into a circle and control from where the center starts. For that purpose the clip-path property has the following value syntax:
circle(<radius> at <x> <y>);
The at x y helps control the starting point. With a value of at 0% 0% we would essentially start a circle from the top-left corner the element selected with a radius of 100%. Example:
.corner.top-left {
width: 100px;
height: 100px;
background-color: red;
clip-path: circle(100% at 0% 0%);
}And the resulting shape:
Notice that the overflow is cut off leaving us with shape that can be colored in to match the background it is on. Doing that on the marquee will leave an inward looking swoop with what remains behind it. Following that pattern, for the other corners we would have (x,y) values of:
- top-right = 100% 0%
- bottom-left = 0% 100%
- bottom-right = 100% 100%
CSS for the lights 💡
We want to create motion in the lights to simulate movement in a clock-wise manner seen on marquees. In order to make it look seamless we need the amount of lights on each side to be of a designated multiple with # of lights on top=bottom and left=right. Doing so will create 'even' movement. In this case we use a multiple of 3. The top/bottom rows have 9 lights and the left/right columns have 3. Depiction below:
| 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | ||
|---|---|---|---|---|---|---|---|---|---|---|
| 3 | 1 | |||||||||
| 2 | 2 | |||||||||
| 1 | 3 | |||||||||
| 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 |
The numbering here is intentional indicating how we want to render the lights as they appear on the screen (This will help with animation explained below). We need the top/bottom row of lights need to be aligned in a horizontal manner with display:flex and the bottom row to be laid out in a flex-direction: row-reverse to acheive the clock-wise arrangement to render lights from right to left.
.lights-bottom {
flex-alignment: row-reverse;
}The left/right lights also need some flex treatment since lights are arranged in a column with the left side lights rendering in a clockwise manner from bottom -> top. Here's what our CSS needs:
.lights-left,
.lights-right {
flex-direction: column;
}
.lights-left {
flex-alignment: column-reverse;
}With that in place we are ready for some animation.
Animation
We only need one animation 'pulse' so each light turns on and off infinitely. As with the total number of lights on the marquee, we also keep the timing to a multiple of 3 (total animation time will be 0.6s) so we can evenly stagger a delay for the other lights to keep the motion going in a clock-wise manner.
animation: pulse 0.6s linear infinite;
/* ... */
@keyframes pulse {
0% {background-color: hsla(48, 100%, 75%, 1);} /* light bulb on */
50% {background-color: hsla(48, 50%, 35%, 0.5);} /* light bulb off */
}
</style>We need a group of selectors to work with selecting lights 3 at a time. Taking the first 3 lights in .lights-top as the example, lets look at what the selectors below do:
.lights-top .light:nth-child(3n) {
animation: pulse 0.6s linear infinite;
}
.lights-top .light:nth-child(3n + 1) {
animation: pulse 0.6s 0.2s linear infinite;
}
.lights-top .light:nth-child(3n + 2) {
animation: pulse 0.6s 0.4s linear infinite;
}- The
3nselector selects the 3rd light and immediately run the pulse animation that turns it on. - The second rule with a
3n+1selects the first light which adds a delay0.2s. - The middle light is selected by
3n+2and a further delay0.4sis added.
With this in place we get this effect of the light traveling from left to right. Extending the number of lights in multiples of 3 then give you the seamless effect traveling down any given direction !
Marquee
The last piece involves centering text and creating the same swoooping effect with multiple background layers using radial gradients. This background technique creates a pixelated curve for the inward swoop which is a bit rough-looking but easier "hide" on this smaller inner part of the marquee.
.marquee-content {
display: grid;
place-content: center center;
background: radial-gradient(
circle 50px at 100% 0px,
var(--marquee-bg) 50%,
transparent 50%
), radial-gradient(
circle 50px at 0 0,
var(--marquee-bg) 50%,
transparent 50%
), radial-gradient(
circle 50px at 0 100%,
var(--marquee-bg) 50%,
transparent 50%
), radial-gradient(
circle 50px at 100% 100%,
var(--marquee-bg) 50%,
transparent 50%
), radial-gradient(var(--marquee-content-light), var(--marquee-content)),
hsla(48, 50%, 92%, 1);
}