이 글은 누구를 위한 것인가
- 신규 사용자의 초기 이탈을 줄이려는 팀
- 복잡한 기능을 단계별로 소개하는 온보딩을 설계하는 개발자
- 온보딩 완료율을 측정하고 개선하려는 팀
들어가며
"기능이 너무 많아서 어디서부터 시작해야 할지 모르겠어요" — 이것이 신규 사용자 이탈의 주원인이다. 점진적 공개(Progressive Disclosure)는 처음에는 핵심 기능만 보여주고, 숙련도가 높아질수록 더 많은 기능을 드러낸다.
이 글은 bluefoxdev.kr의 온보딩 UX 점진적 공개 가이드 를 참고하여 작성했습니다.
1. 온보딩 패턴 선택 가이드
[온보딩 유형]
1. 빈 상태 온보딩 (Empty State)
- 데이터가 없을 때 안내 + CTA
- 가장 비침습적
- 예: "첫 상품을 추가해보세요 →"
2. 체크리스트 (Setup Checklist)
- 완료해야 할 항목 목록
- 게이미피케이션 효과 (진행률)
- 예: Stripe, Notion 첫 설정
3. 프로덕트 투어 (Product Tour)
- 단계별 툴팁/하이라이트
- 스킵 가능해야 함
- 첫 접속 1회만, 재실행 가능
4. 인라인 도움말
- 아이콘 클릭 시 설명 팝오버
- 필요할 때 볼 수 있음
- 가장 비침습적
[점진적 공개 원칙]
레벨 1: 핵심 기능만 (80% 사용자)
레벨 2: 중급 기능 (고급 설정 토글)
레벨 3: 전문가 기능 (숨김 메뉴)
예: 이메일 에디터
기본: 텍스트, 이미지, 버튼
고급: HTML 편집, 사용자 정의 CSS
[완료율 측정]
onboarding_started → step1_completed
→ step2_completed → onboarding_completed
각 단계 이탈률 분석
이탈 많은 단계 개선
2. 온보딩 구현
import { useState, useEffect } from 'react';
// 체크리스트 온보딩
interface OnboardingStep {
id: string;
title: string;
description: string;
action: string;
actionUrl: string;
completed: boolean;
}
function OnboardingChecklist({ steps, onStepComplete }: {
steps: OnboardingStep[];
onStepComplete: (stepId: string) => void;
}) {
const completed = steps.filter(s => s.completed).length;
const progress = Math.round(completed / steps.length * 100);
return (
<div style={{ border: '1px solid #e2e8f0', borderRadius: 12, padding: 24 }}>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 16 }}>
<h3 style={{ fontWeight: 600 }}>시작하기 ({completed}/{steps.length})</h3>
<span style={{ color: '#10b981', fontWeight: 600 }}>{progress}%</span>
</div>
{/* 진행률 바 */}
<div style={{ height: 6, background: '#e2e8f0', borderRadius: 3, marginBottom: 20 }}>
<div style={{ width: `${progress}%`, height: '100%', background: '#10b981', borderRadius: 3, transition: 'width 0.4s ease' }} />
</div>
{steps.map(step => (
<div key={step.id} style={{ display: 'flex', alignItems: 'flex-start', gap: 12, padding: '12px 0', borderBottom: '1px solid #f1f5f9' }}>
<button
onClick={() => !step.completed && onStepComplete(step.id)}
aria-label={step.completed ? `${step.title} 완료` : `${step.title} 시작`}
style={{
width: 24, height: 24, borderRadius: '50%', flexShrink: 0,
background: step.completed ? '#10b981' : 'white',
border: `2px solid ${step.completed ? '#10b981' : '#d1d5db'}`,
cursor: step.completed ? 'default' : 'pointer',
display: 'flex', alignItems: 'center', justifyContent: 'center',
}}
>
{step.completed && <span style={{ color: 'white', fontSize: 12 }}>✓</span>}
</button>
<div style={{ flex: 1 }}>
<p style={{ fontWeight: 500, color: step.completed ? '#9ca3af' : '#111827', textDecoration: step.completed ? 'line-through' : 'none' }}>
{step.title}
</p>
<p style={{ fontSize: 13, color: '#6b7280', marginTop: 2 }}>{step.description}</p>
{!step.completed && (
<a href={step.actionUrl} style={{ fontSize: 13, color: '#3b82f6', textDecoration: 'none', fontWeight: 500 }}>
{step.action} →
</a>
)}
</div>
</div>
))}
</div>
);
}
// 프로덕트 투어 훅
function useProductTour(tourId: string) {
const [currentStep, setCurrentStep] = useState(-1);
const [isActive, setIsActive] = useState(false);
useEffect(() => {
const completed = localStorage.getItem(`tour:${tourId}`);
if (!completed) {
setIsActive(true);
setCurrentStep(0);
}
}, [tourId]);
const next = () => setCurrentStep(s => s + 1);
const skip = () => {
localStorage.setItem(`tour:${tourId}`, 'completed');
setIsActive(false);
setCurrentStep(-1);
};
const complete = () => {
localStorage.setItem(`tour:${tourId}`, 'completed');
setIsActive(false);
analytics.track('onboarding_completed', { tourId });
};
return { currentStep, isActive, next, skip, complete };
}
const analytics = { track: (e: string, d: any) => {} };
마무리
온보딩의 성공은 "완료율"로 측정한다. 각 단계의 이탈률을 추적하고, 이탈이 많은 단계를 단순화한다. 체크리스트 패턴은 달성감을 주어 완료율이 높고, 프로덕트 투어는 항상 스킵 가능하게 해 강요받는 느낌을 주지 않아야 한다.