Trendproof alternatief voor carrousels: een horizontale scroller met kaarten, gebouwd met CSS Scroll Snap. Werkt met swipe, muiswiel en toetsenbord. Geen JavaScript nodig.
Plaats deze sectie waar je de slider wil. De “bolletjes” zijn gewoon links naar elke kaart.
<section class="cardslider" aria-label="Aanbevolen projecten">
<!-- Track: focusbaar voor toetsenbordscroll -->
<div class="track" id="cards-track" tabindex="0">
<article class="card" id="card-1">
<img src="assets/img/proj1-640.jpg" width="640" height="360" alt="Poster 4GT — kleurrijke typografie">
<div class="card-body">
<h3 class="h6 mb-1">Poster 4GT</h3>
<p class="mb-2 small">Kleurstudie en typografie-experiment.</p>
<a class="btn btn-sm btn-primary" href="#">Bekijk</a>
</div>
</article>
<article class="card" id="card-2">
<img src="assets/img/proj2-640.jpg" width="640" height="360" loading="lazy" alt="Packaging — doosje met patroon">
<div class="card-body">
<h3 class="h6 mb-1">Packaging</h3>
<p class="mb-2 small">Kartonnen doosje met eigen patroon.</p>
<a class="btn btn-sm btn-primary" href="#">Bekijk</a>
</div>
</article>
<article class="card" id="card-3">
<img src="assets/img/proj3-640.jpg" width="640" height="360" loading="lazy" alt="Web lay-out — landing met hero en kaarten">
<div class="card-body">
<h3 class="h6 mb-1">Web lay-out</h3>
<p class="mb-2 small">Hero + kaarten, mobile-first.</p>
<a class="btn btn-sm btn-primary" href="#">Bekijk</a>
</div>
</article>
</div>
<!-- Dots: ankernavigatie zonder JS -->
<nav class="dots" aria-label="Slides">
<a href="#card-1" aria-label="Ga naar kaart 1"></a>
<a href="#card-2" aria-label="Ga naar kaart 2"></a>
<a href="#card-3" aria-label="Ga naar kaart 3"></a>
</nav>
</section>
assets/css/custom.cssScroll-snap, kaartstijl, subtiele rand-masker en toegankelijke focus.
/* ==== Cardslider (CSS-only) ==== */
.cardslider{
--gap: 1rem;
--card-w: clamp(260px, 45vw, 360px);
--edge-fade: 24px; /* hint dat er meer is */
position: relative;
}
/* Horizontale track met scroll snap */
.cardslider .track{
display: grid;
grid-auto-flow: column;
grid-auto-columns: var(--card-w);
gap: var(--gap);
overflow-x: auto;
padding: .5rem;
scroll-snap-type: x mandatory;
scroll-behavior: smooth; /* prettige pijtjes/anker-scroll */
-webkit-overflow-scrolling: touch;
/* rand-fade met mask (verbergt harde randen) */
mask-image: linear-gradient(to right,
transparent 0, black var(--edge-fade),
black calc(100% - var(--edge-fade)), transparent 100%);
}
/* Kaarten */
.cardslider .card{
scroll-snap-align: start;
border: 1px solid rgba(0,0,0,.08);
border-radius: .75rem;
background: var(--brand-surface);
box-shadow: 0 8px 24px rgba(0,0,0,.06);
overflow: hidden;
display: flex; flex-direction: column;
}
/* Afbeelding netjes schalen */
.cardslider .card img{
display: block; width: 100%;
aspect-ratio: 16 / 9; object-fit: cover;
}
/* Body */
.cardslider .card .card-body{ padding: .75rem; }
/* Dots (ankers) */
.cardslider .dots{
display: flex; justify-content: center; gap: .5rem;
margin-top: .5rem;
}
.cardslider .dots a{
inline-size: .6rem; block-size: .6rem;
border-radius: 50%;
background: color-mix(in oklab, var(--brand-text), white 70%);
display: inline-block;
text-decoration: none;
outline: none;
}
.cardslider .dots a:hover,
.cardslider .dots a:focus{
background: var(--brand-primary);
box-shadow: 0 0 0 .2rem color-mix(in oklab, var(--brand-primary), white 70%);
}
/* Active dot met :has() op het target (moderne browsers) */
@supports (selector(:has(*))){
.cardslider:has(#card-1:target) .dots a[href="#card-1"],
.cardslider:has(#card-2:target) .dots a[href="#card-2"],
.cardslider:has(#card-3:target) .dots a[href="#card-3"]{
background: var(--brand-primary);
}
/* Als niets getarget is, maak eerste dot actief */
.cardslider:not(:has(.card:target)) .dots a[href="#card-1"]{
background: var(--brand-primary);
}
}
/* Toetsenbord: focus zichtbaar op track en kaarten */
.cardslider .track:focus{ outline: 2px solid var(--brand-primary); outline-offset: 2px; }
.cardslider .card:focus-within{ box-shadow: 0 0 0 .2rem color-mix(in oklab, var(--brand-primary), white 70%); }
/* Minder beweging = minder smooth scroll */
@media (prefers-reduced-motion: reduce){
.cardslider .track{ scroll-behavior: auto; }
}
scroll-snap-type, scroll-behavior en (optioneel) :has().prefers-reduced-motion respecteren (smooth scroll uit bij reduce).