16 CSS Mobile Navigation Patterns12 / 16

CSS + JSMIT licensed

Minimal Dot Navigation

A swipeable five-slide carousel with dot indicators that expand into a pill on the active slide. Supports arrow buttons, dot click, touch swipe, and auto-advance — with CSS transitions handling all visual motion.

Published

Live Demo
Try it

This is a full-page demo — interact inside the frame above, or open it in the playground for the full-screen experience.

The code

<div class="mn-12" id="mn-12-root">
  <div class="mn-12__track" id="mn-12-track">
    <div class="mn-12__slide">
      <div class="mn-12__slide-emoji">🚀</div>
      <h2>Launch Your Ideas</h2>
      <p>Build and ship products 10x faster with our streamlined platform.</p>
      <a href="#" class="mn-12__slide-cta">Get Started →</a>
    </div>
    <div class="mn-12__slide">
      <div class="mn-12__slide-emoji">🔐</div>
      <h2>Security First</h2>
      <p>Enterprise-grade encryption and compliance built into every layer.</p>
      <a href="#" class="mn-12__slide-cta">Learn More →</a>
    </div>
    <div class="mn-12__slide">
      <div class="mn-12__slide-emoji">⚡</div>
      <h2>Lightning Fast</h2>
      <p>Sub-100ms response times globally with our edge network.</p>
      <a href="#" class="mn-12__slide-cta">See Benchmarks →</a>
    </div>
    <div class="mn-12__slide">
      <div class="mn-12__slide-emoji">🌍</div>
      <h2>Global Scale</h2>
      <p>Deploy to 32 regions worldwide in a single click.</p>
      <a href="#" class="mn-12__slide-cta">Explore Regions →</a>
    </div>
    <div class="mn-12__slide">
      <div class="mn-12__slide-emoji">💎</div>
      <h2>Premium Support</h2>
      <p>24/7 expert support with guaranteed 1-hour response time.</p>
      <a href="#" class="mn-12__slide-cta">Talk to Us →</a>
    </div>
  </div>

  <div class="mn-12__label">Discover</div>
  <div class="mn-12__counter"><span id="mn-12-curr">1</span> / 5</div>

  <button class="mn-12__arrow mn-12__arrow--prev" id="mn-12-prev">‹</button>
  <button class="mn-12__arrow mn-12__arrow--next" id="mn-12-next">›</button>

  <div class="mn-12__dots" id="mn-12-dots">
    <button class="mn-12__dot is-active" data-i="0"></button>
    <button class="mn-12__dot" data-i="1"></button>
    <button class="mn-12__dot" data-i="2"></button>
    <button class="mn-12__dot" data-i="3"></button>
    <button class="mn-12__dot" data-i="4"></button>
  </div>
</div>
*,
*::before,
*::after {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

body {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 100vh;
  background: #0f0f13;
  font-family: 'Segoe UI', sans-serif;
}

.mn-12 {
  --accent: #6366f1;
  width: 375px;
  height: 667px;
  position: relative;
  overflow: hidden;
  border-radius: 32px;
  box-shadow: 0 30px 80px rgba(0,0,0,0.7);
}
/* Slides */

.mn-12__track {
  display: flex;
  width: 500%;
  height: 100%;
  transition: transform 0.5s cubic-bezier(0.4, 0, 0.2, 1);
  will-change: transform;
}

.mn-12__slide {
  width: 20%;
  height: 100%;
  flex-shrink: 0;
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  padding: 40px 36px;
  text-align: center;
  position: relative;
}

.mn-12__slide:nth-child(1) {
  background: linear-gradient(160deg, #1e1b4b 0%, #312e81 100%);
}

.mn-12__slide:nth-child(2) {
  background: linear-gradient(160deg, #0c1446 0%, #1e3a5f 100%);
}

.mn-12__slide:nth-child(3) {
  background: linear-gradient(160deg, #1a0533 0%, #3b0764 100%);
}

.mn-12__slide:nth-child(4) {
  background: linear-gradient(160deg, #0a2e1a 0%, #14532d 100%);
}

.mn-12__slide:nth-child(5) {
  background: linear-gradient(160deg, #2d1606 0%, #7c2d12 100%);
}

.mn-12__slide-emoji {
  font-size: 72px;
  margin-bottom: 24px;
  filter: drop-shadow(0 8px 24px rgba(0,0,0,0.3));
}

.mn-12__slide h2 {
  font-size: 28px;
  font-weight: 700;
  color: #fff;
  letter-spacing: -0.5px;
  margin-bottom: 12px;
  line-height: 1.2;
}

.mn-12__slide p {
  font-size: 14px;
  color: rgba(255,255,255,0.6);
  line-height: 1.7;
  max-width: 280px;
}
/* CTA button */

.mn-12__slide-cta {
  display: inline-block;
  margin-top: 28px;
  background: rgba(255,255,255,0.15);
  color: #fff;
  font-size: 13px;
  font-weight: 600;
  padding: 12px 28px;
  border-radius: 100px;
  text-decoration: none;
  border: 1px solid rgba(255,255,255,0.25);
  transition: background 0.2s;
}

.mn-12__slide-cta:hover {
  background: rgba(255,255,255,0.22);
}
/* Dot navigation */

.mn-12__dots {
  position: absolute;
  bottom: 32px;
  left: 50%;
  transform: translateX(-50%);
  display: flex;
  align-items: center;
  gap: 8px;
  z-index: 10;
}

.mn-12__dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: rgba(255,255,255,0.3);
  border: none;
  cursor: pointer;
  padding: 0;
  transition: all 0.35s cubic-bezier(0.34, 1.56, 0.64, 1);
}

.mn-12__dot.is-active {
  width: 28px;
  border-radius: 4px;
  background: #fff;
}
/* Arrow nav */

.mn-12__arrow {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  z-index: 10;
  width: 44px;
  height: 44px;
  border-radius: 50%;
  background: rgba(255,255,255,0.12);
  border: 1px solid rgba(255,255,255,0.2);
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  color: #fff;
  font-size: 16px;
  transition: background 0.2s;
}

.mn-12__arrow:hover {
  background: rgba(255,255,255,0.22);
}

.mn-12__arrow--prev {
  left: 16px;
}

.mn-12__arrow--next {
  right: 16px;
}
/* Slide counter */

.mn-12__counter {
  position: absolute;
  top: 20px;
  right: 20px;
  font-size: 13px;
  font-weight: 600;
  color: rgba(255,255,255,0.5);
  letter-spacing: 1px;
}

.mn-12__counter span {
  color: rgba(255,255,255,0.9);
}
/* Top label */

.mn-12__label {
  position: absolute;
  top: 20px;
  left: 20px;
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 2px;
  text-transform: uppercase;
  color: rgba(255,255,255,0.3);
}

@media (prefers-reduced-motion: reduce) {
  .mn-12__track {
    transition: none;
  }

  .mn-12__dot {
    transition: none;
  }
}
(function() {
  const track = document.getElementById('mn-12-track');
  const dots = document.querySelectorAll('.mn-12__dot');
  const counter = document.getElementById('mn-12-curr');
  const total = 5;
  let current = 0;

  function goTo(idx) {
    current = (idx + total) % total;
    track.style.transform = `translateX(-${current * 20}%)`;
    dots.forEach((d, i) => d.classList.toggle('is-active', i === current));
    counter.textContent = current + 1;
  }

  document.getElementById('mn-12-prev').addEventListener('click', () => goTo(current - 1));
  document.getElementById('mn-12-next').addEventListener('click', () => goTo(current + 1));
  dots.forEach(d => d.addEventListener('click', () => goTo(+d.dataset.i)));

  // Touch swipe
  let startX = 0;
  track.addEventListener('touchstart', e => { startX = e.touches[0].clientX; }, { passive: true });
  track.addEventListener('touchend', e => {
    const dx = e.changedTouches[0].clientX - startX;
    if (Math.abs(dx) > 50) goTo(current + (dx < 0 ? 1 : -1));
  });

  // Auto-advance
  let timer = setInterval(() => goTo(current + 1), 4000);
  document.getElementById('mn-12-root').addEventListener('touchstart', () => clearInterval(timer));
})();
Paste this into ChatGPT, Claude, Cursor, or any coding assistant. The block below is pre-framed with everything the AI needs to integrate this demo into your project — markup, styles, scoping notes, and the source URL. Hit Copy and paste straight into your chat.
Here's a working CSS Mobile Navigation Pattern from CodeFronts. Use it as-is or adapt to your framework. All classes are scoped under a unique prefix so the code won't collide with your existing styles. MIT licensed.
Demo: Minimal Dot Navigation
Source: https://codefronts.com/navigation/css-mobile-navigation/minimal-dot-navigation/

A swipeable five-slide carousel with dot indicators that expand into a pill on the active slide. Supports arrow buttons, dot click, touch swipe, and auto-advance — with CSS transitions handling all visual motion.
## HTML
```html
<div class="mn-12" id="mn-12-root">
  <div class="mn-12__track" id="mn-12-track">
    <div class="mn-12__slide">
      <div class="mn-12__slide-emoji">🚀</div>
      <h2>Launch Your Ideas</h2>
      <p>Build and ship products 10x faster with our streamlined platform.</p>
      <a href="#" class="mn-12__slide-cta">Get Started →</a>
    </div>
    <div class="mn-12__slide">
      <div class="mn-12__slide-emoji">🔐</div>
      <h2>Security First</h2>
      <p>Enterprise-grade encryption and compliance built into every layer.</p>
      <a href="#" class="mn-12__slide-cta">Learn More →</a>
    </div>
    <div class="mn-12__slide">
      <div class="mn-12__slide-emoji">⚡</div>
      <h2>Lightning Fast</h2>
      <p>Sub-100ms response times globally with our edge network.</p>
      <a href="#" class="mn-12__slide-cta">See Benchmarks →</a>
    </div>
    <div class="mn-12__slide">
      <div class="mn-12__slide-emoji">🌍</div>
      <h2>Global Scale</h2>
      <p>Deploy to 32 regions worldwide in a single click.</p>
      <a href="#" class="mn-12__slide-cta">Explore Regions →</a>
    </div>
    <div class="mn-12__slide">
      <div class="mn-12__slide-emoji">💎</div>
      <h2>Premium Support</h2>
      <p>24/7 expert support with guaranteed 1-hour response time.</p>
      <a href="#" class="mn-12__slide-cta">Talk to Us →</a>
    </div>
  </div>

  <div class="mn-12__label">Discover</div>
  <div class="mn-12__counter"><span id="mn-12-curr">1</span> / 5</div>

  <button class="mn-12__arrow mn-12__arrow--prev" id="mn-12-prev">‹</button>
  <button class="mn-12__arrow mn-12__arrow--next" id="mn-12-next">›</button>

  <div class="mn-12__dots" id="mn-12-dots">
    <button class="mn-12__dot is-active" data-i="0"></button>
    <button class="mn-12__dot" data-i="1"></button>
    <button class="mn-12__dot" data-i="2"></button>
    <button class="mn-12__dot" data-i="3"></button>
    <button class="mn-12__dot" data-i="4"></button>
  </div>
</div>

```
## CSS
```css
*,
*::before,
*::after {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

body {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 100vh;
  background: #0f0f13;
  font-family: 'Segoe UI', sans-serif;
}

.mn-12 {
  --accent: #6366f1;
  width: 375px;
  height: 667px;
  position: relative;
  overflow: hidden;
  border-radius: 32px;
  box-shadow: 0 30px 80px rgba(0,0,0,0.7);
}
/* Slides */

.mn-12__track {
  display: flex;
  width: 500%;
  height: 100%;
  transition: transform 0.5s cubic-bezier(0.4, 0, 0.2, 1);
  will-change: transform;
}

.mn-12__slide {
  width: 20%;
  height: 100%;
  flex-shrink: 0;
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  padding: 40px 36px;
  text-align: center;
  position: relative;
}

.mn-12__slide:nth-child(1) {
  background: linear-gradient(160deg, #1e1b4b 0%, #312e81 100%);
}

.mn-12__slide:nth-child(2) {
  background: linear-gradient(160deg, #0c1446 0%, #1e3a5f 100%);
}

.mn-12__slide:nth-child(3) {
  background: linear-gradient(160deg, #1a0533 0%, #3b0764 100%);
}

.mn-12__slide:nth-child(4) {
  background: linear-gradient(160deg, #0a2e1a 0%, #14532d 100%);
}

.mn-12__slide:nth-child(5) {
  background: linear-gradient(160deg, #2d1606 0%, #7c2d12 100%);
}

.mn-12__slide-emoji {
  font-size: 72px;
  margin-bottom: 24px;
  filter: drop-shadow(0 8px 24px rgba(0,0,0,0.3));
}

.mn-12__slide h2 {
  font-size: 28px;
  font-weight: 700;
  color: #fff;
  letter-spacing: -0.5px;
  margin-bottom: 12px;
  line-height: 1.2;
}

.mn-12__slide p {
  font-size: 14px;
  color: rgba(255,255,255,0.6);
  line-height: 1.7;
  max-width: 280px;
}
/* CTA button */

.mn-12__slide-cta {
  display: inline-block;
  margin-top: 28px;
  background: rgba(255,255,255,0.15);
  color: #fff;
  font-size: 13px;
  font-weight: 600;
  padding: 12px 28px;
  border-radius: 100px;
  text-decoration: none;
  border: 1px solid rgba(255,255,255,0.25);
  transition: background 0.2s;
}

.mn-12__slide-cta:hover {
  background: rgba(255,255,255,0.22);
}
/* Dot navigation */

.mn-12__dots {
  position: absolute;
  bottom: 32px;
  left: 50%;
  transform: translateX(-50%);
  display: flex;
  align-items: center;
  gap: 8px;
  z-index: 10;
}

.mn-12__dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: rgba(255,255,255,0.3);
  border: none;
  cursor: pointer;
  padding: 0;
  transition: all 0.35s cubic-bezier(0.34, 1.56, 0.64, 1);
}

.mn-12__dot.is-active {
  width: 28px;
  border-radius: 4px;
  background: #fff;
}
/* Arrow nav */

.mn-12__arrow {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  z-index: 10;
  width: 44px;
  height: 44px;
  border-radius: 50%;
  background: rgba(255,255,255,0.12);
  border: 1px solid rgba(255,255,255,0.2);
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  color: #fff;
  font-size: 16px;
  transition: background 0.2s;
}

.mn-12__arrow:hover {
  background: rgba(255,255,255,0.22);
}

.mn-12__arrow--prev {
  left: 16px;
}

.mn-12__arrow--next {
  right: 16px;
}
/* Slide counter */

.mn-12__counter {
  position: absolute;
  top: 20px;
  right: 20px;
  font-size: 13px;
  font-weight: 600;
  color: rgba(255,255,255,0.5);
  letter-spacing: 1px;
}

.mn-12__counter span {
  color: rgba(255,255,255,0.9);
}
/* Top label */

.mn-12__label {
  position: absolute;
  top: 20px;
  left: 20px;
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 2px;
  text-transform: uppercase;
  color: rgba(255,255,255,0.3);
}

@media (prefers-reduced-motion: reduce) {
  .mn-12__track {
    transition: none;
  }

  .mn-12__dot {
    transition: none;
  }
}
```

## JavaScript
```js
(function() {
  const track = document.getElementById('mn-12-track');
  const dots = document.querySelectorAll('.mn-12__dot');
  const counter = document.getElementById('mn-12-curr');
  const total = 5;
  let current = 0;

  function goTo(idx) {
    current = (idx + total) % total;
    track.style.transform = `translateX(-${current * 20}%)`;
    dots.forEach((d, i) => d.classList.toggle('is-active', i === current));
    counter.textContent = current + 1;
  }

  document.getElementById('mn-12-prev').addEventListener('click', () => goTo(current - 1));
  document.getElementById('mn-12-next').addEventListener('click', () => goTo(current + 1));
  dots.forEach(d => d.addEventListener('click', () => goTo(+d.dataset.i)));

  // Touch swipe
  let startX = 0;
  track.addEventListener('touchstart', e => { startX = e.touches[0].clientX; }, { passive: true });
  track.addEventListener('touchend', e => {
    const dx = e.changedTouches[0].clientX - startX;
    if (Math.abs(dx) > 50) goTo(current + (dx < 0 ? 1 : -1));
  });

  // Auto-advance
  let timer = setInterval(() => goTo(current + 1), 4000);
  document.getElementById('mn-12-root').addEventListener('touchstart', () => clearInterval(timer));
})();
```

How this works

The five slides live inside a .mn-12__track flex container set to width: 500% so each slide occupies exactly 20% of the track. Navigation calls track.style.transform = translateX(-N * 20%) and CSS handles the transition: transform 0.5s cubic-bezier(0.4,0,0.2,1). The JS never touches individual slide visibility — only the single transform on the track container.

Dot indicators are plain <button> elements. The active dot gets the is-active class which transitions its width from 8px to 28px and border-radius from 50% to 4px — turning a circle into a pill. Touch swipe records touchstart X, compares with touchend X, and calls goTo() if delta exceeds 50px.

Make it yours

  • Add a sixth slide by appending a new .mn-12__slide div, changing the track to width: 600%, updating each slide width to 16.666%, and adding a sixth dot button.
  • Disable auto-advance by removing the setInterval call and the clearInterval(timer) on touch — useful when slides contain interactive elements.
  • Change slide transition easing by editing cubic-bezier(0.4,0,0.2,1) on .mn-12__track — try cubic-bezier(0.34, 1.56, 0.64, 1) for a spring overshoot effect.
  • Make dots always pill-shaped by setting inactive dot width: 16px; border-radius: 4px and active width: 32px.
  • Replace arrow buttons with swipe-only navigation on touch devices by hiding .mn-12__arrow with @media (pointer: coarse) { display: none }.

Gotchas — read before shipping

  • The width: 500% track approach means each slide is width: 20% of the track — if you add or remove slides without updating both the track width and the percentage in goTo(), slides will be clipped or misaligned.
  • Auto-advance uses setInterval which continues even when the demo is off-screen — in a gallery context, pair with an IntersectionObserver to pause when not visible.
  • Setting transition: none during drag would allow drag-to-slide; without it, the track snaps on every JS call rather than following the finger continuously.

Browser support

ChromeSafariFirefoxEdge
55+11+52+55+

Touch events are universally supported on mobile. The pill-width dot transition uses standard CSS width which animates smoothly everywhere.

Techniques used in this demo

Search CodeFronts

Loading…