Stepper

Stepper

콘텐츠를 단계(step) 시퀀스로 나누어 표시하는 컴포넌트예요. 다단계 프로세스나 가입 흐름을 표현할 때 사용해요.

출처: 문서

본문

사용법 (Usage)

Stepper 컴포넌트는 콘텐츠를 일련의 단계로 나누어 보여줘요. active prop으로 현재 활성 단계를 제어해요.

import { useState } from 'react';
import { Stepper, Button, Group } from '@mantine/core';

function Demo() {
  const [active, setActive] = useState(1);
  const nextStep = () => setActive((current) => (current < 3 ? current + 1 : current));
  const prevStep = () => setActive((current) => (current > 0 ? current - 1 : current));

  return (
    <>
      <Stepper active={active} onStepClick={setActive}>
        <Stepper.Step label="First step" description="Create an account">
          Step 1 content: Create an account
        </Stepper.Step>
        <Stepper.Step label="Second step" description="Verify email">
          Step 2 content: Verify email
        </Stepper.Step>
        <Stepper.Step label="Final step" description="Get full access">
          Step 3 content: Get full access
        </Stepper.Step>
        <Stepper.Completed>
          Completed, click back button to get to previous step
        </Stepper.Completed>
      </Stepper>

      <Group justify="center" mt="xl">
        <Button variant="default" onClick={prevStep}>Back</Button>
        <Button onClick={nextStep}>Next step</Button>
      </Group>
    </>
  );
}

단계 선택 허용 (Allow step select)

단계 선택을 비활성화하려면 Stepper.Step 컴포넌트에 allowStepSelect prop을 설정해요. 이를 통해 사용자가 아직 도달하지 않은 다음 단계로는 진행하지 못하게 하면서, 이미 도달한 단계 사이에서는 자유롭게 오갈 수 있게 할 수 있어요.

import { useState } from 'react';
import { Stepper, Button, Group } from '@mantine/core';

function Demo() {
  const [active, setActive] = useState(1);
  const [highestStepVisited, setHighestStepVisited] = useState(active);

  const handleStepChange = (nextStep: number) => {
    const isOutOfBounds = nextStep > 3 || nextStep < 0;
    if (isOutOfBounds) return;
    setActive(nextStep);
    setHighestStepVisited((hSC) => Math.max(hSC, nextStep));
  };

  // 방문한 단계 사이를 자유롭게 오갈 수 있게 해요.
  const shouldAllowSelectStep = (step: number) => highestStepVisited >= step && active !== step;

  return (
    <>
      <Stepper active={active} onStepClick={setActive} allowNextStepsSelect={false}>
        {/* Stepper.Step 단계 정의 */}
      </Stepper>
      <Group justify="center" mt="xl">
        <Button variant="default" onClick={() => handleStepChange(active - 1)}>Back</Button>
        <Button onClick={() => handleStepChange(active + 1)}>Next step</Button>
      </Group>
    </>
  );
}

다음 단계 선택 비활성화 (Disable next steps selection)

다가오는 단계의 선택을 비활성화하는 또 다른 방법은 Stepper 컴포넌트에 직접 allowNextStepsSelect를 사용하는 거예요. 이는 각 단계별로 동작을 세밀하게 제어할 필요가 없을 때 유용해요.

import { useState } from 'react';
import { Stepper, Button, Group } from '@mantine/core';

function Demo() {
  const [active, setActive] = useState(1);
  const nextStep = () => setActive((current) => (current < 3 ? current + 1 : current));
  const prevStep = () => setActive((current) => (current > 0 ? current - 1 : current));

  return (
    <>
      <Stepper active={active} onStepClick={setActive} allowNextStepsSelect={false}>
        {/* Stepper.Step 단계 정의 */}
      </Stepper>
      <Group justify="center" mt="xl">
        <Button variant="default" onClick={prevStep}>Back</Button>
        <Button onClick={nextStep}>Next step</Button>
      </Group>
    </>
  );
}

색상, 반경, 크기 (Color, radius and size)

Stepper에 color, radius, size prop을 적용할 수 있어요.

import { Stepper } from '@mantine/core';

function Demo() {
  return (
    <Stepper active={1} color="teal" radius="lg" size="md">
      {/* Stepper.Step 단계 정의 */}
    </Stepper>
  );
}

컴포넌트 크기는 size와 iconSize 두 가지 prop으로 제어돼요. size prop은 아이콘 크기, 라벨과 설명의 글꼴 크기를 제어해요. iconSize는 아이콘 크기를 다른 크기 값과 별도로 덮어쓸 수 있게 해줘요.

import { Stepper } from '@mantine/core';

function Demo() {
  return (
    <Stepper active={1} iconSize={52}>
      {/* Stepper.Step 단계 정의 */}
    </Stepper>
  );
}

커스텀 아이콘 사용 (With custom icons)

Stepper.Step 컴포넌트에 icon prop을 설정해 단계 아이콘을 바꿀 수 있어요. 완료 체크 아이콘을 바꾸려면 Stepper 컴포넌트에 completedIcon을 설정해요. 아이콘으로는 컴포넌트, 문자열, 숫자 등 어떤 React 노드든 사용할 수 있어요.

import { useState } from 'react';
import { UserCheckIcon, EnvelopeOpenIcon, ShieldCheckIcon, CheckCircleIcon } from '@phosphor-icons/react';
import { Stepper } from '@mantine/core';

function Demo() {
  const [active, setActive] = useState(1);

  return (
    <Stepper
      active={active}
      onStepClick={setActive}
      completedIcon={<CheckCircleIcon size={24} />}
    >
      <Stepper.Step
        icon={<UserCheckIcon size={24} />}
        label="Step 1"
        description="Create an account"
      />
      <Stepper.Step
        icon={<EnvelopeOpenIcon size={24} />}
        label="Step 2"
        description="Verify email"
      />
      <Stepper.Step
        icon={<ShieldCheckIcon size={24} />}
        label="Step 3"
        description="Get full access"
      />
    </Stepper>
  );
}

Stepper를 아이콘만으로 사용할 수도 있어요. 이 경우 접근성을 위해 Stepper.Step 컴포넌트에 aria-label 또는 title을 설정해야 해요.

각 단계의 완료 아이콘을 바꿔 오류 상태 등을 표시할 수도 있어요.

import { Stepper } from '@mantine/core';
import { XCircleIcon } from '@phosphor-icons/react';

function Demo() {
  return (
    <Stepper active={1}>
      <Stepper.Step completedIcon={<XCircleIcon size={24} />} label="Step 1" />
    </Stepper>
  );
}

세로 방향 (Vertical orientation)

import { useState } from 'react';
import { Stepper } from '@mantine/core';

function Demo() {
  const [active, setActive] = useState(1);

  return (
    <Stepper active={active} onStepClick={setActive} orientation="vertical">
      {/* Stepper.Step 단계 정의 */}
    </Stepper>
  );
}

라벨 위치 (Label position)

labelPosition="bottom"을 설정하면 단계 라벨과 설명이 단계 아이콘 아래에 표시돼요.

import { useState } from 'react';
import { Stepper } from '@mantine/core';

function Demo() {
  const [active, setActive] = useState(1);
  return (
    <Stepper active={active} onStepClick={setActive} labelPosition="bottom">
      {/* Stepper.Step 단계 정의 */}
    </Stepper>
  );
}

아이콘 위치 (Icon position)

단계 아이콘과 본문의 배치를 바꾸려면 iconPosition="right"를 설정해요.

import { useState } from 'react';
import { Stepper } from '@mantine/core';

function Demo() {
  const [active, setActive] = useState(1);

  return (
    <Stepper active={active} onStepClick={setActive} iconPosition="right">
      {/* Stepper.Step 단계 정의 */}
    </Stepper>
  );
}

로딩 상태 (Loading state)

로딩 상태를 표시하려면 Step 컴포넌트에 loading prop을 설정해요. 그러면 Loader가 단계 아이콘을 대체해요. 기본 로더는 theme에서 설정할 수 있어요.

import { Stepper } from '@mantine/core';

function Demo() {
  return (
    <Stepper active={1}>
      <Stepper.Step label="Step 1" loading />
    </Stepper>
  );
}

Styles API

Stepper는 Styles API를 지원해요. classNames prop으로 컴포넌트의 내부 요소에 스타일을 추가할 수 있어요. 자세한 내용은 Styles API 문서를 참고해요.

selector 설명
root 루트 요소
steps 단계 컨트롤 래퍼
separator 단계 컨트롤 사이의 구분선
verticalSeparator 단계 컨트롤 사이의 세로 구분선
content 현재 단계 콘텐츠 래퍼
stepWrapper 단계 아이콘과 구분선용 래퍼
step 단계 컨트롤 버튼
stepIcon 단계 아이콘 래퍼
stepCompletedIcon 완료된 단계 아이콘, stepIcon 안에 렌더링돼요
stepIconContent 완료되지 않은 단계의 아이콘 콘텐츠 래퍼, stepIcon 안에 렌더링돼요
stepBody stepLabel과 stepDescription을 포함해요
stepLabel 단계 라벨
stepDescription 단계 설명
stepLoader 단계 로더

단계 ref 가져오기 (Get step ref)

단계 버튼과 stepper 루트 요소(div)의 ref를 가져올 수 있어요.

import { useRef } from 'react';
import { Stepper } from '@mantine/core';

function MyStepper() {
  const firstStep = useRef<HTMLButtonElement>(null);
  const stepper = useRef<HTMLDivElement>(null);

  return (
    <Stepper active={1} ref={stepper}>
      <Stepper.Step ref={firstStep} label="Step 1" />
    </Stepper>
  );
}

Stepper.Step 래핑 (Wrap Stepper.Step)

Stepper 컴포넌트는 Stepper.Step의 순서에 의존해요. Stepper.Step을 래핑하는 것은 지원되지 않아요. 대신 다른 접근 방식을 사용해야 해요.

import { Stepper } from '@mantine/core';

// 이 방식은 동작하지 않아요, step 자식이 렌더링되지 않아요
function WillNotWork() {
  return (
    <Stepper.Step label="Step 1">
      This part will not render
    </Stepper.Step>
  );
}

// 자식용으로 별도의 컴포넌트를 만들어요
function WillWork() {
  return <div>This will work as expected!</div>;
}

function Demo() {
  return (
    <Stepper active={1}>
      <Stepper.Step label="First step">
        <WillWork />
      </Stepper.Step>
      {/* 래핑한 Stepper.Step은 자식을 렌더링하지 않으므로 하지 마세요 */}
      <Stepper.Step label="Third step" />
    </Stepper>
  );
}

접근성 (Accessibility)

Stepper.Step 컴포넌트는 button 요소를 렌더링해요. label이나 description을 지정하지 않은 경우 화면 판독기가 볼 수 있도록 aria-label 또는 title props를 설정해요.

import { Stepper } from '@mantine/core';

function Demo() {
  return (
    <Stepper active={1}>
      {/* Not ok, no label for screen reader */}

      {/* Ok, label and description */}
      <Stepper.Step label="Step 1" description="Description" />

      {/* Ok, aria-label */}
      <Stepper.Step aria-label="Step 1" />
    </Stepper>
  );
}

키보드 내비게이션 (Keyboard Navigation)

Stepper는 완전한 키보드 내비게이션을 지원해요.

  • Tab / Shift+Tab – 클릭 가능한 단계 사이로 포커스를 이동해요
  • Space / Enter – 포커스된 단계를 활성화해요
  • 각 단계는 적절한 ARIA 속성을 가진 button 요소예요

클릭할 수 없는 비활성 단계는 Tab 내비게이션 중 건너뛰어져요. tabIndex는 단계가 클릭 가능한지 여부에 따라 자동으로 관리돼요.

더 알아보기 (Learn more)