사용자 경험을 고려한 로딩 전략은 프론트엔드 개발에서 점점 더 중요해지고 있습니다. 스켈레톤 UI는 이러한 맥락에서 등장한 대표적인 UI 패턴 중 하나입니다. 로딩 스피너보다 시각적으로 더 안정감을 주며, 콘텐츠가 준비되고 있다는 명확한 피드백을 제공합니다.
스켈레톤 UI(Skeleton UI)는 실제 콘텐츠가 로딩되기 전, 콘텐츠의 레이아웃 구조를 미리 시각적으로 보여주는 UI입니다. 일반적으로 회색의 박스 형태로 콘텐츠의 형태를 대신 표현하며, 흔히 animate-pulse 애니메이션을 통해 미묘한 깜빡임 효과를 줍니다.
이처럼 스켈레톤 UI는 단순히 ‘심심한 로딩화면’의 대체재가 아니라, 실제로 사용자 경험에 큰 영향을 주는 중요한 요소입니다.
TailwindCSS의 animate-pulse 클래스는 기본적인 깜빡임 효과를 제공합니다. 여기에 필요한 width/height/rounded 클래스 등을 조합해 유연한 Skeleton 컴포넌트를 만들 수 있습니다.
// components/Skeleton.tsx
interface SkeletonProps {
className?: string;
}
const Skeleton = ({ className = '' }: SkeletonProps) => {
return <div className={`bg-gray-200 rounded-md animate-pulse ${className}`} />;
};
export default Skeleton;
해당 컴포넌트는 다음과 같은 방식으로 다양한 곳에 재사용할 수 있습니다:
<Skeleton className="w-32 h-4 mb-2" />
<Skeleton className="w-16 h-3 mt-1" />
<Skeleton className="h-10 w-full rounded-lg" />
대표적인 케이스 중 하나는 카드 UI입니다. 로딩 중임에도 불구하고 레이아웃이 유지되어 사용자에게 일관된 인상을 주는 것이 핵심입니다.
// components/StoreCardSkeleton.tsx
import Skeleton from './Skeleton';
export default function StoreCardSkeleton() {
return (
<div className="flex gap-3 p-3 bg-white rounded-2xl border border-gray-100 shadow-sm w-full max-w-md">
<Skeleton className="w-24 h-24 rounded-xl flex-shrink-0" />
<div className="flex flex-col justify-between flex-1 py-1">
<div>
<Skeleton className="w-32 h-4 mb-2" />
<Skeleton className="w-20 h-3 mb-1" />
<Skeleton className="w-24 h-3" />
</div>
<Skeleton className="w-16 h-4 mt-2" />
</div>
</div>
);
}
페이지 상에서는 isLoading 여부를 기준으로 스켈레톤 컴포넌트를 렌더링합니다.
{isLoading
? Array.from({ length: 4 }).map((_, i) => <StoreCardSkeleton key={i} />)
: storeList.map((store) => <StoreCard key={store.id} {...store} />)}
이처럼 같은 구조의 컴포넌트를 뼈대로 먼저 보여준 뒤, 실제 데이터를 연결하는 방식은 사용자에게 일관된 시각적 경험을 제공합니다.
스켈레톤 UI는 카드에만 국한되지 않습니다. 다양한 UI 요소에 적용할 수 있으며, 예시 몇 가지를 아래에 소개합니다.
<div className="flex gap-4 items-center mb-4">
<Skeleton className="w-10 h-10 rounded-full" />
<div className="flex-1">
<Skeleton className="w-1/2 h-4 mb-1" />
<Skeleton className="w-1/3 h-3" />
</div>
</div>
<Skeleton className="w-32 h-10 rounded-full" />
<Skeleton className="h-10 w-full rounded-md" />
기본 색상은 bg-gray-200이지만, 더 밝게(bg-gray-100) 또는 어둡게(bg-gray-300, bg-gray-400) 조절할 수 있습니다. 다크 모드 대응은 dark:bg-gray-700 등을 추가합니다.
animate-pulse 외에도 커스텀 shimmer 애니메이션을 직접 만들 수 있습니다.
@keyframes shimmer {
0% { background-position: -400px 0; }
100% { background-position: 400px 0; }
}
스크린리더가 읽지 않도록 aria-hidden, role="presentation" 등을 추가합니다.
<div aria-hidden="true" role="presentation" className="animate-pulse bg-gray-200 ..." />
react-loading-skeleton을 예로 들 수 있습니다.
npm install react-loading-skeleton
import Skeleton from 'react-loading-skeleton';
<Skeleton height={20} width={100} borderRadius={8} />
장점은 빠르게 적용 가능하다는 점, 단점은 커스터마이징 제한과 Tailwind와의 스타일 격리 문제입니다.
isLoading, isFetching 등의 상태를 정확하게 구분하는 것이 중요합니다.min-width, min-height 등을 적용하여 스켈레톤이 레이아웃을 잡아주도록 합니다.Suspense와 fallback을 활용하면 컴포넌트 단위의 스켈레톤 처리도 가능합니다.theme.ts 또는 style-guide.ts)onLoad 이벤트 기반으로 스켈레톤 제거스켈레톤 UI는 이제 더 이상 단순한 시각적 장치가 아닌, 전체 UX를 좌우하는 핵심 구성 요소입니다. 특히 API 연동이 많은 SPA 환경에서는 사용자의 인내심을 유지시키는 데 매우 효과적입니다. TailwindCSS와 React 조합만으로도 쉽고 유연하게 구성할 수 있으니, 프로젝트에 반드시 도입해보길 추천합니다.