Image Carousel
by Pratik Rai
Read the full write-upBuild an image carousel that shows one image at a time from a fixed list, with arrows to step through them and a row of dots showing where you are.
The images and the styling are already in the starter. What is missing is the behaviour.
Requirements
Show one image at a time, starting with the first.
The ‹ and › arrows move back and forward by one image.
Both arrows wrap around: forward from the last image lands on the first, back from the first lands on the last.
Render one dot per image, and mark the dot for the image on screen so it is distinguishable from the rest.
Clicking a dot jumps straight to that image.
Every control is a real
<button>with an accessible name, so the carousel works from the keyboard.
Notes
The focus is behaviour, not visual polish —
styles.cssalready covers the layout, and.dot-activeis waiting for you.No data fetching and no libraries: the images are defined in
App.js, and the preview only has React available.Sliding animations, autoplay and swipe are out of scope. They are the natural follow-ups once this works.
Hints
Image Carousel (reference solution)
One way to do it, not the only way. Yours passes if it renders.
Solution files
1import { useState } from 'react';
2
3// Four slides, drawn inline so the preview never waits on a network.
4function slide(label, color) {
5 return (
6 'data:image/svg+xml;utf8,' +
7 encodeURIComponent(
8 '<svg xmlns="http://www.w3.org/2000/svg" width="640" height="360">' +
9 '<rect width="640" height="360" fill="' + color + '"/>' +
10 '<text x="320" y="196" fill="#ffffff" font-family="sans-serif" ' +
11 'font-size="44" font-weight="700" text-anchor="middle">' + label + '</text>' +
12 '</svg>'
13 )
14 );
15}
16
17const IMAGES = [
18 { src: slide('Mountains', '#2563eb'), alt: 'Illustration of blue mountains' },
19 { src: slide('Desert', '#d97706'), alt: 'Illustration of an orange desert' },
20 { src: slide('Forest', '#16a34a'), alt: 'Illustration of a green forest' },
21 { src: slide('Ocean', '#0891b2'), alt: 'Illustration of a teal ocean' },
22];
23
24export default function App() {
25 // One number is the entire state of a carousel. Anything else — the visible
26 // image, which dot is filled, what each arrow does next — is derived from it.
27 const [index, setIndex] = useState(0);
28
29 // Modular arithmetic is what makes the ends wrap. Adding IMAGES.length before
30 // the modulo keeps the backward step positive: in JavaScript -1 % 4 is -1,
31 // not 3.
32 const go = (step) =>
33 setIndex((current) => (current + step + IMAGES.length) % IMAGES.length);
34
35 const image = IMAGES[index];
36
37 return (
38 <div className="carousel">
39 <div className="stage">
40 <img className="slide" src={image.src} alt={image.alt} />
41
42 <button
43 className="arrow arrow-prev"
44 aria-label="Previous image"
45 onClick={() => go(-1)}
46 >
47 ‹
48 </button>
49 <button
50 className="arrow arrow-next"
51 aria-label="Next image"
52 onClick={() => go(1)}
53 >
54 ›
55 </button>
56 </div>
57
58 <div className="dots">
59 {IMAGES.map((item, i) => (
60 <button
61 key={item.src}
62 className={'dot' + (i === index ? ' dot-active' : '')}
63 // The dots are a set of choices with one selected, which is what
64 // aria-current describes. Without it the only thing separating the
65 // active dot from the rest is its colour.
66 aria-current={i === index ? 'true' : undefined}
67 aria-label={'Go to image ' + (i + 1)}
68 onClick={() => setIndex(i)}
69 />
70 ))}
71 </div>
72 </div>
73 );
74}How it works
A carousel is a list plus a cursor. Hold the cursor — the index — in state, derive everything else from it, and the arrows and dots stop being separate features: they are two ways of moving the same number.
The one trap is the backward step. -1 % 4 is -1 in JavaScript, not 3, because the remainder takes the sign of the dividend. Adding the length before taking the modulo keeps the value positive, which is why go reads (current + step + IMAGES.length) % IMAGES.length instead of the shorter version.
Using <button> for the arrows and dots is not decoration. Buttons are focusable, fire on Enter and Space, and are announced as controls; a <div onClick> is none of those things, and a UI round will ask about it.