32 CSS Timelines12 / 32
Film Reel Timeline — Sprocket-Hole Border History Strip
History as celluloid: a vertical strip bordered by repeating sprocket holes (one repeating-radial-gradient, zero images), frame-numbered milestones, and a projector-flicker hover — the cinematic treatment for studio histories, film-festival retrospectives and media-brand 'our story' pages.
Published Updated
The code
<section class="tls-12" aria-label="Film history timeline demo">
<div class="tls-12__strip">
<p class="tls-12__leader">A STUDIO IN FIVE REELS · EST. 1962</p>
<ol class="tls-12__frames" role="list">
<li class="tls-12__frame">
<div class="tls-12__still" style="--g:linear-gradient(160deg,#8c6d46,#3d2f1c)" aria-hidden="true"></div>
<div class="tls-12__cap"><time datetime="1962">1962</time><p>Two projectionists rent a basement and call it a studio.</p></div>
</li>
<li class="tls-12__frame">
<div class="tls-12__still" style="--g:linear-gradient(160deg,#5e7a8c,#1e2f3d)" aria-hidden="true"></div>
<div class="tls-12__cap"><time datetime="1974">1974</time><p>First feature. It loses money beautifully.</p></div>
</li>
<li class="tls-12__frame">
<div class="tls-12__still" style="--g:linear-gradient(160deg,#8c4646,#3d1c1c)" aria-hidden="true"></div>
<div class="tls-12__cap"><time datetime="1989">1989</time><p>The archive fire. Three reels survive; we rebuild from prints.</p></div>
</li>
<li class="tls-12__frame">
<div class="tls-12__still" style="--g:linear-gradient(160deg,#46748c,#12212e)" aria-hidden="true"></div>
<div class="tls-12__cap"><time datetime="2011">2011</time><p>Full catalogue restored and scanned at 4K.</p></div>
</li>
</ol>
</div>
</section><section class="tls-12" aria-label="Film history timeline demo">
<div class="tls-12__strip">
<p class="tls-12__leader">A STUDIO IN FIVE REELS · EST. 1962</p>
<ol class="tls-12__frames" role="list">
<li class="tls-12__frame">
<div class="tls-12__still" style="--g:linear-gradient(160deg,#8c6d46,#3d2f1c)" aria-hidden="true"></div>
<div class="tls-12__cap"><time datetime="1962">1962</time><p>Two projectionists rent a basement and call it a studio.</p></div>
</li>
<li class="tls-12__frame">
<div class="tls-12__still" style="--g:linear-gradient(160deg,#5e7a8c,#1e2f3d)" aria-hidden="true"></div>
<div class="tls-12__cap"><time datetime="1974">1974</time><p>First feature. It loses money beautifully.</p></div>
</li>
<li class="tls-12__frame">
<div class="tls-12__still" style="--g:linear-gradient(160deg,#8c4646,#3d1c1c)" aria-hidden="true"></div>
<div class="tls-12__cap"><time datetime="1989">1989</time><p>The archive fire. Three reels survive; we rebuild from prints.</p></div>
</li>
<li class="tls-12__frame">
<div class="tls-12__still" style="--g:linear-gradient(160deg,#46748c,#12212e)" aria-hidden="true"></div>
<div class="tls-12__cap"><time datetime="2011">2011</time><p>Full catalogue restored and scanned at 4K.</p></div>
</li>
</ol>
</div>
</section>.tls-12,
.tls-12 *,
.tls-12 *::before,
.tls-12 *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
.tls-12 {
min-height: 100vh;
width: 100%;
--tl-w: 340px;
--tl-film: #141210;
--tl-paper: #f5f0e6;
font-family: 'Segoe UI',system-ui,sans-serif;
background: #211d19;
padding: 56px 24px;
display: grid;
place-items: center;
}
.tls-12__strip {
width: min(var(--tl-w),100%);
background: var(--tl-film);
position: relative;
padding: 22px 44px 30px;
border-radius: 4px;
box-shadow: 0 20px 50px -24px rgba(0,0,0,.8);
}
.tls-12__strip::before,
.tls-12__strip::after {
content: '';
position: absolute;
top: 0;
bottom: 0;
width: 26px;
background: repeating-radial-gradient(circle at center,var(--tl-paper) 0 4.5px,transparent 5.5px) center / 100% 26px;
}
.tls-12__strip::before {
left: 6px;
}
.tls-12__strip::after {
right: 6px;
}
.tls-12__leader {
font-family: ui-monospace,Menlo,monospace;
font-size: 10px;
letter-spacing: .28em;
color: #a89e8c;
text-align: center;
margin-bottom: 18px;
}
.tls-12__frames {
list-style: none;
counter-reset: tls-frame;
display: flex;
flex-direction: column;
gap: 18px;
}
.tls-12__frame {
counter-increment: tls-frame;
position: relative;
}
.tls-12__still {
width: 100%;
aspect-ratio: 4/3;
background: var(--g);
border: 3px solid #2b2723;
border-radius: 2px;
position: relative;
overflow: hidden;
}
.tls-12__still::after {
content: '';
position: absolute;
inset: 0;
background: linear-gradient(105deg,transparent 40%,rgba(255,250,230,.07) 45%,transparent 52%);
}
@media (prefers-reduced-motion: no-preference) {
.tls-12__frame:hover .tls-12__still {
animation: tls-12-flicker .35s steps(2,jump-none) infinite;
}
@keyframes tls-12-flicker {
0% {
filter: brightness(1);
}
50% {
filter: brightness(1.18);
}
100% {
filter: brightness(.94);
}
}
}
.tls-12__cap {
display: flex;
gap: 12px;
align-items: baseline;
margin-top: 8px;
}
.tls-12__cap::before {
content: counter(tls-frame,decimal-leading-zero);
font-family: ui-monospace,Menlo,monospace;
font-size: 10px;
color: #6e6353;
letter-spacing: .1em;
}
.tls-12__cap time {
font-family: ui-monospace,Menlo,monospace;
font-size: 13px;
font-weight: 700;
color: var(--tl-paper);
}
.tls-12__cap p {
font-size: 12.5px;
line-height: 1.5;
color: #b8ac97;
text-wrap: pretty;
}.tls-12,
.tls-12 *,
.tls-12 *::before,
.tls-12 *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
.tls-12 {
min-height: 100vh;
width: 100%;
--tl-w: 340px;
--tl-film: #141210;
--tl-paper: #f5f0e6;
font-family: 'Segoe UI',system-ui,sans-serif;
background: #211d19;
padding: 56px 24px;
display: grid;
place-items: center;
}
.tls-12__strip {
width: min(var(--tl-w),100%);
background: var(--tl-film);
position: relative;
padding: 22px 44px 30px;
border-radius: 4px;
box-shadow: 0 20px 50px -24px rgba(0,0,0,.8);
}
.tls-12__strip::before,
.tls-12__strip::after {
content: '';
position: absolute;
top: 0;
bottom: 0;
width: 26px;
background: repeating-radial-gradient(circle at center,var(--tl-paper) 0 4.5px,transparent 5.5px) center / 100% 26px;
}
.tls-12__strip::before {
left: 6px;
}
.tls-12__strip::after {
right: 6px;
}
.tls-12__leader {
font-family: ui-monospace,Menlo,monospace;
font-size: 10px;
letter-spacing: .28em;
color: #a89e8c;
text-align: center;
margin-bottom: 18px;
}
.tls-12__frames {
list-style: none;
counter-reset: tls-frame;
display: flex;
flex-direction: column;
gap: 18px;
}
.tls-12__frame {
counter-increment: tls-frame;
position: relative;
}
.tls-12__still {
width: 100%;
aspect-ratio: 4/3;
background: var(--g);
border: 3px solid #2b2723;
border-radius: 2px;
position: relative;
overflow: hidden;
}
.tls-12__still::after {
content: '';
position: absolute;
inset: 0;
background: linear-gradient(105deg,transparent 40%,rgba(255,250,230,.07) 45%,transparent 52%);
}
@media (prefers-reduced-motion: no-preference) {
.tls-12__frame:hover .tls-12__still {
animation: tls-12-flicker .35s steps(2,jump-none) infinite;
}
@keyframes tls-12-flicker {
0% {
filter: brightness(1);
}
50% {
filter: brightness(1.18);
}
100% {
filter: brightness(.94);
}
}
}
.tls-12__cap {
display: flex;
gap: 12px;
align-items: baseline;
margin-top: 8px;
}
.tls-12__cap::before {
content: counter(tls-frame,decimal-leading-zero);
font-family: ui-monospace,Menlo,monospace;
font-size: 10px;
color: #6e6353;
letter-spacing: .1em;
}
.tls-12__cap time {
font-family: ui-monospace,Menlo,monospace;
font-size: 13px;
font-weight: 700;
color: var(--tl-paper);
}
.tls-12__cap p {
font-size: 12.5px;
line-height: 1.5;
color: #b8ac97;
text-wrap: pretty;
}/* No JavaScript — sprocket holes are one repeating-radial-gradient per rail; frame numbers are CSS counters; the flicker is opt-in behind prefers-reduced-motion:no-preference. */
/* No JavaScript — sprocket holes are one repeating-radial-gradient per rail; frame numbers are CSS counters; the flicker is opt-in behind prefers-reduced-motion:no-preference. */Here's a working CSS Timeline from CodeFronts. Use it as-is or adapt to your framework. All classes are scoped under a unique prefix so the code won't collide with your existing styles. MIT licensed.
Demo: Film Reel Timeline — Sprocket-Hole Border History Strip
Source: https://codefronts.com/layouts/css-timelines/film-reel-timeline/
History as celluloid: a vertical strip bordered by repeating sprocket holes (one repeating-radial-gradient, zero images), frame-numbered milestones, and a projector-flicker hover — the cinematic treatment for studio histories, film-festival retrospectives and media-brand 'our story' pages.
## HTML
```html
<section class="tls-12" aria-label="Film history timeline demo">
<div class="tls-12__strip">
<p class="tls-12__leader">A STUDIO IN FIVE REELS · EST. 1962</p>
<ol class="tls-12__frames" role="list">
<li class="tls-12__frame">
<div class="tls-12__still" style="--g:linear-gradient(160deg,#8c6d46,#3d2f1c)" aria-hidden="true"></div>
<div class="tls-12__cap"><time datetime="1962">1962</time><p>Two projectionists rent a basement and call it a studio.</p></div>
</li>
<li class="tls-12__frame">
<div class="tls-12__still" style="--g:linear-gradient(160deg,#5e7a8c,#1e2f3d)" aria-hidden="true"></div>
<div class="tls-12__cap"><time datetime="1974">1974</time><p>First feature. It loses money beautifully.</p></div>
</li>
<li class="tls-12__frame">
<div class="tls-12__still" style="--g:linear-gradient(160deg,#8c4646,#3d1c1c)" aria-hidden="true"></div>
<div class="tls-12__cap"><time datetime="1989">1989</time><p>The archive fire. Three reels survive; we rebuild from prints.</p></div>
</li>
<li class="tls-12__frame">
<div class="tls-12__still" style="--g:linear-gradient(160deg,#46748c,#12212e)" aria-hidden="true"></div>
<div class="tls-12__cap"><time datetime="2011">2011</time><p>Full catalogue restored and scanned at 4K.</p></div>
</li>
</ol>
</div>
</section>
```
## CSS
```css
.tls-12,
.tls-12 *,
.tls-12 *::before,
.tls-12 *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
.tls-12 {
min-height: 100vh;
width: 100%;
--tl-w: 340px;
--tl-film: #141210;
--tl-paper: #f5f0e6;
font-family: 'Segoe UI',system-ui,sans-serif;
background: #211d19;
padding: 56px 24px;
display: grid;
place-items: center;
}
.tls-12__strip {
width: min(var(--tl-w),100%);
background: var(--tl-film);
position: relative;
padding: 22px 44px 30px;
border-radius: 4px;
box-shadow: 0 20px 50px -24px rgba(0,0,0,.8);
}
.tls-12__strip::before,
.tls-12__strip::after {
content: '';
position: absolute;
top: 0;
bottom: 0;
width: 26px;
background: repeating-radial-gradient(circle at center,var(--tl-paper) 0 4.5px,transparent 5.5px) center / 100% 26px;
}
.tls-12__strip::before {
left: 6px;
}
.tls-12__strip::after {
right: 6px;
}
.tls-12__leader {
font-family: ui-monospace,Menlo,monospace;
font-size: 10px;
letter-spacing: .28em;
color: #a89e8c;
text-align: center;
margin-bottom: 18px;
}
.tls-12__frames {
list-style: none;
counter-reset: tls-frame;
display: flex;
flex-direction: column;
gap: 18px;
}
.tls-12__frame {
counter-increment: tls-frame;
position: relative;
}
.tls-12__still {
width: 100%;
aspect-ratio: 4/3;
background: var(--g);
border: 3px solid #2b2723;
border-radius: 2px;
position: relative;
overflow: hidden;
}
.tls-12__still::after {
content: '';
position: absolute;
inset: 0;
background: linear-gradient(105deg,transparent 40%,rgba(255,250,230,.07) 45%,transparent 52%);
}
@media (prefers-reduced-motion: no-preference) {
.tls-12__frame:hover .tls-12__still {
animation: tls-12-flicker .35s steps(2,jump-none) infinite;
}
@keyframes tls-12-flicker {
0% {
filter: brightness(1);
}
50% {
filter: brightness(1.18);
}
100% {
filter: brightness(.94);
}
}
}
.tls-12__cap {
display: flex;
gap: 12px;
align-items: baseline;
margin-top: 8px;
}
.tls-12__cap::before {
content: counter(tls-frame,decimal-leading-zero);
font-family: ui-monospace,Menlo,monospace;
font-size: 10px;
color: #6e6353;
letter-spacing: .1em;
}
.tls-12__cap time {
font-family: ui-monospace,Menlo,monospace;
font-size: 13px;
font-weight: 700;
color: var(--tl-paper);
}
.tls-12__cap p {
font-size: 12.5px;
line-height: 1.5;
color: #b8ac97;
text-wrap: pretty;
}
```
## JavaScript
```js
/* No JavaScript — sprocket holes are one repeating-radial-gradient per rail; frame numbers are CSS counters; the flicker is opt-in behind prefers-reduced-motion:no-preference. */
```Here's a working CSS Timeline from CodeFronts. Use it as-is or adapt to your framework. All classes are scoped under a unique prefix so the code won't collide with your existing styles. MIT licensed.
Demo: Film Reel Timeline — Sprocket-Hole Border History Strip
Source: https://codefronts.com/layouts/css-timelines/film-reel-timeline/
History as celluloid: a vertical strip bordered by repeating sprocket holes (one repeating-radial-gradient, zero images), frame-numbered milestones, and a projector-flicker hover — the cinematic treatment for studio histories, film-festival retrospectives and media-brand 'our story' pages.
## HTML
```html
<section class="tls-12" aria-label="Film history timeline demo">
<div class="tls-12__strip">
<p class="tls-12__leader">A STUDIO IN FIVE REELS · EST. 1962</p>
<ol class="tls-12__frames" role="list">
<li class="tls-12__frame">
<div class="tls-12__still" style="--g:linear-gradient(160deg,#8c6d46,#3d2f1c)" aria-hidden="true"></div>
<div class="tls-12__cap"><time datetime="1962">1962</time><p>Two projectionists rent a basement and call it a studio.</p></div>
</li>
<li class="tls-12__frame">
<div class="tls-12__still" style="--g:linear-gradient(160deg,#5e7a8c,#1e2f3d)" aria-hidden="true"></div>
<div class="tls-12__cap"><time datetime="1974">1974</time><p>First feature. It loses money beautifully.</p></div>
</li>
<li class="tls-12__frame">
<div class="tls-12__still" style="--g:linear-gradient(160deg,#8c4646,#3d1c1c)" aria-hidden="true"></div>
<div class="tls-12__cap"><time datetime="1989">1989</time><p>The archive fire. Three reels survive; we rebuild from prints.</p></div>
</li>
<li class="tls-12__frame">
<div class="tls-12__still" style="--g:linear-gradient(160deg,#46748c,#12212e)" aria-hidden="true"></div>
<div class="tls-12__cap"><time datetime="2011">2011</time><p>Full catalogue restored and scanned at 4K.</p></div>
</li>
</ol>
</div>
</section>
```
## CSS
```css
.tls-12,
.tls-12 *,
.tls-12 *::before,
.tls-12 *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
.tls-12 {
min-height: 100vh;
width: 100%;
--tl-w: 340px;
--tl-film: #141210;
--tl-paper: #f5f0e6;
font-family: 'Segoe UI',system-ui,sans-serif;
background: #211d19;
padding: 56px 24px;
display: grid;
place-items: center;
}
.tls-12__strip {
width: min(var(--tl-w),100%);
background: var(--tl-film);
position: relative;
padding: 22px 44px 30px;
border-radius: 4px;
box-shadow: 0 20px 50px -24px rgba(0,0,0,.8);
}
.tls-12__strip::before,
.tls-12__strip::after {
content: '';
position: absolute;
top: 0;
bottom: 0;
width: 26px;
background: repeating-radial-gradient(circle at center,var(--tl-paper) 0 4.5px,transparent 5.5px) center / 100% 26px;
}
.tls-12__strip::before {
left: 6px;
}
.tls-12__strip::after {
right: 6px;
}
.tls-12__leader {
font-family: ui-monospace,Menlo,monospace;
font-size: 10px;
letter-spacing: .28em;
color: #a89e8c;
text-align: center;
margin-bottom: 18px;
}
.tls-12__frames {
list-style: none;
counter-reset: tls-frame;
display: flex;
flex-direction: column;
gap: 18px;
}
.tls-12__frame {
counter-increment: tls-frame;
position: relative;
}
.tls-12__still {
width: 100%;
aspect-ratio: 4/3;
background: var(--g);
border: 3px solid #2b2723;
border-radius: 2px;
position: relative;
overflow: hidden;
}
.tls-12__still::after {
content: '';
position: absolute;
inset: 0;
background: linear-gradient(105deg,transparent 40%,rgba(255,250,230,.07) 45%,transparent 52%);
}
@media (prefers-reduced-motion: no-preference) {
.tls-12__frame:hover .tls-12__still {
animation: tls-12-flicker .35s steps(2,jump-none) infinite;
}
@keyframes tls-12-flicker {
0% {
filter: brightness(1);
}
50% {
filter: brightness(1.18);
}
100% {
filter: brightness(.94);
}
}
}
.tls-12__cap {
display: flex;
gap: 12px;
align-items: baseline;
margin-top: 8px;
}
.tls-12__cap::before {
content: counter(tls-frame,decimal-leading-zero);
font-family: ui-monospace,Menlo,monospace;
font-size: 10px;
color: #6e6353;
letter-spacing: .1em;
}
.tls-12__cap time {
font-family: ui-monospace,Menlo,monospace;
font-size: 13px;
font-weight: 700;
color: var(--tl-paper);
}
.tls-12__cap p {
font-size: 12.5px;
line-height: 1.5;
color: #b8ac97;
text-wrap: pretty;
}
```
## JavaScript
```js
/* No JavaScript — sprocket holes are one repeating-radial-gradient per rail; frame numbers are CSS counters; the flicker is opt-in behind prefers-reduced-motion:no-preference. */
```How this works
The sprockets are the whole show and they cost one declaration per side: background: repeating-radial-gradient(circle at center, #f5f0e6 0 5px, transparent 6px) center / 100% 26px on two absolutely-positioned edge rails. Repeating radial gradients tile the punched holes down the full strip height — add fifty more milestones and the perforation just continues; there is no image to resize and no SVG to duplicate.
Each milestone is a 'frame': an <li> with a 4:3 image slot (gradient placeholder — swap for stills), a frame-number counter rendered with a CSS counter (counter(tls-frame, decimal-leading-zero)) so numbering is automatic and reorder-safe, and a caption bar in the typeface language of leader tape.
The hover flicker is a steps() animation nudging opacity between three values — steps(2, jump-none) makes it strobe like a projector gate rather than fade like a UI. It runs ONLY on hover and only outside reduced-motion, because flicker is exactly the kind of motion vestibular users need to opt out of.
Techniques used: repeating-radial-gradient sprocket perforation (infinite, image-free) · CSS counters for automatic frame numbering · steps() flicker on hover (gated behind motion preferences) · 4:3 aspect-ratio frame slots (CLS-free image swap) · film-leader typography with letterpress tracking
Make it yours
- Sprocket pitch: the 26px tile height in the background-size — match it to your frame spacing for aligned holes.
- Swap gradient slots for real stills:
<img>withwidth:100%; aspect-ratio:4/3; object-fit:cover— the strip never reflows. - Frame numbers start at the counter-reset value — set
counter-reset: tls-frame 1893for a cinema-history piece starting at frame 1893. - Strip width: the 320px --tl-w token; sprocket rails scale with it automatically.
Gotchas — read before shipping
- Keep the sprocket rail width (26px) + strip padding in sync — holes overlapping content reads as a rendering bug, not a style.
- The flicker MUST sit behind @media (prefers-reduced-motion:no-preference) — strobing effects are a WCAG 2.3 concern; opt IN to motion, not out.
- CSS counters increment in DOM order — if you reorder milestones with flex order, the visible numbers won't match; reorder the markup instead.
- Use decimal-leading-zero (or pad manually) — '07' reads as a frame number, '7' reads as a typo.
Browser support
| Chrome | Safari | Firefox | Edge |
|---|---|---|---|
| 114+ | 17.5+ | 121+ | 114+ |
Floor set by text-wrap as this demo is written. The core technique itself goes back further — Chrome all.
repeating-radial-gradient, counters and steps() easing are baseline-everywhere CSS — nothing gated at all.