DEV Community

Cover image for Mantine SelectStepper 2.0.0 — Swipe, Scroll, Resize, Repeat
Giovambattista Fazioli
Giovambattista Fazioli

Posted on

Mantine SelectStepper 2.0.0 — Swipe, Scroll, Resize, Repeat

Vertical orientation, touch gestures, responsive props, imperative API, and 9 bug fixes — all in one major release.

Introduction

This release started with a one-line CSS fix from a community contributor: wrap="nowrap". That PR from @kruschid — just a single prop addition — exposed a deeper truth: SelectStepper v1 wasn't ready for the real world. Narrow containers broke it. Touch devices couldn't use it. Vertical layouts weren't possible. And the animation engine had a race condition hiding in plain sight. So instead of patching, we rebuilt — keeping the same API surface but rearchitecting the internals to handle every scenario we'd been ignoring. The result is v2.0.0: the same component, but now it actually works everywhere.

✨ What's New

Vertical Orientation

Flip the stepper on its side. Navigation buttons switch to up/down arrows, keyboard keys remap automatically, and swipe gestures respond to vertical movement.

<SelectStepper
  data={['XS', 'S', 'M', 'L', 'XL']}
  orientation="vertical"
  label="Size"
/>
Enter fullscreen mode Exit fullscreen mode

The orientation prop is also responsive — vertical on mobile, horizontal on desktop:

<SelectStepper
  orientation={{ base: 'vertical', sm: 'horizontal' }}
  data={['XS', 'S', 'M', 'L', 'XL']}
/>
Enter fullscreen mode Exit fullscreen mode

Swipe & Touch Gestures

Native Pointer Events — no external dependencies. Swipe left/right in horizontal mode, up/down in vertical. Enabled by default with a configurable threshold.

<SelectStepper
  data={['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday']}
  swipeable            // true by default
  swipeThreshold={30}  // pixels
  loop
/>
Enter fullscreen mode Exit fullscreen mode

Imperative API

Control the stepper programmatically via controlRef. Navigate, reset, or jump to any value from parent code.

const controlRef = useRef<SelectStepperRef | null>(null);

<SelectStepper
  controlRef={controlRef}
  data={['React', 'Vue', 'Angular', 'Svelte']}
/>

// From anywhere in the parent:
controlRef.current?.next();
controlRef.current?.prev();
controlRef.current?.reset();
controlRef.current?.navigateTo('Svelte');
Enter fullscreen mode Exit fullscreen mode

Responsive Props (CSS-Native)

viewWidth, viewHeight, and size accept Mantine breakpoint objects. Under the hood, responsive values are resolved via InlineStyles with real CSS @media queries — the same pattern Mantine core uses in SimpleGrid and Grid. Zero React re-renders on resize.

<SelectStepper
  data={['React', 'Vue', 'Angular']}
  viewWidth={{ base: 120, sm: 180, md: 260 }}
  size={{ base: 'xs', sm: 'sm', md: 'md' }}
/>
Enter fullscreen mode Exit fullscreen mode

Size Prop

Control the ActionIcon button size independently from xs to xl. Supports responsive breakpoint objects.

<SelectStepper data={data} size="lg" />
<SelectStepper data={data} size={{ base: 'xs', md: 'md' }} />
Enter fullscreen mode Exit fullscreen mode

Animation Callbacks

Synchronize external logic with navigation transitions using onStepStart and onStepEnd.

<SelectStepper
  data={data}
  onStepStart={() => setAnimating(true)}
  onStepEnd={() => setAnimating(false)}
/>
Enter fullscreen mode Exit fullscreen mode

Gradient Variant

Full gradient support for navigation buttons, consistent with Mantine's variant system.

<SelectStepper
  data={['Low', 'Medium', 'High', 'Ultra']}
  variant="gradient"
  gradient={{ from: 'indigo', to: 'cyan', deg: 45 }}
/>
Enter fullscreen mode Exit fullscreen mode

Accessible Button Labels

Localize navigation button labels for screen readers.

<SelectStepper
  previousLabel="Vorheriges Element"
  nextLabel="Nächstes Element"
/>
Enter fullscreen mode Exit fullscreen mode

New Props Summary

Prop Type Default Description
orientation `StyleProp<'horizontal' \ 'vertical'>` 'horizontal'
viewHeight StyleProp<CSSProperties['height']> 36 Viewport height in vertical mode
size StyleProp<MantineSize> 'sm' ActionIcon button size
swipeable boolean true Enable touch/swipe navigation
swipeThreshold number 30 Min swipe distance in pixels
onStepStart () => void Fired when animation starts
onStepEnd () => void Fired when animation ends
controlRef `RefObject<SelectStepperRef \ null>`
previousLabel string 'Previous item' Aria label for prev button
nextLabel string 'Next item' Aria label for next button
gradient MantineGradient Gradient config for variant="gradient"

💥 Breaking Changes

defaultValue={null} no longer auto-selects

Before (v1): Passing defaultValue={null} auto-selected the first non-disabled item.
After (v2): defaultValue={null} means "no selection" — the component starts empty.

// If you relied on null to auto-select, just omit the prop:
<SelectStepper data={data} />  // ← auto-selects first item
Enter fullscreen mode Exit fullscreen mode

viewWidth type changed to StyleProp

The prop now accepts responsive objects. TypeScript users who explicitly type viewWidth as CSSProperties['width'] will need to update to StyleProp<CSSProperties['width']>. Runtime behavior is backward compatible.

onChange timing changed

The callback is now properly wired through Mantine's useUncontrolled hook. If you had workarounds for stale-state issues in v1, they may no longer be needed.

🐛 Bug Fixes

  • Timeout race condition — Rapid clicks no longer orphan timers or cause setState on unmounted components
  • Controlled mode sync — External value changes during animation no longer cause visual glitches
  • Loop animation — Infinite scroll now consistently moves forward instead of reversing at the boundary
  • Narrow container wrapping — Navigation buttons no longer wrap to a new line (thanks @kruschid)
  • View shrinking misalignment — Resizing below viewWidth no longer misaligns the scroll offset
  • Stale data handling — Changing data while value references a removed item now auto-resets
  • Callback consistencyonStepStart only fires when navigation actually occurs; onLeftIconClick/onRightIconClick respect canGoPrev/canGoNext
  • Demo import typo — Styles API demo no longer references the wrong package

🔧 Improvements

Full ARIA Accessibility

The component now implements role="spinbutton" with complete attributes: aria-valuemin, aria-valuemax, aria-valuenow, aria-valuetext, aria-disabled, aria-label, and aria-labelledby. A hidden aria-live="polite" region announces value changes to screen readers.

Orientation-Aware Keyboard Navigation

Arrow keys are now direction-specific: ArrowLeft/ArrowRight in horizontal, ArrowUp/ArrowDown in vertical. No more confusing cross-axis navigation.

66 Tests (up from 2)

Comprehensive coverage across: navigation, keyboard, controlled/uncontrolled mode, loop, disabled items, empty data, swipe gestures (with PointerEvent polyfill), imperative API, responsive props, ARIA attributes, and animation callbacks.

Documentation Reorganized

Docs follow the Mantine standard: Installation → Usage → Core → Behavior → Visual → Advanced → Reference. Eight new interactive demos added.

📦 Installation

yarn add @gfazioli/mantine-select-stepper
Enter fullscreen mode Exit fullscreen mode

Import styles at your app root:

import '@gfazioli/mantine-select-stepper/styles.css';
Enter fullscreen mode Exit fullscreen mode

Or with CSS layers:

import '@gfazioli/mantine-select-stepper/styles.layer.css';
Enter fullscreen mode Exit fullscreen mode

Compatibility: Mantine 8.x · React 18/19 · TypeScript 5.x · MIT License

🔗 Links

Top comments (0)