A reliable foundation
The scroll port settles each card into a predictable position.
Combine scroll snap with ::scroll-button(), ::scroll-marker, and ::scroll-marker-group to build a native, accessible slider controlled entirely by CSS.
Drag, use the wheel, keyboard, or the controls generated by the browser.
Your browser does not render the experimental buttons or markers yet. Scrolling and scroll snap remain available as the fallback.
<ul class="carousel" aria-label="Native CSS carousel examples" tabindex="0">
<li
class="slide"
style="--slide-accent: #ffb38a"
>
<article class="slide-card">
<p class="slide-eyebrow">01 · Scroll snap</p>
<h2>A reliable foundation</h2>
<p>The scroll port settles each card into a predictable position.</p>
</article>
</li>
<li
class="slide"
style="--slide-accent: #72d6c9"
>
<article class="slide-card">
<p class="slide-eyebrow">02 · Scroll buttons</p>
<h2>Controls from CSS</h2>
<p>The browser creates real previous and next buttons without extra markup.</p>
</article>
</li>
<li
class="slide"
style="--slide-accent: #b6a8ff"
>
<article class="slide-card">
<p class="slide-eyebrow">03 · Scroll markers</p>
<h2>Native pagination</h2>
<p>Every slide generates a marker that navigates to its own scroll target.</p>
</article>
</li>
<li
class="slide"
style="--slide-accent: #f7a6c4"
>
<article class="slide-card">
<p class="slide-eyebrow">04 · Target current</p>
<h2>State without scripts</h2>
<p>The current marker follows the visible slide through :target-current.</p>
</article>
</li>
</ul><ul class="flex flex-row gap-[16px] [inline-size:min(100%,_56rem)] m-0 p-0 overflow-x-auto [overscroll-behavior-inline:contain] scroll-smooth motion-reduce:scroll-auto [scroll-snap-type:inline_mandatory] [scroll-marker-group:after] [anchor-name:--carousel] [&::scroll-marker-group]:flex [&::scroll-marker-group]:flex-row [&::scroll-marker-group]:items-center [&::scroll-marker-group]:justify-center [&::scroll-marker-group]:gap-3 [&::scroll-marker-group]:pt-5 [&::scroll-marker-group]:pb-2 [&::scroll-button(*)]:absolute [&::scroll-button(*)]:[position-anchor:--carousel] [&::scroll-button(*)]:z-[1] [&::scroll-button(*)]:grid [&::scroll-button(*)]:place-items-center [&::scroll-button(*)]:size-10 [&::scroll-button(*)]:border-0 [&::scroll-button(*)]:rounded-full [&::scroll-button(*)]:bg-[#172033] [&::scroll-button(*)]:text-white [&::scroll-button(*)]:[font:inherit] [&::scroll-button(*)]:text-[1.15rem] [&::scroll-button(*)]:shadow-[0_0.75rem_2rem_rgb(0_0_0/0.24)] [&::scroll-button(*)]:cursor-pointer [&::scroll-button(*)]:transition-[background-color,opacity,scale] [&::scroll-button(*)]:duration-200 [&::scroll-button(*):hover]:bg-[#7c5cff] [&::scroll-button(*):hover]:scale-[1.08] [&::scroll-button(*):focus-visible]:[outline:3px_solid_color-mix(in_oklch,_#7c5cff_42%,_transparent)] [&::scroll-button(*):focus-visible]:outline-offset-3 [&::scroll-button(*):disabled]:opacity-[0.35] [&::scroll-button(*):disabled]:cursor-not-allowed [&::scroll-button(inline-start)]:content-['←'] [&::scroll-button(inline-start)]:[position-area:left_center] [&::scroll-button(inline-start)]:[translate:60%] [&::scroll-button(inline-end)]:content-['→'] [&::scroll-button(inline-end)]:[position-area:right_center] [&::scroll-button(inline-end)]:[translate:-60%]" aria-label="Native CSS carousel examples" tabindex="0">
<li class="list-none flex-[0_0_84%] [scroll-snap-align:center] [scroll-snap-stop:always] [&::scroll-marker]:content-[''] [&::scroll-marker]:[inline-size:20px] [&::scroll-marker]:[block-size:20px] [&::scroll-marker]:border-2 [&::scroll-marker]:border-[#64748b] [&::scroll-marker]:rounded-full [&::scroll-marker]:bg-white [&::scroll-marker]:cursor-pointer [&::scroll-marker]:transition-[background-color,border-color,scale] [&::scroll-marker]:duration-200 [&::scroll-marker:hover]:border-[#7c5cff] [&::scroll-marker:hover]:bg-[color-mix(in_oklch,#7c5cff_18%,#fff)] [&::scroll-marker:focus-visible]:[outline:3px_solid_color-mix(in_oklch,_#7c5cff_38%,_transparent)] [&::scroll-marker:focus-visible]:outline-offset-2 [&::scroll-marker:target-current]:border-[#172033] [&::scroll-marker:target-current]:bg-[#172033] [&::scroll-marker:target-current]:shadow-[0_0_0_2px_#fff,0_0_0_4px_#172033] [&::scroll-marker:target-current]:scale-110">
<article class="box-border grid content-end gap-2 min-block-[18rem] p-[clamp(1.5rem,5vw,3rem)] border border-[color-mix(in_oklch,#ffb38a_72%,#172033)] rounded-[1.5rem] bg-[#ffb38a] text-[#172033] *:m-0 [&_h2]:max-inline-[16ch] [&_h2]:text-[clamp(1.75rem,6vw,3.5rem)] [&_h2]:leading-none [&_h2]:text-balance">
<p class="text-[rgb(23_32_51/0.72)] font-bold tracking-[0.08em] uppercase">01 · Scroll snap</p>
<h2>A reliable foundation</h2>
<p>The scroll port settles each card into a predictable position.</p>
</article>
</li>
<li class="list-none flex-[0_0_84%] [scroll-snap-align:center] [scroll-snap-stop:always] [&::scroll-marker]:content-[''] [&::scroll-marker]:[inline-size:20px] [&::scroll-marker]:[block-size:20px] [&::scroll-marker]:border-2 [&::scroll-marker]:border-[#64748b] [&::scroll-marker]:rounded-full [&::scroll-marker]:bg-white [&::scroll-marker]:cursor-pointer [&::scroll-marker]:transition-[background-color,border-color,scale] [&::scroll-marker]:duration-200 [&::scroll-marker:hover]:border-[#7c5cff] [&::scroll-marker:hover]:bg-[color-mix(in_oklch,#7c5cff_18%,#fff)] [&::scroll-marker:focus-visible]:[outline:3px_solid_color-mix(in_oklch,_#7c5cff_38%,_transparent)] [&::scroll-marker:focus-visible]:outline-offset-2 [&::scroll-marker:target-current]:border-[#172033] [&::scroll-marker:target-current]:bg-[#172033] [&::scroll-marker:target-current]:shadow-[0_0_0_2px_#fff,0_0_0_4px_#172033] [&::scroll-marker:target-current]:scale-110">
<article class="box-border grid content-end gap-2 min-block-[18rem] p-[clamp(1.5rem,5vw,3rem)] border border-[color-mix(in_oklch,#72d6c9_72%,#172033)] rounded-[1.5rem] bg-[#72d6c9] text-[#172033] *:m-0 [&_h2]:max-inline-[16ch] [&_h2]:text-[clamp(1.75rem,6vw,3.5rem)] [&_h2]:leading-none [&_h2]:text-balance">
<p class="text-[rgb(23_32_51/0.72)] font-bold tracking-[0.08em] uppercase">02 · Scroll buttons</p>
<h2>Controls from CSS</h2>
<p>The browser creates real previous and next buttons without extra markup.</p>
</article>
</li>
<li class="list-none flex-[0_0_84%] [scroll-snap-align:center] [scroll-snap-stop:always] [&::scroll-marker]:content-[''] [&::scroll-marker]:[inline-size:20px] [&::scroll-marker]:[block-size:20px] [&::scroll-marker]:border-2 [&::scroll-marker]:border-[#64748b] [&::scroll-marker]:rounded-full [&::scroll-marker]:bg-white [&::scroll-marker]:cursor-pointer [&::scroll-marker]:transition-[background-color,border-color,scale] [&::scroll-marker]:duration-200 [&::scroll-marker:hover]:border-[#7c5cff] [&::scroll-marker:hover]:bg-[color-mix(in_oklch,#7c5cff_18%,#fff)] [&::scroll-marker:focus-visible]:[outline:3px_solid_color-mix(in_oklch,_#7c5cff_38%,_transparent)] [&::scroll-marker:focus-visible]:outline-offset-2 [&::scroll-marker:target-current]:border-[#172033] [&::scroll-marker:target-current]:bg-[#172033] [&::scroll-marker:target-current]:shadow-[0_0_0_2px_#fff,0_0_0_4px_#172033] [&::scroll-marker:target-current]:scale-110">
<article class="box-border grid content-end gap-2 min-block-[18rem] p-[clamp(1.5rem,5vw,3rem)] border border-[color-mix(in_oklch,#b6a8ff_72%,#172033)] rounded-[1.5rem] bg-[#b6a8ff] text-[#172033] *:m-0 [&_h2]:max-inline-[16ch] [&_h2]:text-[clamp(1.75rem,6vw,3.5rem)] [&_h2]:leading-none [&_h2]:text-balance">
<p class="text-[rgb(23_32_51/0.72)] font-bold tracking-[0.08em] uppercase">03 · Scroll markers</p>
<h2>Native pagination</h2>
<p>Every slide generates a marker that navigates to its own scroll target.</p>
</article>
</li>
<li class="list-none flex-[0_0_84%] [scroll-snap-align:center] [scroll-snap-stop:always] [&::scroll-marker]:content-[''] [&::scroll-marker]:[inline-size:20px] [&::scroll-marker]:[block-size:20px] [&::scroll-marker]:border-2 [&::scroll-marker]:border-[#64748b] [&::scroll-marker]:rounded-full [&::scroll-marker]:bg-white [&::scroll-marker]:cursor-pointer [&::scroll-marker]:transition-[background-color,border-color,scale] [&::scroll-marker]:duration-200 [&::scroll-marker:hover]:border-[#7c5cff] [&::scroll-marker:hover]:bg-[color-mix(in_oklch,#7c5cff_18%,#fff)] [&::scroll-marker:focus-visible]:[outline:3px_solid_color-mix(in_oklch,_#7c5cff_38%,_transparent)] [&::scroll-marker:focus-visible]:outline-offset-2 [&::scroll-marker:target-current]:border-[#172033] [&::scroll-marker:target-current]:bg-[#172033] [&::scroll-marker:target-current]:shadow-[0_0_0_2px_#fff,0_0_0_4px_#172033] [&::scroll-marker:target-current]:scale-110">
<article class="box-border grid content-end gap-2 min-block-[18rem] p-[clamp(1.5rem,5vw,3rem)] border border-[color-mix(in_oklch,#f7a6c4_72%,#172033)] rounded-[1.5rem] bg-[#f7a6c4] text-[#172033] *:m-0 [&_h2]:max-inline-[16ch] [&_h2]:text-[clamp(1.75rem,6vw,3.5rem)] [&_h2]:leading-none [&_h2]:text-balance">
<p class="text-[rgb(23_32_51/0.72)] font-bold tracking-[0.08em] uppercase">04 · Target current</p>
<h2>State without scripts</h2>
<p>The current marker follows the visible slide through :target-current.</p>
</article>
</li>
</ul>.carousel {
display: flex;
flex-direction: row;
gap: 16px;
inline-size: min(100%, 56rem);
margin: 0;
padding: 0;
overflow-x: auto;
overscroll-behavior-inline: contain;
scroll-behavior: smooth;
scroll-snap-type: inline mandatory;
scroll-marker-group: after;
anchor-name: --carousel;
}
.carousel > .slide {
list-style: none;
flex: 0 0 84%;
scroll-snap-align: center;
scroll-snap-stop: always;
}
.slide-card {
box-sizing: border-box;
display: grid;
align-content: end;
gap: 0.5rem;
min-block-size: 18rem;
padding: clamp(1.5rem, 5vw, 3rem);
border: 1px solid color-mix(in oklch, var(--slide-accent) 72%, #172033);
border-radius: 1.5rem;
background: var(--slide-accent);
color: #172033;
}
.slide-card > * {
margin: 0;
}
.slide-card h2 {
max-inline-size: 16ch;
font-size: clamp(1.75rem, 6vw, 3.5rem);
line-height: 1;
text-wrap: balance;
}
.slide-eyebrow {
color: rgb(23 32 51 / 0.72);
font-weight: 700;
letter-spacing: 0.08em;
text-transform: uppercase;
}
.carousel::scroll-marker-group {
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
gap: 0.75rem;
padding-block: 1.25rem 0.5rem;
}
.carousel > .slide::scroll-marker {
content: "";
inline-size: 20px;
block-size: 20px;
border: 2px solid #64748b;
border-radius: 50%;
background: #fff;
cursor: pointer;
transition: background-color 0.2s, border-color 0.2s, scale 0.2s;
}
.carousel > .slide::scroll-marker:hover {
border-color: #7c5cff;
background: color-mix(in oklch, #7c5cff 18%, #fff);
}
.carousel > .slide::scroll-marker:focus-visible {
outline: 3px solid color-mix(in oklch, #7c5cff 38%, transparent);
outline-offset: 2px;
}
.carousel > .slide::scroll-marker:target-current {
border-color: #172033;
background: #172033;
box-shadow: 0 0 0 2px #fff, 0 0 0 4px #172033;
scale: 1.1;
}
.carousel::scroll-button(*) {
position: absolute;
position-anchor: --carousel;
z-index: 1;
display: grid;
place-items: center;
inline-size: 2.5rem;
block-size: 2.5rem;
border: 0;
border-radius: 50%;
background: #172033;
color: #fff;
font: inherit;
font-size: 1.15rem;
box-shadow: 0 0.75rem 2rem rgb(0 0 0 / 0.24);
cursor: pointer;
transition: background-color 0.2s, opacity 0.2s, scale 0.2s;
}
.carousel::scroll-button(*):hover {
background: #7c5cff;
scale: 1.08;
}
.carousel::scroll-button(*):focus-visible {
outline: 3px solid color-mix(in oklch, #7c5cff 42%, transparent);
outline-offset: 3px;
}
.carousel::scroll-button(*):disabled {
opacity: 0.35;
cursor: not-allowed;
}
.carousel::scroll-button(inline-start) {
content: "←";
position-area: left center;
translate: 60%;
}
.carousel::scroll-button(inline-end) {
content: "→";
position-area: right center;
translate: -60%;
}
@media (prefers-reduced-motion: reduce) {
.carousel {
scroll-behavior: auto;
}
}.carousel {
display: flex;
flex-direction: row;
gap: 16px;
inline-size: min(100%, 56rem);
margin: 0;
padding: 0;
overflow-x: auto;
overscroll-behavior-inline: contain;
scroll-behavior: smooth;
scroll-snap-type: inline mandatory;
scroll-marker-group: after;
anchor-name: --carousel;
> .slide {
list-style: none;
flex: 0 0 84%;
scroll-snap-align: center;
scroll-snap-stop: always;
}
&::scroll-marker-group {
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
gap: 0.75rem;
padding-block: 1.25rem 0.5rem;
}
> .slide::scroll-marker {
content: "";
inline-size: 20px;
block-size: 20px;
border: 2px solid #64748b;
border-radius: 50%;
background: #fff;
cursor: pointer;
transition: background-color 0.2s, border-color 0.2s, scale 0.2s;
}
> .slide::scroll-marker:hover {
border-color: #7c5cff;
background: color-mix(in oklch, #7c5cff 18%, #fff);
}
> .slide::scroll-marker:focus-visible {
outline: 3px solid color-mix(in oklch, #7c5cff 38%, transparent);
outline-offset: 2px;
}
> .slide::scroll-marker:target-current {
border-color: #172033;
background: #172033;
box-shadow: 0 0 0 2px #fff, 0 0 0 4px #172033;
scale: 1.1;
}
&::scroll-button(*) {
position: absolute;
position-anchor: --carousel;
z-index: 1;
display: grid;
place-items: center;
inline-size: 2.5rem;
block-size: 2.5rem;
border: 0;
border-radius: 50%;
background: #172033;
color: #fff;
font: inherit;
font-size: 1.15rem;
box-shadow: 0 0.75rem 2rem rgb(0 0 0 / 0.24);
cursor: pointer;
transition: background-color 0.2s, opacity 0.2s, scale 0.2s;
}
&::scroll-button(*):hover {
background: #7c5cff;
scale: 1.08;
}
&::scroll-button(*):focus-visible {
outline: 3px solid color-mix(in oklch, #7c5cff 42%, transparent);
outline-offset: 3px;
}
&::scroll-button(*):disabled {
opacity: 0.35;
cursor: not-allowed;
}
&::scroll-button(inline-start) {
content: "←";
position-area: left center;
translate: 60%;
}
&::scroll-button(inline-end) {
content: "→";
position-area: right center;
translate: -60%;
}
@media (prefers-reduced-motion: reduce) {
scroll-behavior: auto;
}
}
.slide-card {
box-sizing: border-box;
display: grid;
align-content: end;
gap: 0.5rem;
min-block-size: 18rem;
padding: clamp(1.5rem, 5vw, 3rem);
border: 1px solid color-mix(in oklch, var(--slide-accent) 72%, #172033);
border-radius: 1.5rem;
background: var(--slide-accent);
color: #172033;
> * {
margin: 0;
}
h2 {
max-inline-size: 16ch;
font-size: clamp(1.75rem, 6vw, 3.5rem);
line-height: 1;
text-wrap: balance;
}
}
.slide-eyebrow {
color: rgb(23 32 51 / 0.72);
font-weight: 700;
letter-spacing: 0.08em;
text-transform: uppercase;
}