Mantine Select Stepper

Logo

@gfazioli/mantine-select-stepper

Mantine SelectStepper is a React component that allows users to navigate through a list of options using increment and decrement buttons, providing an intuitive alternative to traditional dropdown selects for cycling through predefined values.

Installation

yarn add @gfazioli/mantine-select-stepper

After installation import package styles at the root of your application:

import '@gfazioli/mantine-select-stepper/styles.css';

You can import styles within a layer @layer mantine-select-stepper by importing @gfazioli/mantine-select-stepper/styles.layer.css file.

import '@gfazioli/mantine-select-stepper/styles.layer.css';

Usage

This is a description

React

Angular

Vue

Svelte

Ember

Selected: React
Size
Animation duration
View width
View height
Radius
import { SelectStepper } from '@gfazioli/mantine-select-stepper';

function Demo(props: any) {
  return <SelectStepper label="Select framework" description="This is a description"
    data={['React', 'Angular', 'Vue', 'Svelte', 'Ember']} />;
}

Controlled

The SelectStepper component can be fully controlled through the value prop. Use onChange handler to update the value:

React

Vue

Angular

Selected: Vue
import { useState } from 'react';
import { SelectStepper } from '@gfazioli/mantine-select-stepper';

function Demo() {
  const [value, setValue] = useState<string | null>('Vue');

  return <SelectStepper data={['React', 'Vue', 'Angular']} value={value} onChange={setValue} />;
}

onChange handler

onChange is called with two arguments:

  • value - string value of the selected option
  • option – selected option object

If you prefer object format in state, use second argument of onChange handler:

import { useState } from 'react';
import {
  SelectStepper,
  type ComboboxItem,
} from '@gfazioli/mantine-select-stepper';

function Demo() {
  const [value, setValue] = useState<ComboboxItem | null>({
    value: 'vue',
    label: 'Vue.js Framework',
  });

  const data = [
    { value: 'react', label: 'React JS' },
    { value: 'vue', label: 'Vue.js Framework' },
    { value: 'angular', label: 'Angular Platform' },
  ];

  return (
    <SelectStepper
      data={data}
      value={value ? value.value : null}
      onChange={(_value, option) => setValue(option)}
    />
  );
}

Loop

Enable infinite scrolling by setting the loop prop. When enabled, the stepper will wrap around to the beginning when reaching the end, and vice versa.

React

Vue

Angular

Svelte

Solid

React

Vue

Angular

Svelte

Solid

React

Vue

Angular

Svelte

Solid

Selected: React
import { SelectStepper } from '@gfazioli/mantine-select-stepper';

function Demo() {
  return (
    <SelectStepper 
      data={['React', 'Vue', 'Angular', 'Svelte', 'Solid']} 
      loop 
    />
  );
}

Disabled items

You can disable specific items in the data array. Disabled items will be skipped when navigating through the options.

React

Vue

Angular

Svelte

Solid

Selected: React
import { SelectStepper } from '@gfazioli/mantine-select-stepper';

function Demo() {
  return (
    <SelectStepper
      data={[
        { value: 'react', label: 'React' },
        { value: 'vue', label: 'Vue', disabled: true },
        { value: 'angular', label: 'Angular' },
        { value: 'svelte', label: 'Svelte', disabled: true },
        { value: 'solid', label: 'Solid' },
      ]}
    />
  );
}

Disabled state

Set the disabled prop to disable the entire component. This will prevent any interaction with the stepper.

React

Vue

Angular

Selected: Vue
import { SelectStepper } from '@gfazioli/mantine-select-stepper';

function Demo() {
  return (
    <SelectStepper 
      data={['React', 'Vue', 'Angular']} 
      defaultValue="Vue"
      disabled 
    />
  );
}

Animation

Control the animation behavior with the animate, animationDuration, and animationTimingFunction props. You can create custom timing functions for unique effects, or disable animations entirely.

Use the onStepStart and onStepEnd callbacks to synchronize external logic with transitions.

Fast

Normal

Slow

Bouncy

Selected: Fast

Instant

Switch

No Animation

Selected: Instant
import { SelectStepper } from '@gfazioli/mantine-select-stepper';
import { Stack } from '@mantine/core';

function Demo() {
  return (
    <Stack>
      {/* Custom animation duration and timing */}
      <SelectStepper 
        data={['Fast', 'Normal', 'Slow', 'Bouncy']} 
        animationDuration={800} 
        animationTimingFunction="cubic-bezier(0.68, -0.55, 0.265, 1.55)" 
      />
      
      {/* No animation */}
      <SelectStepper 
        data={['Instant', 'Switch', 'No Animation']} 
        animate={false} 
      />
    </Stack>
  );
}

Keyboard navigation

SelectStepper supports keyboard navigation:

Horizontal mode (default):

  • ArrowLeft - Move to previous item
  • ArrowRight - Move to next item

Vertical mode:

  • ArrowUp - Move to previous item
  • ArrowDown - Move to next item

Navigation automatically skips disabled items.

Swipe gestures

SelectStepper supports touch/swipe navigation out of the box. Swipe left/right (or up/down in vertical mode) to navigate between items. Control this behavior with the swipeable and swipeThreshold props.

Swipe left/right or use buttons

Monday

Tuesday

Wednesday

Thursday

Friday

Monday

Tuesday

Wednesday

Thursday

Friday

Monday

Tuesday

Wednesday

Thursday

Friday

Selected: Monday
import { SelectStepper } from '@gfazioli/mantine-select-stepper';

function Demo() {
  return (
    <SelectStepper
      data={['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday']}
      swipeable
      swipeThreshold={30}
      loop
      label="Day of the week"
      description="Swipe left/right or use buttons"
    />
  );
}

Sizes

Use the size prop to control the ActionIcon and overall component size.

React

Vue

Angular

Selected: React

React

Vue

Angular

Selected: React

React

Vue

Angular

Selected: React

React

Vue

Angular

Selected: React

React

Vue

Angular

Selected: React
import { SelectStepper } from '@gfazioli/mantine-select-stepper';
import { Stack } from '@mantine/core';

function Demo() {
  return (
    <Stack>
      <SelectStepper data={['React', 'Vue', 'Angular']} size="xs" label="Extra small" />
      <SelectStepper data={['React', 'Vue', 'Angular']} size="sm" label="Small" />
      <SelectStepper data={['React', 'Vue', 'Angular']} size="md" label="Medium" />
      <SelectStepper data={['React', 'Vue', 'Angular']} size="lg" label="Large" />
      <SelectStepper data={['React', 'Vue', 'Angular']} size="xl" label="Extra large" />
    </Stack>
  );
}

Responsive props

The viewWidth, viewHeight, size, and orientation props support responsive values using Mantine breakpoint objects. This allows the component to fully adapt to different screen sizes — for example, switching from vertical on mobile to horizontal on desktop.

Responsive viewport width

The viewport and buttons grow with the screen

React

Vue

Angular

Svelte

Selected: React

Responsive orientation

Vertical on mobile, horizontal on tablet+

XS

S

M

L

XL

XS

S

M

L

XL

XS

S

M

L

XL

Selected: XS
import { SelectStepper } from '@gfazioli/mantine-select-stepper';
import { Stack, Text } from '@mantine/core';

function Demo() {
  return (
    <Stack>
      <div>
        <Text size="sm" fw={500}>Responsive viewport width</Text>
        <SelectStepper
          data={['React', 'Vue', 'Angular', 'Svelte']}
          viewWidth={{ base: 120, sm: 180, md: 260 }}
          size={{ base: 'xs', sm: 'sm', md: 'md' }}
          label="Framework"
          description="The viewport and buttons grow with the screen"
        />
      </div>

      <div>
        <Text size="sm" fw={500}>Responsive orientation</Text>
        <SelectStepper
          data={['XS', 'S', 'M', 'L', 'XL']}
          orientation={{ base: 'vertical', sm: 'horizontal' }}
          label="Size"
          description="Vertical on mobile, horizontal on tablet+"
          loop
        />
      </div>
    </Stack>
  );
}

Custom icons

Customize the left and right icons using the leftIcon and rightIcon props. You can use any React node as an icon.

16GB

32GB

64GB

128GB

256GB

Selected: 16GB
import { SelectStepper } from '@gfazioli/mantine-select-stepper';
import { IconMinus, IconPlus } from '@tabler/icons-react';

function Demo() {
  return (
    <SelectStepper
      data={['16GB', '32GB', '64GB', '128GB', '256GB']}
      leftIcon={<IconMinus size={16} />}
      rightIcon={<IconPlus size={16} />}
    />
  );
}

Custom width

Adjust the viewport width using the viewWidth prop. This controls how much space is allocated for displaying the current value.

Short

Medium Width

Very Long Item Name

Selected: Short
import { SelectStepper } from '@gfazioli/mantine-select-stepper';

function Demo() {
  return (
    <SelectStepper 
      data={['Short', 'Medium Width', 'Very Long Item Name']} 
      viewWidth={300} 
    />
  );
}

Vertical orientation

Set orientation="vertical" to render the stepper in vertical mode. Navigation buttons switch to up/down arrows, and swipe gestures respond to vertical movement.

XS

S

M

L

XL

XS

S

M

L

XL

XS

S

M

L

XL

Selected: XS

50%

75%

100%

125%

150%

Selected: 50%
import { SelectStepper } from '@gfazioli/mantine-select-stepper';
import { Group } from '@mantine/core';

function Demo() {
  return (
    <Group>
      <SelectStepper
        data={['XS', 'S', 'M', 'L', 'XL']}
        orientation="vertical"
        label="Size"
        loop
      />
      <SelectStepper
        data={['50%', '75%', '100%', '125%', '150%']}
        orientation="vertical"
        label="Zoom"
      />
    </Group>
  );
}

With border

Add a border to the component using the withBorder prop.

React

Vue

Angular

Selected: React

React

Vue

Angular

Selected: React
import { SelectStepper } from '@gfazioli/mantine-select-stepper';
import { Stack } from '@mantine/core';

function Demo() {
  return (
    <Stack>
      {/* With border */}
      <SelectStepper 
        data={['React', 'Vue', 'Angular']} 
        withBorder 
      />
      
      {/* Without border (default) */}
      <SelectStepper 
        data={['React', 'Vue', 'Angular']} 
        withBorder={false} 
      />
    </Stack>
  );
}

Gradient variant

Use variant="gradient" with the gradient prop to apply gradient styling to the navigation buttons.

Low

Medium

High

Ultra

Selected: Low
import { SelectStepper } from '@gfazioli/mantine-select-stepper';

function Demo() {
  return (
    <SelectStepper
      data={['Low', 'Medium', 'High', 'Ultra']}
      variant="gradient"
      gradient={{ from: 'indigo', to: 'cyan', deg: 45 }}
      label="Quality"
    />
  );
}

Styles API

SelectStepper supports Styles API, you can add styles to any inner element of the component with classNames prop. Follow Styles API documentation to learn more.

This is a description

Hello

Selected: Hello

This is an error

Component Styles API

Hover over selectors to highlight corresponding elements

/*
 * Hover over selectors to apply outline styles
 *
 */

Imperative API

Use the controlRef prop to access imperative methods: next(), prev(), reset(), and navigateTo(value). The standard ref prop still provides access to the underlying DOM element.

React

Vue

Angular

Svelte

Ember

Selected: React
import { useRef } from 'react';
import { SelectStepper, type SelectStepperRef } from '@gfazioli/mantine-select-stepper';
import { Button, Group, Stack } from '@mantine/core';

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

  return (
    <Stack>
      <SelectStepper
        controlRef={controlRef}
        data={['React', 'Vue', 'Angular', 'Svelte', 'Ember']}
        label="Framework"
      />
      <Group>
        <Button size="xs" variant="light" onClick={() => controlRef.current?.prev()}>
          Prev
        </Button>
        <Button size="xs" variant="light" onClick={() => controlRef.current?.next()}>
          Next
        </Button>
        <Button size="xs" variant="light" onClick={() => controlRef.current?.navigateTo('Svelte')}>
          Go to Svelte
        </Button>
        <Button size="xs" variant="subtle" onClick={() => controlRef.current?.reset()}>
          Reset
        </Button>
      </Group>
    </Stack>
  );
}

Custom rendering

renderOption prop

Use the renderOption prop to customize how each option is displayed. This function receives a ComboboxItem object and should return a React node. This is useful when you want to add additional visual elements like badges, icons, or custom formatting to your options.

React
(5 chars)
Vue
(3 chars)
Angular
(7 chars)
Svelte
(6 chars)
Solid
(5 chars)
Selected: React
import { SelectStepper } from '@gfazioli/mantine-select-stepper';
import { Badge, Group } from '@mantine/core';

function Demo() {
  return (
    <SelectStepper
      data={['React', 'Vue', 'Angular', 'Svelte', 'Solid']}
      renderOption={(item) => (
        <Group gap="xs">
          <Badge variant="light">{item.label}</Badge>
          <span style={{ fontSize: 12, color: '#888' }}>({item.value.length} chars)</span>
        </Group>
      )}
    />
  );
}

Custom data with extended properties

You can extend the ComboboxItem interface to include additional properties in your data. When using the onChange handler, the second argument will contain the complete option object with all custom properties. This is perfect for storing metadata alongside your options.

{
  "value": "vue",
  "label": "Vue.js",
  "version": "3.2.37",
  "color": "green"
}
React
v18.2.0
Vue.js
v3.2.37
Angular
v13.3.0
Selected: Vue.js
import { useState } from 'react';
import { SelectStepper, type ComboboxItem } from '@gfazioli/mantine-select-stepper';
import { Badge, Code, Group, Stack } from '@mantine/core';

interface ComplexItem extends ComboboxItem {
  version?: string;
  color?: string;
}

function Demo() {
  const [value, setValue] = useState<ComplexItem | null>({ 
    value: 'vue', 
    label: 'Vue.js', 
    version: '3.2.37',
    color: 'green'
  });

  const data: ComplexItem[] = [
    { value: 'react', label: 'React', version: '18.2.0', color: 'blue' },
    { value: 'vue', label: 'Vue.js', version: '3.2.37', color: 'green' },
    { value: 'angular', label: 'Angular', version: '13.3.0', color: 'red' },
  ];

  return (
    <Stack>
      <Code block>{JSON.stringify(value, null, 2)}</Code>
      
      <SelectStepper
        data={data}
        value={value ? value.value : null}
        onChange={(_value, option) => setValue(option as ComplexItem)}
        renderOption={(item) => {
          const complexItem = item as ComplexItem;
          return (
            <Group gap="xs">
              <Badge color={complexItem.color || 'gray'}>
                {complexItem.label}
              </Badge>
              <span style={{ fontSize: 11, color: '#888' }}>
                v{complexItem.version}
              </span>
            </Group>
          );
        }}
        viewWidth={280}
      />
    </Stack>
  );
}

Empty value

Use the emptyValue prop to display custom content when no options are available.

No frameworks available

No options

import { SelectStepper } from '@gfazioli/mantine-select-stepper';
import { Text } from '@mantine/core';

function Demo() {
  return (
    <SelectStepper
      data={[]}
      emptyValue={<Text c="dimmed" fs="italic">No options</Text>}
      label="Framework"
      description="No frameworks available"
    />
  );
}

Use in forms

SelectStepper integrates seamlessly with other Mantine form components. It works well alongside inputs, selects, and other form controls, making it perfect for multi-step forms or configuration panels.

16GB

32GB

64GB

Selected: 16GB
import { IconMinus, IconPlus } from '@tabler/icons-react';
import { Flex, Group, Stack, TextInput } from '@mantine/core';
import { SelectStepper } from '@gfazioli/mantine-select-stepper';

function Demo() {
  return (
    <Stack>
      <TextInput placeholder="Server application" label="Name" />

      <Group grow>
        <Flex>
          <SelectStepper
            viewWidth={100}
            leftIcon={<IconMinus />}
            rightIcon={<IconPlus />}
            data={['16GB', '32GB', '64GB']}
            label="Ram"
          />
        </Flex>
        <TextInput label="Location" placeholder="Region" />
      </Group>
    </Stack>
  );
}

Boolean stepper pattern

The SelectStepper can be effectively used as a boolean toggle by combining several features. This pattern is useful when you want to create an interactive on/off switch with enhanced visual feedback.

Key features demonstrated in this example:

  • Dynamic Icons: The leftIcon and rightIcon props change based on the current value, providing visual context
  • Custom Rendering: The renderOption prop displays the option as a colored badge (green for enabled, red for disabled)
  • Loop Navigation: With loop enabled, users can continuously toggle between the two states
  • Controlled State: The component maintains the boolean value as a string ('true' or 'false')

This pattern is particularly useful for:

  • Security settings (enable/disable features)
  • Feature flags and toggles
  • Binary choices with visual distinction
  • Settings that benefit from explicit labeling of both states
Malware Protection
Malware Protection
Malware Protection
Malware Protection
Malware Protection
Malware Protection
Selected: Malware Protection
import { useState } from 'react';
import { SelectStepper } from '@gfazioli/mantine-select-stepper';
import { IconCheck, IconMinus, IconPlus } from '@tabler/icons-react';
import { Badge } from '@mantine/core';

function Demo() {
  const [value, setValue] = useState<string | null>('false');

  return (
    <SelectStepper
      viewWidth={200}
      animate={false}
      value={value}
      loop
      label={`Boolean Stepper: ${value}`}
      variant="subtle"
      leftIcon={value === 'true' ? <IconCheck size={16} /> : null}
      rightIcon={value === 'false' ? <IconPlus size={16} /> : <IconMinus color="red" size={16} />}
      onChange={setValue}
      data={[
        {
          value: 'false',
          label: 'Malware Protection',
        },
        {
          value: 'true',
          label: 'Malware Protection',
        },
      ]}
      renderOption={(item) => (
        <Badge color={item.value === 'true' ? 'green' : 'red'}>{item.label}</Badge>
      )}
    />
  );
}

Use cases

SelectStepper is particularly useful in scenarios where you have a limited set of sequential options and want to provide an intuitive navigation experience:

  • Settings and Preferences: Choose between predefined options like theme modes (Light/Dark/Auto), text sizes (Small/Medium/Large), or quality levels (Low/Medium/High)
  • Step-by-step Wizards: Navigate through sequential steps in a form or configuration process
  • Rating Systems: Select ratings or levels (Beginner/Intermediate/Advanced, or 1-5 stars)
  • Time Selection: Choose between time slots, hours, or predefined time intervals
  • Difficulty Levels: Select game difficulty, exercise intensity, or skill levels
  • Priority Selection: Choose task priorities (Low/Medium/High/Critical)
  • Size Selection: Pick product sizes (XS/S/M/L/XL) in e-commerce applications
  • View Modes: Switch between different view layouts (List/Grid/Table)
  • Playback Speed: Control media playback speed (0.5x/1x/1.5x/2x)
  • Zoom Levels: Select predefined zoom percentages (50%/75%/100%/125%/150%)

The component is especially effective when:

  • The number of options is relatively small (typically 3-10 items)
  • Options have a natural sequential order
  • Users benefit from seeing the current selection prominently
  • Space is limited and a traditional select dropdown would be too large
  • You want to encourage exploration of available options through keyboard or button navigation