Pagination
페이지 내비게이션 — 비제어(defaultPage) + onPageChange 알림. 줄임 로직(1 … 9 10 11 … 20), 경계에서 이전/다음 disabled.
마지막 업데이트 2026-06-11
한눈에#
목록·검색 결과를 페이지 단위로 끊어 이동하는 내비게이션입니다 — 현재 페이지 ±1과 양끝만 남기고 사이를 줄임표(…)로 접습니다.
- 줄임 로직(1 … 9 10 11 … 20)
- 비제어 + onPageChange
- numeric·dots
- 44px 히트 영역
경계에서 이전/다음이 자동 비활성 — 현재 페이지는 aria-current=page
직접 클릭해 보세요 — 현재 페이지 ±1과 양끝(1, 20)만 남기고 사이를 줄임표(…)로 접습니다.
사용 시점#
목록·검색 결과를 페이지 단위로 끊어 이동하면 Pagination — 편집·정렬이 있는 운영 표는 DataGrid가 합성합니다.
쓴다
목록·검색 결과를 페이지 단위로 끊어 이동(URL이 진실의 원천이면 page로 제어)
플레이그라운드#
컨트롤로 props를 조작하면 미리보기와 코드가 실시간 갱신됩니다.
import { Pagination } from '@wds/ui-web';
<Pagination totalPages={10} />변형#
비제어(defaultPage)와 제어(page)를 모두 지원합니다. 비제어는 현재
페이지를 내부에서 소유하고 변경만 알립니다. URL이 진실의 원천이어야 하는 경우
page로 제어하세요 — 클릭은 onPageChange로만 통지하고 현재 페이지는 외부
값을 따릅니다(함수 prop은 서버 MDX로 전달 불가라 코드로 보입니다):
'use client';
// 비제어 — 컴포넌트가 페이지를 소유, 라우터는 부수효과
function ProductListPagination({ totalPages }: { totalPages: number }) {
const router = useRouter();
return (
<Pagination
totalPages={totalPages}
defaultPage={1}
onPageChange={(page) => router.push(`?page=${page}`)}
/>
);
}
// 제어 — URL search param이 진실의 원천(page가 권위)
function ControlledPagination({ totalPages }: { totalPages: number }) {
const params = useSearchParams();
const router = useRouter();
const page = Number(params.get('page') ?? 1);
return (
<Pagination
totalPages={totalPages}
page={page}
onPageChange={(next) => router.push(`?page=${next}`)}
/>
);
}
크기#
size prop이 없습니다 — 버튼은 시각 32px 정사각형(space.8)에
44px 히트 영역이 확장된 단일 크기입니다.
상태#
- 현재 페이지 —
primary-subtle배경 +primary-text+ semibold (aria-current="page") - 경계 disabled — 1페이지에서 이전, 마지막에서 다음 버튼이 비활성
- hover —
surface-hover배경, focus — 2pxfocus-ring링 - 이미 현재인 페이지를 클릭하면
onPageChange가 호출되지 않습니다
Props#
| Prop | 타입 | 기본값 | 설명 |
|---|---|---|---|
totalPages | number | — | 전체 페이지 수 (1 이상, 필수) |
page | number | — | 제어 현재 페이지 — 주면 page가 권위(onPageChange로만 통지) · 범위 밖 값은 클램프 |
defaultPage | number | 1 | 초기 현재 페이지 (비제어) — 범위 밖 값은 클램프 |
onPageChange | (page: number) => void | — | 페이지 변경 알림 — 변경이 있을 때만 호출 |
label | string | '페이지네이션' | nav의 aria-label |
previousLabel | string | '이전 페이지' | 이전 버튼 aria-label |
nextLabel | string | '다음 페이지' | 다음 버튼 aria-label |
variant | 'numeric' | 'dots' | 'numeric' | numeric: 숫자 버튼+줄임 · dots: 점 페이지 컨트롤(소량 페이지·모바일, Apple page control) (ADR-012 B-P1) |
…rest | Omit<HTMLAttributes<nav>, 'aria-label'> | — | className 등 전달 (aria-label은 label prop으로만) |
접근성#
<nav aria-label="페이지네이션">+ul/li— 랜드마크로 점프 가능합니다- 현재 페이지는
aria-current="page", 각 페이지 버튼은aria-label="N 페이지" - 이전/다음은 아이콘 버튼이라
previousLabel/nextLabel이 접근성 이름입니다 - 줄임표(…)는 장식(
aria-hidden) — 낭독되지 않습니다 - 경계 비활성은
disabled속성 — 포커스 순서에서 자연스럽게 제외됩니다
토큰#
component 토큰 없이 semantic을 직접 소비합니다(신설 기준 §4 미충족).
| 속성 | 토큰 |
|---|---|
| 버튼 기본 | color.text-muted · font-size.body-2 + font-weight.medium |
| 버튼 hover | color.surface-hover + color.text |
버튼 눌림(:active) | color.surface-pressed (현재 페이지는 유지) |
| 현재 페이지 | color.primary-subtle + color.primary-text + font-weight.semibold |
| disabled | color.text-placeholder |
| 줄임표 | color.text-placeholder |
| 형태 | space.8(32px 셀) · space.1/2 · radius.md · icon.size-md |
| 모션 | duration.fast + ease.standard |