Navigation

Mobile Counter

A compact indicator for phones: a "step 2 of 5" counter, the current step's name, a hint about the next one, and segment bars instead of a ribbon of labels.

  • stepper
  • mobile
  • compact
  • progress

Preview

1440pxHost theme

Use it with AI

  1. 1. Copy the link.
  2. 2. Write to your agent in your own words and drop the link into the sentence.
  3. 3. The agent opens the link and installs the component from the registry.

put this in the header: https://vibeui.ru/c/stepper-006?lang=en

Show the full instructions

What the link says, spelled out. Use it when your agent cannot open links — paste this text instead.

# Install and place "stepper-006" (Mobile Counter) from VibeUI

## 1. Install first — do not skip, do not recreate
Run this exact command before writing any code:
npx shadcn@latest add https://vibeui.ru/r/stepper-006.json

Registry item: https://vibeui.ru/r/stepper-006.json
Installs to: components/vibeui/stepper-006.tsx (the exact path follows this project's components.json aliases).
npm dependencies: none.
Registry dependencies: none.

Install it from the registry. Do not recreate it from the description,
do not substitute a similar component from another library, and do not
rewrite it to match the project's existing style.

## 2. What it is
A compact indicator for phones: a "step 2 of 5" counter, the current step's name, a hint about the next one, and segment bars instead of a ribbon of labels.

A compact step counter for phones: "step 2 of 5", the current step in large type and segment bars. Zero dependencies, one file, no client JS.

## 3. How to use it
import { Stepper006 } from "@/components/vibeui/stepper-006"

<Stepper006
  current={1}
  nextPrefix="Next"
/>

Read the installed file for the full prop list.

## 4. Where to place it
This is a small inline component. Put it exactly where the user asked,
inside the existing markup. Do not create a new page, section or
wrapper for it. If a similar control already sits in that spot,
replace it instead of adding a second one.

Placement: ___
(The user fills this line in. If it is still blank, ask where to put it
instead of guessing.)

## 5. Keep exactly as installed
- the local --vibeui-stepper-006-* palette — do not swap it for your theme tokens
- the segments as an ol with hidden labels: a bar made of divs is empty to a screen reader
- aria-current="step" on the current segment: the text counter and the markup must agree
- the "step N of M" counter in words: on a phone it is the only navigation through the process
- clamping current to the array bounds: otherwise the current step title comes out empty
- the component's own light surface: dark text disappears on the dark catalog card

## 6. You may change
- the step names through steps
- the active step number through current
- the next-step prefix through nextPrefix
- the block's accessible name through label

## 7. Rules
- Step names must be short: a long one wraps to two lines and breaks the card height.
- On the last step the "next" line is not drawn — there is nothing to hint at.
- More than seven or eight segments is pointless: the bars stop being distinguishable.
- Do not lift the CSS variables into globals.css: the component has to stay a single file.

## 8. Verify
- it renders with no console errors;
- it looks like the preview on the VibeUI page you copied this from;
- if it does not, you changed something listed in section 5 — put it back.

For developers

npx shadcn@latest add https://vibeui.ru/r/stepper-006.json
https://vibeui.ru/r/stepper-006.json

Компонент самодостаточен: один файл, без зависимостей, палитра в переменных --vibeui-stepper-006-*. Серверный: хуков нет. На ширине телефона пять подписей не помещаются, поэтому вместо ленты выводится счётчик и одно название — крупно. Сегменты остаются ol: каждый пункт несёт своё состояние и визуально скрытую фразу, поэтому индикатор озвучивается как список шагов, а не как декоративная полоса. Текущий сегмент помечен aria-current="step" и растянут flex-grow, поэтому позиция читается и без чтения счётчика. Номер шага зажимается в границы массива: current вне диапазона не даст пустого заголовка.

Component source

The same file your agent installs. Here in case you would rather copy it by hand.

import type { ComponentPropsWithoutRef, CSSProperties } from "react"

export type Stepper006Props = Omit<
  ComponentPropsWithoutRef<"nav">,
  "children"
> & {
  steps?: string[]
  /** Номер текущего шага, считая с нуля. */
  current?: number
  /** Приставка к названию следующего шага. */
  nextPrefix?: string
  label?: string
  accent?: string
}

// Идея компонента: на телефоне не хватает ширины на пять подписей, поэтому
// вместо ленты выводится счётчик «шаг 2 из 5», название текущего шага и
// подсказка про следующий. Полоски-сегменты остаются списком: каждая —
// пункт со своим состоянием и словом для озвучки, а не декоративный div.
const STYLES = `
:where([data-vibeui-block="stepper-006"]){
--vibeui-stepper-006-bg:oklch(1 0 0);
--vibeui-stepper-006-fg:oklch(0.24 0.016 265);
--vibeui-stepper-006-muted:oklch(0.56 0.014 265);
--vibeui-stepper-006-border:oklch(0.92 0.006 265);
--vibeui-stepper-006-track:oklch(0.93 0.008 265);
--vibeui-stepper-006-accent:oklch(0.55 0.2 262);
--vibeui-stepper-006-font:ui-sans-serif,system-ui,-apple-system,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif;
}
[data-vibeui-block="stepper-006"]{
width:100%;max-width:24rem;box-sizing:border-box;
font-family:var(--vibeui-stepper-006-font);color:var(--vibeui-stepper-006-fg);
}
[data-vibeui-block="stepper-006"] [data-part="shell"]{
background:var(--vibeui-stepper-006-bg);
border:1px solid var(--vibeui-stepper-006-border);
border-radius:0.875rem;padding:0.875rem 1rem 0.9375rem;
}
[data-vibeui-block="stepper-006"] [data-part="top"]{
display:flex;align-items:baseline;justify-content:space-between;gap:0.75rem;
}
[data-vibeui-block="stepper-006"] [data-part="counter"]{
font-size:0.6875rem;font-weight:650;letter-spacing:0.04em;text-transform:uppercase;
color:var(--vibeui-stepper-006-accent);font-variant-numeric:tabular-nums;
}
[data-vibeui-block="stepper-006"] [data-part="left"]{
font-size:0.6875rem;color:var(--vibeui-stepper-006-muted);font-variant-numeric:tabular-nums;
}
[data-vibeui-block="stepper-006"] [data-part="title"]{
margin:0.25rem 0 0;font-size:1.0625rem;font-weight:650;line-height:1.25;
}
[data-vibeui-block="stepper-006"] [data-part="next"]{
margin:0.125rem 0 0;font-size:0.75rem;line-height:1.4;color:var(--vibeui-stepper-006-muted);
}
[data-vibeui-block="stepper-006"] ol{
display:flex;gap:0.25rem;margin:0.75rem 0 0;padding:0;list-style:none;
}
[data-vibeui-block="stepper-006"] li{
flex:1 1 0;height:0.3125rem;border-radius:9999px;
background:var(--vibeui-stepper-006-track);
}
[data-vibeui-block="stepper-006"] li[data-state="done"]{
background:color-mix(in oklab,var(--vibeui-stepper-006-accent) 55%,var(--vibeui-stepper-006-track));
}
[data-vibeui-block="stepper-006"] li[data-state="current"]{
background:var(--vibeui-stepper-006-accent);flex-grow:1.6;
}
[data-vibeui-block="stepper-006"] [data-part="sr"]{
position:absolute;width:1px;height:1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap;
}
@media (prefers-reduced-motion:reduce){[data-vibeui-block="stepper-006"] *{animation:none!important;transition:none!important}}
`

const DEFAULT_STEPS = ["Корзина", "Доставка", "Оплата", "Проверка", "Готово"]

/**
 * Компактный счётчик шагов для телефона: «шаг 2 из 5» и полоски-сегменты.
 * Один файл, ноль зависимостей, собственная палитра.
 */
export function Stepper006({
  steps = DEFAULT_STEPS,
  current = 1,
  nextPrefix = "Далее",
  label = "Оформление заказа",
  accent,
  className,
  style,
  ...props
}: Stepper006Props) {
  const palette = {
    ...(accent ? { "--vibeui-stepper-006-accent": accent } : null),
    ...style,
  } as CSSProperties

  const index = Math.min(Math.max(current, 0), steps.length - 1)
  const next = steps[index + 1]
  const left = steps.length - index - 1

  return (
    <>
      <style href="vibeui-stepper-006" precedence="medium">
        {STYLES}
      </style>
      <nav
        {...props}
        data-vibeui-block="stepper-006"
        aria-label={label}
        className={className}
        style={palette}
      >
        <div data-part="shell">
          <p data-part="top">
            <span data-part="counter">
              Шаг {index + 1} из {steps.length}
            </span>
            <span data-part="left">
              {left > 0 ? `осталось ${left}` : "последний"}
            </span>
          </p>
          <p data-part="title">{steps[index]}</p>
          {next ? (
            <p data-part="next">
              {nextPrefix}: {next}
            </p>
          ) : null}
          <ol>
            {steps.map((step, position) => {
              const state =
                position < index
                  ? "done"
                  : position === index
                    ? "current"
                    : "todo"

              return (
                <li
                  key={step}
                  data-state={state}
                  aria-current={state === "current" ? "step" : undefined}
                >
                  <span data-part="sr">
                    {step}
                    {state === "done"
                      ? " — пройден"
                      : state === "current"
                        ? " — текущий шаг"
                        : " — впереди"}
                  </span>
                </li>
              )
            })}
          </ol>
        </div>
      </nav>
    </>
  )
}