Pagination | CoUI
LogoCoUI

Pagination

대용량 콘텐츠를 여러 페이지로 나누어 탐색하는 페이지네이션 컴포넌트

Pagination#

대용량 콘텐츠를 페이지 단위로 나누어 탐색할 수 있는 컴포넌트입니다. 현재 페이지 표시, 이전/다음 이동, 첫/마지막 페이지 이동을 지원합니다.

Live Preview#

사용 시기 (When to Use)#

이 컴포넌트를 사용하세요:

  • 테이블, 검색 결과, 상품 목록처럼 많은 데이터를 페이지 단위로 나눠 표시할 때
  • 사용자가 특정 페이지로 직접 이동할 수 있어야 할 때
  • 전체 데이터 양이 많지 않아 페이지 번호로 탐색이 유용한 경우

대신 다른 컴포넌트를 사용하세요:

  • RefreshTrigger: 무한 스크롤 방식으로 데이터를 로드할 때
  • Select: 페이지 크기 선택 등 드롭다운 옵션이 필요할 때

기본 사용법 (Basic Usage)#

Flutter / Web 모두 동일한 Pagination API 를 사용합니다.

// 기본 페이지네이션
Pagination(
  page: _currentPage,
  totalPages: 10,
  onPageChanged: handlePageChanged,
)

// 더 넓은 페이지 범위 + prev/next 라벨
Pagination(
  page: _currentPage,
  totalPages: 20,
  onPageChanged: handlePageChanged,
  maxPages: 7,
  showLabel: true,
)

// 첫 페이지에서 이전 버튼 숨김
Pagination(
  page: _currentPage,
  totalPages: 5,
  onPageChanged: handlePageChanged,
  hidePreviousOnFirstPage: true,
)
// 기본 페이지네이션 (이전/다음 버튼 포함)
Pagination(
  page: currentPage,
  totalPages: 10,
  onPageChanged: handlePageChanged,
)

// 더 넓은 페이지 범위 + prev/next 라벨
Pagination(
  page: currentPage,
  totalPages: 20,
  onPageChanged: handlePageChanged,
  maxPages: 7,
  showLabel: true,
)

Props / Parameters#

속성타입기본값설명
pageint필수현재 페이지 (1-based)
totalPagesint필수전체 페이지 수
onPageChanged void Function(int)? null 페이지 변경 핸들러 (1-based)
maxPages int 3 표시할 최대 페이지 버튼 수
showSkipToFirstPage bool true 선행 생략 시 첫 페이지 이동 버튼 표시
showSkipToLastPage bool true 후행 생략 시 마지막 페이지 이동 버튼 표시
hidePreviousOnFirstPage bool false 첫 페이지에서 이전 버튼 숨김
hideNextOnLastPage bool false 마지막 페이지에서 다음 버튼 숨김
showLabel bool false 이전/다음 버튼에 텍스트 라벨 표시
variant CorePaginationVariant standard 시각 변형
paginationStyle CorePaginationStyle? null 인스턴스 스타일 (Style 시스템 참조)

스타일 시스템 (Style System)#

Pagination 의 모든 chrome / dimensional 오버라이드는 CorePaginationStyle 단일 슬롯으로 흐릅니다. behaviour 필드 (page / totalPages / onPageChanged / maxPages / showSkipToFirstPage / showSkipToLastPage / hidePreviousOnFirstPage / hideNextOnLastPage / showLabel) 는 위젯/컴포넌트 파라미터로 직접 전달합니다.

시맨틱 vs 스타일#

  • 시맨틱 / behaviour: 위젯/컴포넌트 파라미터로 직접 (page, totalPages, onPageChanged, maxPages, showSkipToFirstPage, showSkipToLastPage, hidePreviousOnFirstPage, hideNextOnLastPage, showLabel)
  • chrome / dimensional: CorePaginationStyle 한 곳으로

Resolve chain#

CorePaginationStyle.defaultX                   // design system default
  → CorePaginationTheme.style                  // 프로젝트 공통
  → widget.paginationStyle                     // 인스턴스별

CorePaginationStyle 필드#

필드타입설명
itemSpacing double? Spacing between adjacent pagination buttons (logical px), applied as the host-layout Row.spacing (Flutter) / CSS flex gap (Web) of the outer pagination row.
buttonSize double? Width / height of a square pagination button (logical px).
buttonRadius CoreBorderRadius? Corner radius of a pagination button.
buttonTextStyle CoreTextStyle? Base text style override shared by every page-number / prev / next button (typography role + weight; carries no per-state colour). Layered on top of [defaultButtonTextStyle].
activeBackgroundColor CoreColor? Background colour of the active (current) page button.
activeTextStyle CoreTextStyle? Text style override for the active (current) page button. Text colour is carried via [CoreTextStyle.color] inside this slot (sb8 — raw activeForegroundColor field removed). Defaults to [defaultActiveTextStyle].
hoverBackgroundColor CoreColor? Background colour applied to a button on hover.
hoverTextStyle CoreTextStyle? Text style override applied to a button on hover. Text colour is carried via [CoreTextStyle.color] inside this slot (sb8 — raw hoverForegroundColor field removed). Defaults to [defaultHoverTextStyle].
textStyle CoreTextStyle? Text style override for an inactive / prev / next button. Text colour is carried via [CoreTextStyle.color] inside this slot (sb8 — raw foregroundColor field removed). Defaults to [defaultTextStyle].
hoverDuration Duration? Colour-transition duration for button hover. null → token default ([defaultHoverDuration]).
ellipsisStyle CoreTextStyle? Ellipsis ( ) text style. Deliberately has no default* — absence is what keeps the ellipsis looking like the buttons it sits between. It is an overlay on the already-defaulted per-state button text chain ([defaultButtonTextStyle] plus [defaultTextStyle] / [defaultActiveTextStyle] / [defaultHoverTextStyle]): Flutter builds the ellipsis via baseTextStyle.merge(resolved.ellipsisTextStyle) , so a constant would outrank that base for the alone and make the condensed range read as a different control. Web keys two branches on the null — the inline overlay is written only when the slot is set, and ellipsisColorEmit stays null unless the slot carries a colour, which is what lets the inherit the shared button foreground instead of pinning its own.
clickableStyle CoreClickableStyle? Nested [CoreClickableStyle] slot for the composed per-button Clickable (press scale / durations / focus ring / disabled opacity). Merged on top of [defaultClickableStyle] and raw-forwarded — the Clickable's own resolver fills the rest.

빠른 오버라이드 (Chain)#

이미 만든 Pagination 인스턴스에 스타일을 빠르게 덧붙이고 싶다면 withStyle 체인을 쓸 수 있습니다. 생성자의 style 슬롯 인자와 동일하게 동작하지만, 이미 구성된 위젯 위에서 바로 이어 쓸 수 있습니다.

class PaginationChainExample extends StatefulWidget {
  const PaginationChainExample({super.key});

  @override
  State<PaginationChainExample> createState() => _PaginationChainExampleState();
}

class _PaginationChainExampleState extends State<PaginationChainExample> {
  int _page = 3;

  @override
  Widget build(BuildContext context) {
    return Pagination(
      page: _page,
      totalPages: 10,
      onPageChanged: (p) => setState(() => _page = p),
    ).withStyle(
      const CorePaginationStyle(
        itemSpacing: CoreSpace.space8,
        buttonSize: CoreSize.size40,
        buttonRadius: CoreBorderRadius.all(CoreRadius.radius16),
        activeBackgroundColor: CoreColor.token(CoreColors.tertiary),
      ),
    );
  }
}
class PaginationChainExample extends StatefulComponent {
  const PaginationChainExample({super.key});

  @override
  State<PaginationChainExample> createState() => _PaginationChainExampleState();
}

class _PaginationChainExampleState extends State<PaginationChainExample> {
  int _page = 3;

  @override
  Component build(BuildContext context) {
    return Pagination(
      page: _page,
      totalPages: 10,
      onPageChanged: (value) => setState(() => _page = value),
    ).withStyle(
      const CorePaginationStyle(
        itemSpacing: CoreSpace.space8,
        buttonSize: CoreSize.size40,
        buttonRadius: CoreBorderRadius.all(CoreRadius.radius16),
        activeBackgroundColor: CoreColor.token(CoreColors.tertiary),
      ),
    );
  }
}

변형 (Variants)#

Pagination 은 현재 단일 시각 변형(standard) 을 가집니다. 표시되는 페이지 범위와 prev/next 라벨은 behaviour 파라미터로 조정합니다.

// 더 넓은 페이지 범위 + 라벨
Pagination(
  page: 5,
  totalPages: 20,
  onPageChanged: handlePageChanged,
  maxPages: 7,
  showLabel: true,
)

동작 스펙 (Behavior)#

인터랙션#

  • 페이지 버튼 클릭: 해당 페이지로 이동 (onPageChanged(page) 호출)
  • 이전/다음 버튼: 현재 페이지에서 1씩 감소/증가
  • 첫/마지막 버튼: 1페이지 또는 totalPages로 이동
  • 현재 페이지 버튼: 클릭해도 이벤트 없음 (비활성 강조)
  • 경계 버튼: 첫 페이지에서 이전 버튼, 마지막 페이지에서 다음 버튼은 비활성

상태 전환#

  • 현재 페이지 버튼: 강조 색상 + 비활성 클릭
  • 경계 도달 시 이전/다음 버튼 disabled 처리
  • 페이지 수가 많으면 현재 페이지 기준으로 중간 항목 ...으로 생략

애니메이션#

  • 페이지 전환 시 별도 애니메이션 없음 (콘텐츠 영역 전환은 부모가 처리)
  • 버튼 호버 시 배경색 200ms 전환

사용 가이드라인 (Usage Guidelines)#

✅ Do#

많은 데이터에는 skip-to-first / skip-to-last 버튼 활용

Pagination(
  page: _currentPage,
  totalPages: 50,
  onPageChanged: handlePageChanged,
  showSkipToFirstPage: true,
  showSkipToLastPage: true,
)

50페이지 이상의 데이터에서 첫/마지막 페이지 버튼은 멀리 있는 페이지로의 빠른 이동을 지원한다.


❌ Don't#

페이지가 1개일 때 Pagination 표시

// ❌ 페이지가 1개뿐인데 Pagination 표시
Pagination(
  page: 1,
  totalPages: 1,
  onPageChanged: handlePageChanged,
)

페이지가 1개뿐이면 Pagination이 의미 없고 오히려 혼란을 준다. totalPages > 1인 경우에만 표시한다.

✅ Do#

좁은 화면에서는 maxPages 를 작게 유지

Pagination(
  page: _currentPage,
  totalPages: 20,
  onPageChanged: handlePageChanged,
  maxPages: 3,
)

maxPages 가 크면 버튼이 너무 많아 모바일 화면에서 줄바꿈이 발생한다. 3~5가 적당하다.


❌ Don't#

데이터가 적을 때 불필요하게 페이지네이션을 표시하지 마세요.

// ❌ 한 페이지에 다 들어오는데도 표시
Pagination(
  page: 1,
  totalPages: 1,
  onPageChanged: handlePageChanged,
)

단일 페이지라면 페이지네이션은 불필요한 UI 요소입니다. 총 항목 수가 페이지당 개수보다 적으면 숨기세요.

접근성 (Accessibility)#

키보드 인터랙션#

동작
Tab다음 페이지 버튼으로 포커스
Enter / Space포커스된 페이지 버튼 활성화
Arrow Left/Right이전/다음 페이지 버튼으로 포커스 이동

스크린 리더#

  • Flutter: Semantics(label: '페이지 3, 전체 10페이지') 형태로 현재/전체 페이지 전달
  • Web: role="navigation" + aria-label="페이지 탐색" + 현재 페이지에 aria-current="page" 적용

터치 타겟#

  • 각 페이지 버튼 최소 히트 영역: 24×24 논리 픽셀 (WCAG 2.2 AA, CoreTouchTarget.minimum) — 보이는 크기를 유지한 채 히트 영역만 넓힙니다

크로스 플랫폼 차이점 (Platform Differences)#

항목FlutterWeb
클래스명PaginationPagination (동일)
페이지 기준1-based1-based
버튼div 기반 정사각 버튼<div role="button"> 정사각 버튼
생략 표시 텍스트 (클릭 시 범위 점프) 텍스트 (클릭 시 범위 점프)
호버MouseRegion + 색 전환CSS :hover 토큰 클래스
  • Table: 페이지네이션이 필요한 대용량 테이블 데이터에 사용
  • Select: 페이지 크기 선택 드롭다운과 조합