FSD Frontend System Design

Part V β€” The Craft Β· Lesson 10.1

Image Slider

In one lineOne transform on a track, plus autoplay, swipe, preload, and the accessibility everyone forgets.

Where you've seen itThe homepage banner that rotates every five seconds and keeps changing while you're reading it.

  • Time16 min
  • DiagramTrack offset model + infinite loop with clones
  • Chapter10 Β· Low Level Design

Diagram

diagram
THE TRACK MODEL β€” move one element, not four

  viewport (overflow: hidden)
  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
  β”‚   slide 2     β”‚
  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
  β”‚   slide 1     β”‚   slide 2     β”‚   slide 3     β”‚  slide 4   β”‚
  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
   ◄── transform: translateX(-100% Γ— index) ──►

  transform + opacity only: composited, no layout, 60fps (12.4)
diagram
INFINITE LOOP β€” clones and the silent jump

  [4'] [1] [2] [3] [4] [1']      ' = clone
   β–²                    β–²
   clone of 4          clone of 1

  at slide 4 β†’ animate to [1'] β†’ on transitionend, jump to [1]
  with transition disabled. The user never sees the reset.
diagram
AUTOPLAY RULES β€” or it becomes an accessibility violation

  pause on hover                 user is reading
  pause on focus within          keyboard user is in it
  pause when tab hidden          visibilitychange β€” saves battery
  stop permanently after         explicit intent beats automation
    manual interaction
  respect prefers-reduced-motion no auto-advance at all
  always provide play/pause      WCAG 2.2.2 for anything > 5s

How it works

  1. Lay slides in a flex row inside an overflow: hidden viewport and move the track with translateX.
  2. Animate transform only β€” left or margin triggers layout on every frame (12.4).
  3. Preload neighbours: current, next, previous eagerly; everything else loading="lazy".
  4. Handle swipe with pointer events, tracking delta and velocity; commit past ~25% of width or on a fast flick.
  5. Make it accessible: role="region" with aria-roledescription="carousel", real <button> controls, a live region announcing "Slide 2 of 5", and full keyboard support.
js
const onPointerMove = (e) => {
  if (!dragging) return;
  const dx = e.clientX - startX;
  track.style.transform = `translateX(calc(${-index * 100}% + ${dx}px))`;
};
const onPointerUp = (e) => {
  const dx = e.clientX - startX;
  const commit = Math.abs(dx) > width * 0.25;
  setIndex(clamp(index + (commit ? -Math.sign(dx) : 0), 0, last));
};

Trade-offs

ApproachNotes
CSS scroll-snapFree momentum + a11y; less control over autoplay
JS transform trackFull control; you own everything
Clone-based infiniteSeamless; edge cases on rapid clicks
Non-infiniteSimplest, and honestly fine for most banners

Worth saying in an interview: carousels perform poorly in most published usability data β€” users rarely see past slide one. Suggesting a static hero or a scrollable row of cards instead of a rotating banner is a legitimate senior answer.

Interview angle

"Build an image carousel."

  • Track with a single translateX, transform-only animation, neighbour preloading, and pointer-based swipe with a commit threshold.
  • Autoplay pauses on hover, focus, and hidden tab, stops after interaction, and honours reduced motion.
  • Mention CSS scroll-snap as the simpler path when autoplay isn't required β€” it gives you momentum and accessibility for free.

Recap

  • Move one track with transform; never animate layout properties.
  • Preload neighbours, lazy-load the rest.
  • Autoplay without pause controls is an accessibility failure, not a feature.