Skip to content
WANCRAFT

D.167Navigation & Menus

Alphabet Index

An A–Z scrubber beside a long grouped list: drag to jump with a letter bubble, sticky headers, locale-aware sorting.

NewReactTailwindintermediate

Overview

Alphabet Index turns a flat list of { id, name } items into a grouped, sorted list with an A–Z index along its edge, like a phone's contacts. Items are sorted with Intl.Collator for the locale you pass and grouped by their first letter; accented initials fold onto their base letter unless your alphabet lists them separately (Å, Ä and Ö in Swedish), and anything else lands under #. Drag along the index to scrub: the list jumps section by section while a large letter bubble follows your finger, and letters without entries are dimmed and skipped (or hidden). Section headers stick to the top of the scroll area and the current section is highlighted in the index. For keyboards, the index is a vertical list of buttons with a single tab stop: arrows preview a letter, Enter jumps and moves focus to that section's header. It scrolls its own list, or an external container you pass by ref, and never the page.

01Installation

Method 01 · Registry

CLI verified

Uses the shadcn CLI in a project initialised with shadcn init. Installs the file to components/ui/alphabet-index.tsx and adds its dependencies.

npx shadcn@latest add https://wancraft.my/r/alphabet-index.json

Method 02 · Copy and paste

  1. 1. No extra dependencies — React and Tailwind CSS only.
  2. 2. Copy the source from the Code tab into components/ui/alphabet-index.tsx.
  3. 3. Import it and adjust props.
Dependencies
None
Peer
react ≥ 18, tailwindcss ≥ 4
Added
13 Oct 2026
Updated
—

02Usage

The default configuration. Change properties above to generate a tailored example in the Usage tab.

example.tsx
import { AlphabetIndex } from "@/components/ui/alphabet-index";

export function AlphabetIndexExample() {
  const contacts = [
    { id: "1", name: "Aisyah Rahman" },
    { id: "2", name: "Benjamin Ng" },
    { id: "3", name: "Émile Laurent" },
    { id: "4", name: "Mei Ling Ong" },
    { id: "5", name: "Zara Hussein" },
  ];

  return (
    <div className="h-96 w-full max-w-sm overflow-hidden rounded-2xl border">
      <AlphabetIndex items={contacts} label="Contacts" />
    </div>
  );
}

03Props

PropTypeDefaultDescription
itemsT extends { id: string; name: string }[]—Required. Sorted with Intl.Collator and grouped by first letter.
renderItem(item: T) => ReactNode—Custom row content. Defaults to an initials avatar and the name.
localestring"en"Locale for sorting and upper-casing.
lettersstring[]A–ZIndex letters in order; other initials fold to their base letter or go under #.
labelstring"Contacts"Accessible name of the list (the index is named "<label> index").
showBubblebooleantrueLarge letter bubble beside the index while scrubbing or using arrows.
emptyLetters"dim" | "hide""dim"Letters without entries are dimmed and disabled, or hidden.
stickyHeadersbooleantrueSection headers stick to the top of the scroll area.
indexSide"right" | "left""right"Which edge the index sits on.
containerRefObject<HTMLElement | null>—External scroll container. By default the list scrolls itself and fills its parent's height.
countLabel(count: number) => string"n items"Accessible count announced with each letter.
accentstring"#FF7448"Current letter, bubble and focus colour.
...propsHTMLAttributes<HTMLDivElement>—Forwarded to the root.

04Features

  • 01Automatic grouping by first letter with Intl.Collator sorting for any locale
  • 02Drag along the index to scrub; a large letter bubble follows the pointer
  • 03Letters without entries are dimmed and skipped, or hidden
  • 04Sticky section headers and a highlighted current letter
  • 05Short indexes condense iOS-style: every other letter becomes a dot, every letter stays reachable
  • 06Scrolls its own list or an external container by setting scrollTop, never the page
motion · tweeninput · draginput · pointerinput · clickinput · keyboardinput · scroll

05Accessibility

  • Keyboard model (chosen over a slider): the index is a <nav> holding a vertical list of buttons with one tab stop. Up / Down (or Left / Right) preview the previous or next letter with entries, PageUp / PageDown move five, Home / End jump to the ends, typing a letter goes to it.
  • Enter or Space jumps to the section and moves focus to its header (a focusable heading), so reading continues from there; a pointer scrub does the same on release.
  • Each letter button is named with its count ("M, 4 contacts"); empty letters are disabled and the current section's letter has aria-current="location".
  • The list is a labelled, focusable region with one labelled <section> per letter. The bubble is decorative (aria-hidden).
  • With prefers-reduced-motion the bubble appears without scaling; jumps are always instant.

06Limitations

  • Not virtualised: suited to hundreds of rows, not tens of thousands.
  • Sticky headers use a solid surface colour; with a custom list background, restyle them via renderItem's surroundings or className.