Gallery✨ Premium

Dots Navigation

Carousel without arrows: a row of dots laid over the bottom of the image, one per slide; clicking a dot slides the track straight to that image.

CSSDotsClick

Updated

This effect is part of Effect.Labs — 840 vanilla effects, some free, some premium. Gallery has 44 effects, including 6 free. Explore the category →

3 usage examples

Photos of a hiking route — page of an online trail guide — Dots Navigation example 1

① Photos of a hiking route — page of an online trail guide

WhenOn the loop's page, the hiker clicks the third dot to see the shepherd's hut before downloading the GPX track; the line "Photo 3 of 5 · break at the hut" updates.
WhyThe five photos follow the route, and the dots show at a glance how many there are and where each one sits: you jump straight to the stage you care about, without scrolling through the others.
SettingsFrame 282 px wide fitted to the column: --dtnv-width:100%, --dtnv-height:100%, --dtnv-radius:14px, demo area cleared (--dtnv-stage-bg:transparent, padding and minimum height at 0). Photos as <img>, caption as a pill at the top left. Capture: the scene script clicks the next dot every 1.4 s.
Gestures of a music player — help screens swiped with a finger — Dots Navigation example 2

② Gestures of a music player — help screens swiped with a finger

WhenOn first launch, the help fills the phone screen; the user swipes left to move from double tap to the next gesture, and the dots above the "Got it" button show two are left.
WhyOn mobile the finger is enough to move on, and the dots act as a landmark rather than the main control. No arrows to aim at on a narrow screen, and four circles are enough to tell how long the help is.
SettingsFull phone screen: --dtnv-height:100%, --dtnv-radius:0, white background and purple dots --dtnv-dot-color:#6d5bd0. In the capture, the scene sends the real code a pointerdown then a pointerup 120 px further left (purple circle = the gesture).
Readings from a connected beehive — tile in a beekeeping app — Dots Navigation example 3

③ Readings from a connected beehive — tile in a beekeeping app

WhenIn the apiary list, the tile of hive 2 shows the weight over seven days; clicking the second dot shows the brood temperature, the third the humidity.
WhyThree measurements from one sensor fit in a 236 px tile with no tabs or menu: the dots say there are three views and leave the space to the chart.
Settings--dtnv-height:190px, --dtnv-ratio:auto, --dtnv-radius:10px, cream background and amber dots --dtnv-dot-color:#b7791f. Each slide holds a label, a value and an SVG chart as <img>. Capture: click on the next dot every 1.3 s.

How it works

Navigation goes through dots, not arrows: that is what sets this effect apart from the Vertical Slider in the same category, which keeps arrows and moves up or down. The script counts the .dtnv-slide elements and creates one button per slide inside .dtnv-dots. Clicking the fourth dot brings the fourth image directly, without stepping through the previous ones: the horizontal .dtnv-track gets translateX(-300%) and slides in 0.5 s.

The dot of the visible image fills in and grows slightly (is-active, scale(1.2)); the others stay 10 px white rings. Each dot is a 24 × 24 px button, big enough for a thumb, even though the drawn ring is small.

Two gestures complete the dots: the ← and → keys (focus then follows the active dot) and a horizontal drag of more than 50 px by finger or mouse, handled with Pointer Events. Those two move one step and start over from the first image after the last. No animation starts without someone acting.

Adding or removing an image needs no change to the dots: they are rebuilt from the number of slides on load. A photo goes in as an <img> inside the slide (full coverage with object-fit: cover). Each .dtnv-carousel has its own instance; nothing is exposed on window.

Accessibility

  • Reachable dots: real <button type="button"> elements in the Tab order, named "Slide 1", "Slide 2"…; the one for the visible image carries aria-current="true".
  • Carousel described: role="region" + aria-roledescription="carousel" on the frame, and for each image a "slide" group named "2 of 5".
  • Nothing hidden is reachable: inert and aria-hidden on images outside the frame.
  • Change announced: invisible aria-live="polite" area ("slide 4 of 5"), silent on load.
  • Reduced motion: no sliding under prefers-reduced-motion: reduce; the clicked dot still shows the right image.

Browser compatibility

A little over 4 KB of standalone JavaScript. Checked on 08/11/2026 with Playwright (Chromium, Firefox, WebKit): dots reached with Tab and activated with Enter, → key from a dot, two independent carousels, script run twice without duplicate dots, finger swipe at 390 px (Chromium) and mouse drag, reduced motion, no errors. Minimum versions from @mdn/browser-compat-data 8.1.8; the most demanding is inert.

Chrome 102+✓ Full (measured)
Firefox 112+✓ Full (measured)
Safari 15.5+✓ Full (measured in WebKit)
Edge 102+✓ Full (Chromium engine)
iOS Safari 15.5+✓ Not checked on a real device
Android Chrome 102+✓ Full (Chromium engine)

Before inert, the carousel works but a link in a hidden image can still be reached by keyboard. Without aspect-ratio (Safari 14), set --dtnv-height. Without JavaScript, no dot is created and only the first image shows. In Safari on Mac, Tab only stops on buttons when the "Press Tab to highlight each item" option is on (a system setting, not the effect).

The code

HTML structure to paste into your page (the CSS + JS come with your purchase):

index.html — structure
<div class="fx-0164-root">
  <div class="dtnv-carousel" role="region" aria-roledescription="carousel" aria-label="Slideshow" data-slide="slide" data-of="of">
    <div class="dtnv-track">
      <div class="dtnv-slide"><span class="dtnv-caption">Slide 1</span></div>
      <div class="dtnv-slide"><span class="dtnv-caption">Slide 2</span></div>
      <!-- … as many div.dtnv-slide as images -->
    </div>
    <div class="dtnv-dots"></div>  <!-- the dot buttons are created here by the script -->
  </div>
</div>

One-time payment, no subscription — the code is yours forever

Customize

Options passed to the API or data-* attributes:

Option / propertyDefaultEffect
Dot colour (CSS — --dtnv-dot-color on .fx-0164-root) #fff Ring, fill of the active dot and focus outline. On a light background, use a dark colour.
Maximum width and format (CSS — --dtnv-width, --dtnv-ratio) 640px · 16 / 10 The frame follows the available width up to this limit; the height comes from the format.
Fixed height (CSS — --dtnv-height) auto For a tile or a screen: 190px, 100%…
Slide speed (CSS — --dtnv-duration) 0.5s The cubic-bezier(.25,.46,.45,.94) curve is kept; jumping from the 1st to the 5th image takes the same time as one step.
Corners (CSS — --dtnv-radius) 1rem 0 for a full screen.
Demo background (CSS — --dtnv-stage-bg, --dtnv-stage-padding, --dtnv-stage-min-height) #0a0a0f · 25px 16px · 300px To neutralise in your page (transparent, 0, 0).
Labels (HTML — data-slide, data-of, aria-label) slide · of · Slideshow data-slide also names the dots ("Slide 3"); change it for another language.

FAQ

Here the track is horizontal and you pick a specific image with its dot, without arrows. The Vertical Slider keeps arrows at the top and bottom, moves one image at a time and lifts the track; its drag is vertical.
The script creates empty buttons with the .dtnv-dot class and draws the ring with ::before. For numbers, add dot.textContent = i + 1; where the script creates the button and remove the .dtnv-dot::before rule; the accessible name "Slide N" set by aria-label stays.
Ten 24 px buttons make 240 px: they fit in a frame of 280 px or more. Beyond twelve or fifteen images, a row of dots becomes hard to read; prefer a gallery with thumbnails.