PaginationIndicator

Updated: Sep 24, 2026
PaginationIndicator reports position within a set of pages, either as a compact “3 of 5” chip or as a row of dots. It owns nothing but the display: it holds no state, performs no navigation, and never takes focus. You give it a page count and the active index, and it renders the matching treatment.
Reach for it when you are building your own paged surface out of Pager or a scroller of your own. Carousel already renders one for you — configure it there with showPagination and paginationMode instead of adding a second indicator.

Usage

Text mode reads well when the page count is large or precise position matters:
import { PaginationIndicator, PaginationMode } from '@wearables-ui-toolkit/mrbd';

type Props = {
  pages: ReadonlyArray<string>;
  activeIndex: number;
};

export function ArticleFooter({ pages, activeIndex }: Props) {
  return (
    <div className="article-footer">
      <PaginationIndicator
        mode={PaginationMode.TEXT}
        pageCount={pages.length}
        currentPage={activeIndex}
      />
    </div>
  );
}
Dot mode reads well for a handful of pages, where the shape of the set matters more than the exact number. Drive both the pager and the indicator from one piece of state:
import { useCallback, useState } from 'react';
import {
  PaginationIndicator,
  PaginationMode,
  Pager,
  PagerPage,
} from '@wearables-ui-toolkit/mrbd';

const PHOTO_PAGES = ['first', 'second', 'third', 'fourth'];

export function PhotoPager() {
  const [activeIndex, setActiveIndex] = useState(0);
  const handlePageChange = useCallback((newIndex: number) => {
    setActiveIndex(newIndex);
  }, []);

  return (
    <div className="photo-pager">
      <Pager currentPageIndex={activeIndex} onPageChange={handlePageChange}>
        {PHOTO_PAGES.map(id => (
          <PagerPage key={id}>
            <PhotoPage id={id} />
          </PagerPage>
        ))}
      </Pager>
      <PaginationIndicator
        mode={PaginationMode.DOTS}
        pageCount={PHOTO_PAGES.length}
        currentPage={activeIndex}
      />
    </div>
  );
}
.photo-pager {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--uit-spacing-small);
  height: 100%;
}

Props

The full prop type is PaginationIndicatorProps, which extends HTMLAttributes<HTMLDivElement>.
PropTypeDefaultDescription
mode
PaginationMode
PaginationMode.TEXT
Whether to render the numeric chip or the row of dots.
pageCount
number
0
Total number of pages.
currentPage
number
0
Zero-based index of the active page.
className
string
''
Added to the root element.
style
CSSProperties
{}
Applied to the root element. In dot mode it is merged after the container’s own width and height, so it can override them.
The ref is forwarded to the root element in both modes, and every other DOM prop — aria-*, data-*, role, and the rest — lands there too.

Types and enums

PaginationMode
MemberDescription
TEXT
A compact chip reading “current of total”, such as 3 of 5.
DOTS
A row of dots with the active page emphasized and a sliding window for larger sets.

Behavior

  • Text mode. Renders a Chip in its default deemphasized style containing the one-based position and the total — currentPage of 0 reads as 1 of 5. The text is held to a single line so the chip does not change height as the numbers change, and the chip is not clickable.
  • Nothing to show. In text mode a pageCount of 0 or less renders nothing at all, so an empty set collapses cleanly. In dot mode the container still renders, keeping its reserved footprint, but contains no dots.
  • Dot window. Dot mode shows at most three “real” dots at once. With three pages or fewer every page gets its own dot and no window logic applies. Beyond that, a smaller overflow dot appears on each side that still has pages outside the window, for a maximum of five dots. The active dot is the largest, inactive real dots are smaller, and overflow dots are smallest.
  • Window movement. The window only shifts when currentPage moves outside it, so paging back and forth within the visible range leaves the dots where they are instead of re-centering on every change. The window is also clamped so it never runs past either end of the set.
  • Stable footprint. The dot container reserves the width of the widest possible window and centers the current row of dots inside it, so the indicator does not jitter or resize as pages change or as overflow dots appear and disappear.
  • Motion. Dots animate their position, diameter, and color with CSS transitions, so the window slide and the active-dot change read as one motion. There is no React-driven animation loop. The component does not read a motion preference for you; if your app honours one, read it with usePrefersReducedMotion where the paged surface lives and fall back to PaginationMode.TEXT, which has no dot motion.
  • Materials and color. The active dot uses --uit-color-control-active and the remaining dots use --uit-color-background-track, so the current page stays the brightest mark on the additive-light display.
  • Out-of-range input. The component does not clamp currentPage for you. An index outside [0, pageCount - 1] produces a row with no active dot in dot mode and an out-of-range number in text mode, so validate the index where your paging state lives.

Accessibility

  • The indicator is visual status, not a control. It sets no role and no ARIA attributes of its own, the dots carry no text, and the text-mode chip is hidden from assistive technology — so by default the component contributes nothing to the accessibility tree and never duplicates what the pager already says.
  • Because of that, position must come from the paged content itself. Give the owning Pager or Carousel an accessible name and page semantics, or render your own status text alongside it.
  • If you do want the indicator itself exposed, pass the ARIA you need — a role plus an aria-label, for instance — and it will be applied to the root element. Keep that text in sync with the page count and index you pass.
  • Nothing in the component is focusable or clickable, so it never becomes a stop in directional navigation and never intercepts activation intended for the content behind it.
  • Update it from the same state that drives the pager or scroller, on the same render.
  • Keep it visually adjacent to the content it describes, and inside the same layout so it moves with that content.
  • Prefer DOTS for a small, fixed set and TEXT when the count is large or the exact position matters.
  • Let the surrounding content, not the indicator, carry the accessible description of where the user is.

Avoid

  • Use tabs or page buttons for navigation. The dots are not interactive and must not be wrapped to appear interactive.
  • Keep pageCount and currentPage synchronized with the content so the visible position stays accurate.
  • Avoid adding another indicator when Carousel already presents one.
  • Avoid rebuilding the dot treatment with your own markup to change spacing or sizing; the reserved footprint and window behavior are what keep it stable.
  • Avoid passing a one-based currentPage — the component adds the one for you in text mode.