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는 단계가 클릭 가능한지 여부에 따라 자동으로 관리돼요.