Pure TailwindMIT licensed

Tailwind Onboarding Slider with Native CSS Scroll-Marker Dots

A phone-width first-run tutorial with illustration, copy and indicator dots. The dots are not markup at all — ::scroll-marker generates one per panel and tracks the active one, which is the first carousel primitive the browser has ever owned.

Live Demo
Plain CSSTailwind v4
Try it

Untitled UI Pro: 5,000+ React + Tailwind CSS v4 components · from $349 one-time

Untitled UI React · Affiliate link

Get Untitled UI Pro

The code

<section class="tcar-11 w-full min-h-svh grid place-items-center bg-tcar11-bg font-tcar11-sans px-5 py-10" aria-label="App onboarding">
  <div class="w-[300px] max-w-full bg-white rounded-[34px] p-4 border border-tcar11-edge shadow-[0_30px_60px_-34px_oklch(0.3_0.08_290/0.6)]">
    <ol tabindex="0" role="group" aria-label="Onboarding steps, swipe or use arrow keys" class="tcar11-scroller list-none flex overflow-x-auto m-0 p-0 rounded-[22px] snap-x snap-mandatory [scrollbar-width:none] [scroll-marker-group:after] focus-visible:outline-3 focus-visible:outline-tcar11-acc focus-visible:outline-offset-[3px]">
      <li class="flex-none basis-full snap-center px-5 pt-2 pb-5 text-center">
        <div class="aspect-square rounded-[20px] overflow-hidden mb-[22px] bg-[linear-gradient(135deg,#6a5cff,#9b8bff)]"><img src="https://picsum.photos/seed/onb-1/400/400" width="400" height="400" decoding="async" alt="Track your habits illustration" class="size-full object-cover block mix-blend-luminosity opacity-90"></div>
        <p class="text-[11px] font-semibold tracking-[0.14em] uppercase text-tcar11-mute mb-2">Step 1 of 3</p>
        <h3 class="text-xl font-extrabold text-tcar11-ink mb-2">Track what matters</h3>
        <p class="text-sm leading-[1.55] text-tcar11-mute m-0">Log habits in two taps and watch streaks build over time.</p>
      </li>
      <li class="flex-none basis-full snap-center px-5 pt-2 pb-5 text-center">
        <div class="aspect-square rounded-[20px] overflow-hidden mb-[22px] bg-[linear-gradient(135deg,#00b3a4,#4fd1c5)]"><img src="https://picsum.photos/seed/onb-2/400/400" width="400" height="400" loading="lazy" decoding="async" alt="Get reminders illustration" class="size-full object-cover block mix-blend-luminosity opacity-90"></div>
        <p class="text-[11px] font-semibold tracking-[0.14em] uppercase text-tcar11-mute mb-2">Step 2 of 3</p>
        <h3 class="text-xl font-extrabold text-tcar11-ink mb-2">Gentle reminders</h3>
        <p class="text-sm leading-[1.55] text-tcar11-mute m-0">Smart nudges at the right moment &mdash; never spammy, always optional.</p>
      </li>
      <li class="flex-none basis-full snap-center px-5 pt-2 pb-5 text-center">
        <div class="aspect-square rounded-[20px] overflow-hidden mb-[22px] bg-[linear-gradient(135deg,#ff7a59,#ffb057)]"><img src="https://picsum.photos/seed/onb-3/400/400" width="400" height="400" loading="lazy" decoding="async" alt="See progress illustration" class="size-full object-cover block mix-blend-luminosity opacity-90"></div>
        <p class="text-[11px] font-semibold tracking-[0.14em] uppercase text-tcar11-mute mb-2">Step 3 of 3</p>
        <h3 class="text-xl font-extrabold text-tcar11-ink mb-2">See your progress</h3>
        <p class="text-sm leading-[1.55] text-tcar11-mute m-0">Weekly insights that show how far you've come, at a glance.</p>
      </li>
    </ol>
  </div>
</section>
@import "tailwindcss";

@theme {
  --color-tcar11-bg: #eceafb;
  --color-tcar11-bg: oklch(0.93 0.025 285);
  --color-tcar11-edge: #e2ddf6;
  --color-tcar11-edge: oklch(0.91 0.025 285);
  --color-tcar11-ink: #231d4a;
  --color-tcar11-ink: oklch(0.28 0.08 285);
  --color-tcar11-mute: #5f5982;
  --color-tcar11-mute: oklch(0.48 0.05 285);
  --color-tcar11-dot: #c9c2f2;
  --color-tcar11-dot: oklch(0.82 0.06 285);
  --color-tcar11-acc: #6a5cff;
  --color-tcar11-acc: oklch(0.55 0.22 285);
  --font-tcar11-sans: 'Plus Jakarta Sans', system-ui, sans-serif;
}

.tcar-11 {
  width: 100%;
  min-height: 100svh;
}

.tcar-11 ::selection {
  background: var(--color-tcar11-acc);
  color: #fff;
}

.tcar11-scroller::-webkit-scrollbar {
  display: none;
}
/* CSS Carousel primitives: no utility form exists, and inventing one would obscure them. */

.tcar11-scroller::scroll-marker-group {
  display: flex;
  justify-content: center;
  align-items: center;
  padding-top: 14px;
}

.tcar11-scroller li::scroll-marker {
  content: "";
  width: 9px;
  height: 9px;
  border-radius: 50%;
  background: var(--color-tcar11-dot);
  margin: 6px;
  cursor: pointer;
  border: 0;
  transition: scale .2s, background-color .2s;
}

.tcar11-scroller li::scroll-marker:target-current {
  background: var(--color-tcar11-acc);
  scale: 1.35;
}

@media (prefers-reduced-motion: reduce) {
  .tcar11-scroller {
    scroll-behavior: auto;
  }

  .tcar11-scroller li::scroll-marker {
    transition: none;
  }
}

Setup — do this once per project

Skip this if your project already runs Tailwind v4.

1Load Tailwind
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>

Compiles in the browser — perfect for a CodePen or a quick test, not for production.

2Add the stylesheetRequiredthe @theme block is what makes the custom utilities exist
@import "tailwindcss";

@theme {
  --color-tcar11-bg: #eceafb;
  --color-tcar11-bg: oklch(0.93 0.025 285);
  --color-tcar11-edge: #e2ddf6;
  --color-tcar11-edge: oklch(0.91 0.025 285);
  --color-tcar11-ink: #231d4a;
  --color-tcar11-ink: oklch(0.28 0.08 285);
  --color-tcar11-mute: #5f5982;
  --color-tcar11-mute: oklch(0.48 0.05 285);
  --color-tcar11-dot: #c9c2f2;
  --color-tcar11-dot: oklch(0.82 0.06 285);
  --color-tcar11-acc: #6a5cff;
  --color-tcar11-acc: oklch(0.55 0.22 285);
  --font-tcar11-sans: 'Plus Jakarta Sans', system-ui, sans-serif;
}

.tcar-11 {
  width: 100%;
  min-height: 100svh;
}

.tcar-11 ::selection {
  background: var(--color-tcar11-acc);
  color: #fff;
}

.tcar11-scroller::-webkit-scrollbar {
  display: none;
}
/* CSS Carousel primitives: no utility form exists, and inventing one would obscure them. */

.tcar11-scroller::scroll-marker-group {
  display: flex;
  justify-content: center;
  align-items: center;
  padding-top: 14px;
}

.tcar11-scroller li::scroll-marker {
  content: "";
  width: 9px;
  height: 9px;
  border-radius: 50%;
  background: var(--color-tcar11-dot);
  margin: 6px;
  cursor: pointer;
  border: 0;
  transition: scale .2s, background-color .2s;
}

.tcar11-scroller li::scroll-marker:target-current {
  background: var(--color-tcar11-acc);
  scale: 1.35;
}

@media (prefers-reduced-motion: reduce) {
  .tcar11-scroller {
    scroll-behavior: auto;
  }

  .tcar11-scroller li::scroll-marker {
    transition: none;
  }
}
3Fonts used by this demooptional — skip if you have your own type stack
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700;800&display=swap">

What changed in the port

Which parts became utilities, which stayed CSS, and why.

  • The ::scroll-marker rules read var(--color-tcar11-dot) and var(--color-tcar11-acc) — the same @theme tokens the utilities use. Tokens are the bridge: CSS that cannot become a utility still shares one source of truth with the markup.
  • [scroll-marker-group:after] is the only Tailwind-side line the feature needs. Everything else is genuinely new CSS with no utility surface, and pretending otherwise with arbitrary variants would make it harder to read, not easier.
  • UI change from the CSS original: each panel gained a literal "Step N of 3" line. The original's own gotcha note said progress should not be dot-only, then shipped it dot-only; on every non-Chromium browser those dots do not exist at all.
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 demo 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: Tailwind Onboarding Slider with Native CSS Scroll-Marker Dots
Source: https://codefronts.com/snippets/tailwind-css-image-carousel/tailwind-onboarding-slider-scroll-marker-dots/

A phone-width first-run tutorial with illustration, copy and indicator dots. The dots are not markup at all — ::scroll-marker generates one per panel and tracks the active one, which is the first carousel primitive the browser has ever owned.
## HTML
```html
<section class="tcar-11 w-full min-h-svh grid place-items-center bg-tcar11-bg font-tcar11-sans px-5 py-10" aria-label="App onboarding">
  <div class="w-[300px] max-w-full bg-white rounded-[34px] p-4 border border-tcar11-edge shadow-[0_30px_60px_-34px_oklch(0.3_0.08_290/0.6)]">
    <ol tabindex="0" role="group" aria-label="Onboarding steps, swipe or use arrow keys" class="tcar11-scroller list-none flex overflow-x-auto m-0 p-0 rounded-[22px] snap-x snap-mandatory [scrollbar-width:none] [scroll-marker-group:after] focus-visible:outline-3 focus-visible:outline-tcar11-acc focus-visible:outline-offset-[3px]">
      <li class="flex-none basis-full snap-center px-5 pt-2 pb-5 text-center">
        <div class="aspect-square rounded-[20px] overflow-hidden mb-[22px] bg-[linear-gradient(135deg,#6a5cff,#9b8bff)]"><img src="https://picsum.photos/seed/onb-1/400/400" width="400" height="400" decoding="async" alt="Track your habits illustration" class="size-full object-cover block mix-blend-luminosity opacity-90"></div>
        <p class="text-[11px] font-semibold tracking-[0.14em] uppercase text-tcar11-mute mb-2">Step 1 of 3</p>
        <h3 class="text-xl font-extrabold text-tcar11-ink mb-2">Track what matters</h3>
        <p class="text-sm leading-[1.55] text-tcar11-mute m-0">Log habits in two taps and watch streaks build over time.</p>
      </li>
      <li class="flex-none basis-full snap-center px-5 pt-2 pb-5 text-center">
        <div class="aspect-square rounded-[20px] overflow-hidden mb-[22px] bg-[linear-gradient(135deg,#00b3a4,#4fd1c5)]"><img src="https://picsum.photos/seed/onb-2/400/400" width="400" height="400" loading="lazy" decoding="async" alt="Get reminders illustration" class="size-full object-cover block mix-blend-luminosity opacity-90"></div>
        <p class="text-[11px] font-semibold tracking-[0.14em] uppercase text-tcar11-mute mb-2">Step 2 of 3</p>
        <h3 class="text-xl font-extrabold text-tcar11-ink mb-2">Gentle reminders</h3>
        <p class="text-sm leading-[1.55] text-tcar11-mute m-0">Smart nudges at the right moment &mdash; never spammy, always optional.</p>
      </li>
      <li class="flex-none basis-full snap-center px-5 pt-2 pb-5 text-center">
        <div class="aspect-square rounded-[20px] overflow-hidden mb-[22px] bg-[linear-gradient(135deg,#ff7a59,#ffb057)]"><img src="https://picsum.photos/seed/onb-3/400/400" width="400" height="400" loading="lazy" decoding="async" alt="See progress illustration" class="size-full object-cover block mix-blend-luminosity opacity-90"></div>
        <p class="text-[11px] font-semibold tracking-[0.14em] uppercase text-tcar11-mute mb-2">Step 3 of 3</p>
        <h3 class="text-xl font-extrabold text-tcar11-ink mb-2">See your progress</h3>
        <p class="text-sm leading-[1.55] text-tcar11-mute m-0">Weekly insights that show how far you've come, at a glance.</p>
      </li>
    </ol>
  </div>
</section>
```
## CSS
```css
@import "tailwindcss";

@theme {
  --color-tcar11-bg: #eceafb;
  --color-tcar11-bg: oklch(0.93 0.025 285);
  --color-tcar11-edge: #e2ddf6;
  --color-tcar11-edge: oklch(0.91 0.025 285);
  --color-tcar11-ink: #231d4a;
  --color-tcar11-ink: oklch(0.28 0.08 285);
  --color-tcar11-mute: #5f5982;
  --color-tcar11-mute: oklch(0.48 0.05 285);
  --color-tcar11-dot: #c9c2f2;
  --color-tcar11-dot: oklch(0.82 0.06 285);
  --color-tcar11-acc: #6a5cff;
  --color-tcar11-acc: oklch(0.55 0.22 285);
  --font-tcar11-sans: 'Plus Jakarta Sans', system-ui, sans-serif;
}

.tcar-11 {
  width: 100%;
  min-height: 100svh;
}

.tcar-11 ::selection {
  background: var(--color-tcar11-acc);
  color: #fff;
}

.tcar11-scroller::-webkit-scrollbar {
  display: none;
}
/* CSS Carousel primitives: no utility form exists, and inventing one would obscure them. */

.tcar11-scroller::scroll-marker-group {
  display: flex;
  justify-content: center;
  align-items: center;
  padding-top: 14px;
}

.tcar11-scroller li::scroll-marker {
  content: "";
  width: 9px;
  height: 9px;
  border-radius: 50%;
  background: var(--color-tcar11-dot);
  margin: 6px;
  cursor: pointer;
  border: 0;
  transition: scale .2s, background-color .2s;
}

.tcar11-scroller li::scroll-marker:target-current {
  background: var(--color-tcar11-acc);
  scale: 1.35;
}

@media (prefers-reduced-motion: reduce) {
  .tcar11-scroller {
    scroll-behavior: auto;
  }

  .tcar11-scroller li::scroll-marker {
    transition: none;
  }
}
```

How this works

The browser owns the pagination. scroll-marker-group:after plus a ::scroll-marker per item gives one clickable dot per panel, with the active one matched by :target-current. No dot markup, no JS, no active-state bookkeeping.

Everything about it stays in CSS. This is the demo where the port draws its line: the layout, the phone frame and the panels are utilities; the four rules describing a pseudo-element Tailwind has never heard of live in the stylesheet, namespaced under .tcar11-scroller.

It degrades to nothing, which is correct. Where ::scroll-marker is unsupported the dots simply do not render and the panel scroller still swipes, snaps and keyboard-pages. The "Step N of 3" line means no user is left without a progress signal.

Make it yours

  • Dot colour: the two ::scroll-marker rules, which read the same --color-tcar11-acc token as the utilities.
  • Add a step: add one panel — the dot appears by itself.
  • Frame size: w-[300px] and the panel padding.
  • Swap each illustration block for a video or Lottie without touching the scroller.

Gotchas — read before shipping

  • ::scroll-marker and scroll-marker-group are Chromium 135+. Treat the dots as enhancement; never make them the only way to tell which step you are on.
  • Panels must be exactly basis-full, or snapping lands between steps.
  • Do not try to write scroll-marker:bg-tcar11-acc — Tailwind has no such variant, and inventing one with an arbitrary variant would be less readable than the four CSS rules.

Browser support

ChromeSafariFirefoxEdge
135+n/an/a135+

::scroll-marker + scroll-marker-group are new CSS Carousel features (Chromium 135+). The scroll-snap slider itself works in every browser at Tailwind v4's usual floor; the dots are progressive enhancement.

Techniques used in this demo

Utilitiesfirst-class utility or variant

Search CodeFronts

Loading…