32 CSS Timelines17 / 32
Cinematic Filmstrip Timeline — Scroll-Snap Career Frames
A widescreen horizontal narrative: each chapter of a career or story is a letterboxed 'frame' in a scroll-snapping strip with chapter numbers, act labels and a progress dot rail driven by scroll-snap alone — the storytelling layout for portfolios, agency case studies and anniversary microsites.
Published Updated
The code
<section class="tls-17" aria-label="Filmstrip career timeline demo">
<header class="tls-17__head"><h3 class="tls-17__h">Fifteen years in five frames</h3><p class="tls-17__hint">Scroll sideways →</p></header>
<ol class="tls-17__strip" role="list" tabindex="0" aria-label="Career chapters, horizontally scrollable">
<li class="tls-17__frame" style="--g:linear-gradient(150deg,#1d3a5f,#0a1526)">
<p class="tls-17__act">Act I · <time datetime="2011">2011</time></p>
<p class="tls-17__no" aria-hidden="true">01</p>
<h4 class="tls-17__t">The first studio job</h4>
<p class="tls-17__d">Junior designer. Learned that shipping beats perfect — by shipping something imperfect.</p>
</li>
<li class="tls-17__frame" style="--g:linear-gradient(150deg,#4a2c15,#170d05)">
<p class="tls-17__act">Act II · <time datetime="2015">2015</time></p>
<p class="tls-17__no" aria-hidden="true">02</p>
<h4 class="tls-17__t">Freelance years</h4>
<p class="tls-17__d">Forty clients, three time zones, one folding desk. Rate doubled the year I learned to say no.</p>
</li>
<li class="tls-17__frame" style="--g:linear-gradient(150deg,#14453d,#04120f)">
<p class="tls-17__act">Act III · <time datetime="2019">2019</time></p>
<p class="tls-17__no" aria-hidden="true">03</p>
<h4 class="tls-17__t">Building the team</h4>
<p class="tls-17__d">First design hire → design director. The work became making other people's work better.</p>
</li>
<li class="tls-17__frame" style="--g:linear-gradient(150deg,#4c1d3d,#160414)">
<p class="tls-17__act">Act IV · <time datetime="2023">2023</time></p>
<p class="tls-17__no" aria-hidden="true">04</p>
<h4 class="tls-17__t">The product bet</h4>
<p class="tls-17__d">Left the safe seat to co-found a tool for design systems. Terrifying. Correct.</p>
</li>
<li class="tls-17__frame" style="--g:linear-gradient(150deg,#3d3d14,#121204)">
<p class="tls-17__act">Act V · <time datetime="2026">now</time></p>
<p class="tls-17__no" aria-hidden="true">05</p>
<h4 class="tls-17__t">Still in the credits</h4>
<p class="tls-17__d">10k teams use the thing. The next frame is unwritten on purpose.</p>
</li>
</ol>
</section><section class="tls-17" aria-label="Filmstrip career timeline demo">
<header class="tls-17__head"><h3 class="tls-17__h">Fifteen years in five frames</h3><p class="tls-17__hint">Scroll sideways →</p></header>
<ol class="tls-17__strip" role="list" tabindex="0" aria-label="Career chapters, horizontally scrollable">
<li class="tls-17__frame" style="--g:linear-gradient(150deg,#1d3a5f,#0a1526)">
<p class="tls-17__act">Act I · <time datetime="2011">2011</time></p>
<p class="tls-17__no" aria-hidden="true">01</p>
<h4 class="tls-17__t">The first studio job</h4>
<p class="tls-17__d">Junior designer. Learned that shipping beats perfect — by shipping something imperfect.</p>
</li>
<li class="tls-17__frame" style="--g:linear-gradient(150deg,#4a2c15,#170d05)">
<p class="tls-17__act">Act II · <time datetime="2015">2015</time></p>
<p class="tls-17__no" aria-hidden="true">02</p>
<h4 class="tls-17__t">Freelance years</h4>
<p class="tls-17__d">Forty clients, three time zones, one folding desk. Rate doubled the year I learned to say no.</p>
</li>
<li class="tls-17__frame" style="--g:linear-gradient(150deg,#14453d,#04120f)">
<p class="tls-17__act">Act III · <time datetime="2019">2019</time></p>
<p class="tls-17__no" aria-hidden="true">03</p>
<h4 class="tls-17__t">Building the team</h4>
<p class="tls-17__d">First design hire → design director. The work became making other people's work better.</p>
</li>
<li class="tls-17__frame" style="--g:linear-gradient(150deg,#4c1d3d,#160414)">
<p class="tls-17__act">Act IV · <time datetime="2023">2023</time></p>
<p class="tls-17__no" aria-hidden="true">04</p>
<h4 class="tls-17__t">The product bet</h4>
<p class="tls-17__d">Left the safe seat to co-found a tool for design systems. Terrifying. Correct.</p>
</li>
<li class="tls-17__frame" style="--g:linear-gradient(150deg,#3d3d14,#121204)">
<p class="tls-17__act">Act V · <time datetime="2026">now</time></p>
<p class="tls-17__no" aria-hidden="true">05</p>
<h4 class="tls-17__t">Still in the credits</h4>
<p class="tls-17__d">10k teams use the thing. The next frame is unwritten on purpose.</p>
</li>
</ol>
</section>.tls-17,
.tls-17 *,
.tls-17 *::before,
.tls-17 *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
.tls-17 {
min-height: 100vh;
width: 100%;
--tl-ink: #f2ede4;
--tl-mut: #9a917f;
font-family: 'Segoe UI',system-ui,sans-serif;
background: #0d0b08;
padding: 56px 0 64px;
}
.tls-17__head {
display: flex;
justify-content: space-between;
align-items: baseline;
gap: 16px;
padding: 0 clamp(20px,6vw,64px);
margin-bottom: 26px;
}
.tls-17__h {
font-family: Georgia,serif;
font-size: clamp(20px,3.4vw,28px);
font-weight: 700;
color: var(--tl-ink);
letter-spacing: -.01em;
}
.tls-17__hint {
font-size: 11px;
letter-spacing: .18em;
text-transform: uppercase;
color: var(--tl-mut);
}
.tls-17__strip {
list-style: none;
display: flex;
gap: 22px;
overflow-x: auto;
scroll-snap-type: x mandatory;
scroll-padding-inline: clamp(20px,6vw,64px);
padding: 4px clamp(20px,6vw,64px) 18px;
scrollbar-width: thin;
scrollbar-color: #3d382f transparent;
}
.tls-17__strip:focus-visible {
outline: 2px solid #c8b98a;
outline-offset: 4px;
}
.tls-17__frame {
flex: 0 0 min(560px,88%);
scroll-snap-align: center;
aspect-ratio: 21/10;
display: flex;
flex-direction: column;
justify-content: flex-end;
gap: 6px;
background: var(--g);
border-radius: 6px;
padding: 28px 30px;
position: relative;
overflow: hidden;
}
.tls-17__frame::before {
content: '';
position: absolute;
inset: 0;
background: linear-gradient(180deg,rgba(6,5,3,.25),transparent 35%,transparent 55%,rgba(6,5,3,.72));
}
.tls-17__act {
position: relative;
font-size: 10.5px;
font-weight: 700;
letter-spacing: .24em;
text-transform: uppercase;
color: #cdbd93;
}
.tls-17__no {
position: absolute;
top: 10px;
right: 22px;
font-family: Georgia,serif;
font-size: clamp(56px,9vw,88px);
font-weight: 700;
color: rgba(242,237,228,.12);
line-height: 1;
}
.tls-17__t {
position: relative;
font-family: Georgia,serif;
font-size: clamp(20px,3vw,27px);
font-weight: 700;
color: var(--tl-ink);
letter-spacing: -.01em;
}
.tls-17__d {
position: relative;
font-size: 13.5px;
line-height: 1.55;
color: #c9c0af;
max-width: 44ch;
text-wrap: pretty;
}
@media (max-width: 560px) {
.tls-17__strip {
flex-direction: column;
overflow: visible;
scroll-snap-type: none;
}
.tls-17__frame {
flex-basis: auto;
aspect-ratio: auto;
min-height: 180px;
}
.tls-17__hint {
display: none;
}
}.tls-17,
.tls-17 *,
.tls-17 *::before,
.tls-17 *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
.tls-17 {
min-height: 100vh;
width: 100%;
--tl-ink: #f2ede4;
--tl-mut: #9a917f;
font-family: 'Segoe UI',system-ui,sans-serif;
background: #0d0b08;
padding: 56px 0 64px;
}
.tls-17__head {
display: flex;
justify-content: space-between;
align-items: baseline;
gap: 16px;
padding: 0 clamp(20px,6vw,64px);
margin-bottom: 26px;
}
.tls-17__h {
font-family: Georgia,serif;
font-size: clamp(20px,3.4vw,28px);
font-weight: 700;
color: var(--tl-ink);
letter-spacing: -.01em;
}
.tls-17__hint {
font-size: 11px;
letter-spacing: .18em;
text-transform: uppercase;
color: var(--tl-mut);
}
.tls-17__strip {
list-style: none;
display: flex;
gap: 22px;
overflow-x: auto;
scroll-snap-type: x mandatory;
scroll-padding-inline: clamp(20px,6vw,64px);
padding: 4px clamp(20px,6vw,64px) 18px;
scrollbar-width: thin;
scrollbar-color: #3d382f transparent;
}
.tls-17__strip:focus-visible {
outline: 2px solid #c8b98a;
outline-offset: 4px;
}
.tls-17__frame {
flex: 0 0 min(560px,88%);
scroll-snap-align: center;
aspect-ratio: 21/10;
display: flex;
flex-direction: column;
justify-content: flex-end;
gap: 6px;
background: var(--g);
border-radius: 6px;
padding: 28px 30px;
position: relative;
overflow: hidden;
}
.tls-17__frame::before {
content: '';
position: absolute;
inset: 0;
background: linear-gradient(180deg,rgba(6,5,3,.25),transparent 35%,transparent 55%,rgba(6,5,3,.72));
}
.tls-17__act {
position: relative;
font-size: 10.5px;
font-weight: 700;
letter-spacing: .24em;
text-transform: uppercase;
color: #cdbd93;
}
.tls-17__no {
position: absolute;
top: 10px;
right: 22px;
font-family: Georgia,serif;
font-size: clamp(56px,9vw,88px);
font-weight: 700;
color: rgba(242,237,228,.12);
line-height: 1;
}
.tls-17__t {
position: relative;
font-family: Georgia,serif;
font-size: clamp(20px,3vw,27px);
font-weight: 700;
color: var(--tl-ink);
letter-spacing: -.01em;
}
.tls-17__d {
position: relative;
font-size: 13.5px;
line-height: 1.55;
color: #c9c0af;
max-width: 44ch;
text-wrap: pretty;
}
@media (max-width: 560px) {
.tls-17__strip {
flex-direction: column;
overflow: visible;
scroll-snap-type: none;
}
.tls-17__frame {
flex-basis: auto;
aspect-ratio: auto;
min-height: 180px;
}
.tls-17__hint {
display: none;
}
}/* No JavaScript — the slide feel is scroll-snap-type:x mandatory + scroll-snap-align:center; the browser owns momentum, touch, keyboard and RTL. */
/* No JavaScript — the slide feel is scroll-snap-type:x mandatory + scroll-snap-align:center; the browser owns momentum, touch, keyboard and RTL. */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: Cinematic Filmstrip Timeline — Scroll-Snap Career Frames
Source: https://codefronts.com/layouts/css-timelines/cinematic-filmstrip/
A widescreen horizontal narrative: each chapter of a career or story is a letterboxed 'frame' in a scroll-snapping strip with chapter numbers, act labels and a progress dot rail driven by scroll-snap alone — the storytelling layout for portfolios, agency case studies and anniversary microsites.
## HTML
```html
<section class="tls-17" aria-label="Filmstrip career timeline demo">
<header class="tls-17__head"><h3 class="tls-17__h">Fifteen years in five frames</h3><p class="tls-17__hint">Scroll sideways →</p></header>
<ol class="tls-17__strip" role="list" tabindex="0" aria-label="Career chapters, horizontally scrollable">
<li class="tls-17__frame" style="--g:linear-gradient(150deg,#1d3a5f,#0a1526)">
<p class="tls-17__act">Act I · <time datetime="2011">2011</time></p>
<p class="tls-17__no" aria-hidden="true">01</p>
<h4 class="tls-17__t">The first studio job</h4>
<p class="tls-17__d">Junior designer. Learned that shipping beats perfect — by shipping something imperfect.</p>
</li>
<li class="tls-17__frame" style="--g:linear-gradient(150deg,#4a2c15,#170d05)">
<p class="tls-17__act">Act II · <time datetime="2015">2015</time></p>
<p class="tls-17__no" aria-hidden="true">02</p>
<h4 class="tls-17__t">Freelance years</h4>
<p class="tls-17__d">Forty clients, three time zones, one folding desk. Rate doubled the year I learned to say no.</p>
</li>
<li class="tls-17__frame" style="--g:linear-gradient(150deg,#14453d,#04120f)">
<p class="tls-17__act">Act III · <time datetime="2019">2019</time></p>
<p class="tls-17__no" aria-hidden="true">03</p>
<h4 class="tls-17__t">Building the team</h4>
<p class="tls-17__d">First design hire → design director. The work became making other people's work better.</p>
</li>
<li class="tls-17__frame" style="--g:linear-gradient(150deg,#4c1d3d,#160414)">
<p class="tls-17__act">Act IV · <time datetime="2023">2023</time></p>
<p class="tls-17__no" aria-hidden="true">04</p>
<h4 class="tls-17__t">The product bet</h4>
<p class="tls-17__d">Left the safe seat to co-found a tool for design systems. Terrifying. Correct.</p>
</li>
<li class="tls-17__frame" style="--g:linear-gradient(150deg,#3d3d14,#121204)">
<p class="tls-17__act">Act V · <time datetime="2026">now</time></p>
<p class="tls-17__no" aria-hidden="true">05</p>
<h4 class="tls-17__t">Still in the credits</h4>
<p class="tls-17__d">10k teams use the thing. The next frame is unwritten on purpose.</p>
</li>
</ol>
</section>
```
## CSS
```css
.tls-17,
.tls-17 *,
.tls-17 *::before,
.tls-17 *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
.tls-17 {
min-height: 100vh;
width: 100%;
--tl-ink: #f2ede4;
--tl-mut: #9a917f;
font-family: 'Segoe UI',system-ui,sans-serif;
background: #0d0b08;
padding: 56px 0 64px;
}
.tls-17__head {
display: flex;
justify-content: space-between;
align-items: baseline;
gap: 16px;
padding: 0 clamp(20px,6vw,64px);
margin-bottom: 26px;
}
.tls-17__h {
font-family: Georgia,serif;
font-size: clamp(20px,3.4vw,28px);
font-weight: 700;
color: var(--tl-ink);
letter-spacing: -.01em;
}
.tls-17__hint {
font-size: 11px;
letter-spacing: .18em;
text-transform: uppercase;
color: var(--tl-mut);
}
.tls-17__strip {
list-style: none;
display: flex;
gap: 22px;
overflow-x: auto;
scroll-snap-type: x mandatory;
scroll-padding-inline: clamp(20px,6vw,64px);
padding: 4px clamp(20px,6vw,64px) 18px;
scrollbar-width: thin;
scrollbar-color: #3d382f transparent;
}
.tls-17__strip:focus-visible {
outline: 2px solid #c8b98a;
outline-offset: 4px;
}
.tls-17__frame {
flex: 0 0 min(560px,88%);
scroll-snap-align: center;
aspect-ratio: 21/10;
display: flex;
flex-direction: column;
justify-content: flex-end;
gap: 6px;
background: var(--g);
border-radius: 6px;
padding: 28px 30px;
position: relative;
overflow: hidden;
}
.tls-17__frame::before {
content: '';
position: absolute;
inset: 0;
background: linear-gradient(180deg,rgba(6,5,3,.25),transparent 35%,transparent 55%,rgba(6,5,3,.72));
}
.tls-17__act {
position: relative;
font-size: 10.5px;
font-weight: 700;
letter-spacing: .24em;
text-transform: uppercase;
color: #cdbd93;
}
.tls-17__no {
position: absolute;
top: 10px;
right: 22px;
font-family: Georgia,serif;
font-size: clamp(56px,9vw,88px);
font-weight: 700;
color: rgba(242,237,228,.12);
line-height: 1;
}
.tls-17__t {
position: relative;
font-family: Georgia,serif;
font-size: clamp(20px,3vw,27px);
font-weight: 700;
color: var(--tl-ink);
letter-spacing: -.01em;
}
.tls-17__d {
position: relative;
font-size: 13.5px;
line-height: 1.55;
color: #c9c0af;
max-width: 44ch;
text-wrap: pretty;
}
@media (max-width: 560px) {
.tls-17__strip {
flex-direction: column;
overflow: visible;
scroll-snap-type: none;
}
.tls-17__frame {
flex-basis: auto;
aspect-ratio: auto;
min-height: 180px;
}
.tls-17__hint {
display: none;
}
}
```
## JavaScript
```js
/* No JavaScript — the slide feel is scroll-snap-type:x mandatory + scroll-snap-align:center; the browser owns momentum, touch, keyboard and RTL. */
```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: Cinematic Filmstrip Timeline — Scroll-Snap Career Frames
Source: https://codefronts.com/layouts/css-timelines/cinematic-filmstrip/
A widescreen horizontal narrative: each chapter of a career or story is a letterboxed 'frame' in a scroll-snapping strip with chapter numbers, act labels and a progress dot rail driven by scroll-snap alone — the storytelling layout for portfolios, agency case studies and anniversary microsites.
## HTML
```html
<section class="tls-17" aria-label="Filmstrip career timeline demo">
<header class="tls-17__head"><h3 class="tls-17__h">Fifteen years in five frames</h3><p class="tls-17__hint">Scroll sideways →</p></header>
<ol class="tls-17__strip" role="list" tabindex="0" aria-label="Career chapters, horizontally scrollable">
<li class="tls-17__frame" style="--g:linear-gradient(150deg,#1d3a5f,#0a1526)">
<p class="tls-17__act">Act I · <time datetime="2011">2011</time></p>
<p class="tls-17__no" aria-hidden="true">01</p>
<h4 class="tls-17__t">The first studio job</h4>
<p class="tls-17__d">Junior designer. Learned that shipping beats perfect — by shipping something imperfect.</p>
</li>
<li class="tls-17__frame" style="--g:linear-gradient(150deg,#4a2c15,#170d05)">
<p class="tls-17__act">Act II · <time datetime="2015">2015</time></p>
<p class="tls-17__no" aria-hidden="true">02</p>
<h4 class="tls-17__t">Freelance years</h4>
<p class="tls-17__d">Forty clients, three time zones, one folding desk. Rate doubled the year I learned to say no.</p>
</li>
<li class="tls-17__frame" style="--g:linear-gradient(150deg,#14453d,#04120f)">
<p class="tls-17__act">Act III · <time datetime="2019">2019</time></p>
<p class="tls-17__no" aria-hidden="true">03</p>
<h4 class="tls-17__t">Building the team</h4>
<p class="tls-17__d">First design hire → design director. The work became making other people's work better.</p>
</li>
<li class="tls-17__frame" style="--g:linear-gradient(150deg,#4c1d3d,#160414)">
<p class="tls-17__act">Act IV · <time datetime="2023">2023</time></p>
<p class="tls-17__no" aria-hidden="true">04</p>
<h4 class="tls-17__t">The product bet</h4>
<p class="tls-17__d">Left the safe seat to co-found a tool for design systems. Terrifying. Correct.</p>
</li>
<li class="tls-17__frame" style="--g:linear-gradient(150deg,#3d3d14,#121204)">
<p class="tls-17__act">Act V · <time datetime="2026">now</time></p>
<p class="tls-17__no" aria-hidden="true">05</p>
<h4 class="tls-17__t">Still in the credits</h4>
<p class="tls-17__d">10k teams use the thing. The next frame is unwritten on purpose.</p>
</li>
</ol>
</section>
```
## CSS
```css
.tls-17,
.tls-17 *,
.tls-17 *::before,
.tls-17 *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
.tls-17 {
min-height: 100vh;
width: 100%;
--tl-ink: #f2ede4;
--tl-mut: #9a917f;
font-family: 'Segoe UI',system-ui,sans-serif;
background: #0d0b08;
padding: 56px 0 64px;
}
.tls-17__head {
display: flex;
justify-content: space-between;
align-items: baseline;
gap: 16px;
padding: 0 clamp(20px,6vw,64px);
margin-bottom: 26px;
}
.tls-17__h {
font-family: Georgia,serif;
font-size: clamp(20px,3.4vw,28px);
font-weight: 700;
color: var(--tl-ink);
letter-spacing: -.01em;
}
.tls-17__hint {
font-size: 11px;
letter-spacing: .18em;
text-transform: uppercase;
color: var(--tl-mut);
}
.tls-17__strip {
list-style: none;
display: flex;
gap: 22px;
overflow-x: auto;
scroll-snap-type: x mandatory;
scroll-padding-inline: clamp(20px,6vw,64px);
padding: 4px clamp(20px,6vw,64px) 18px;
scrollbar-width: thin;
scrollbar-color: #3d382f transparent;
}
.tls-17__strip:focus-visible {
outline: 2px solid #c8b98a;
outline-offset: 4px;
}
.tls-17__frame {
flex: 0 0 min(560px,88%);
scroll-snap-align: center;
aspect-ratio: 21/10;
display: flex;
flex-direction: column;
justify-content: flex-end;
gap: 6px;
background: var(--g);
border-radius: 6px;
padding: 28px 30px;
position: relative;
overflow: hidden;
}
.tls-17__frame::before {
content: '';
position: absolute;
inset: 0;
background: linear-gradient(180deg,rgba(6,5,3,.25),transparent 35%,transparent 55%,rgba(6,5,3,.72));
}
.tls-17__act {
position: relative;
font-size: 10.5px;
font-weight: 700;
letter-spacing: .24em;
text-transform: uppercase;
color: #cdbd93;
}
.tls-17__no {
position: absolute;
top: 10px;
right: 22px;
font-family: Georgia,serif;
font-size: clamp(56px,9vw,88px);
font-weight: 700;
color: rgba(242,237,228,.12);
line-height: 1;
}
.tls-17__t {
position: relative;
font-family: Georgia,serif;
font-size: clamp(20px,3vw,27px);
font-weight: 700;
color: var(--tl-ink);
letter-spacing: -.01em;
}
.tls-17__d {
position: relative;
font-size: 13.5px;
line-height: 1.55;
color: #c9c0af;
max-width: 44ch;
text-wrap: pretty;
}
@media (max-width: 560px) {
.tls-17__strip {
flex-direction: column;
overflow: visible;
scroll-snap-type: none;
}
.tls-17__frame {
flex-basis: auto;
aspect-ratio: auto;
min-height: 180px;
}
.tls-17__hint {
display: none;
}
}
```
## JavaScript
```js
/* No JavaScript — the slide feel is scroll-snap-type:x mandatory + scroll-snap-align:center; the browser owns momentum, touch, keyboard and RTL. */
```How this works
The strip is an <ol> with overflow-x:auto; scroll-snap-type:x mandatory; each frame declares scroll-snap-align:center and flex: 0 0 min(560px, 88%) — frames feel like slides, but it's a native scroll region: momentum, trackpads, touch, keyboard (with tabindex="0" on the scroller) and RTL all behave correctly because the browser owns the physics.
Letterboxing is composition, not decoration: frames are 21:9 aspect-ratio boxes with generous matte padding; the act/chapter chrome (small caps eyebrow, oversized chapter numeral set in a display serif) sits INSIDE the matte, which is what makes it feel cinematic rather than card-like. Edge frames get scroll-margin so snap centers them without clipping.
scroll-padding on the container plus scrollbar-width:thin styling keep the strip polished; the hint text tells users the strip scrolls — horizontal affordance is the pattern's known weakness, so SAY it. A media query stacks frames vertically under 560px, where horizontal strips underperform.
Techniques used: scroll-snap-type:x mandatory + scroll-snap-align:center native slide feel · flex-basis min() frame sizing · 21:9 aspect-ratio letterbox composition · keyboard-scrollable region via tabindex + focus ring · scroll-padding/scroll-margin edge handling · vertical stacking fallback below 560px
Make it yours
- Frame size: the
min(560px, 88%)basis — 88% keeps a peek of the next frame visible, the strongest 'this scrolls' affordance there is. - Snap strictness: mandatory vs proximity — proximity lets users rest between frames; mandatory is more 'slideshow'.
- Chapter numerals: styled with a Georgia display stack here; swap in a brand serif for editorial builds.
- Backgrounds: gradient placeholders ship — swap for stills with object-fit:cover imgs and keep the scrim for text contrast.
Gotchas — read before shipping
- The scroller needs
tabindex="0"(and a visible focus ring) or keyboard users cannot move it at all — the #1 shipped bug in scroll-snap galleries. - mandatory snap + frames taller than the viewport traps scroll — that's why the strip caps frame height and falls back to vertical stacking on small screens.
- Don't hide the scrollbar entirely; a thin styled one is an affordance. scrollbar-width:none + no hint = users never find frames 3–6.
- scroll-snap-stop:always prevents fast swipes skipping frames on touch — add it if each frame is essential reading.
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.
Scroll snap has been baseline since 2019; scrollbar-width styling is 2024-baseline in Safari and harmless where unsupported.