Published on

페이지네이션 가독성 리디자인 회고 — 파란색 테마 위의 활성 상태 표현

Authors
  • avatar
    Name
    Hyo814
    Twitter

페이지네이션 가독성 리디자인 회고

게시판 페이지네이션을 Bootstrap 기본 스타일로 붙여뒀더니, 실제 화면에서 활성 페이지 번호가 전혀 눈에 띄지 않았습니다. 원인을 파보니 사이트 테마와의 충돌이었고, 결국 전면 리디자인으로 풀었습니다. 그 과정을 정리합니다.


1. 증상 — 현재 페이지가 어디인지 모르겠다

Bootstrap의 기본 페이지네이션은 활성 상태에 background-color: var(--bs-primary) 를 씁니다. 기본값은 파란색 계열이죠.

문제는 이 서비스의 브랜드 컬러 자체가 파란색이었다는 점입니다. 헤더, 링크, 주요 버튼이 전부 파란색이라 활성 페이지 번호도 파란 배경에 흰 숫자로 나오는데, 주변의 비활성 페이지 번호 역시 파란 글자라 색상 대비가 거의 사라졌습니다.

<!-- 기본 Bootstrap -->
<ul class="pagination">
  <li class="page-item"><a class="page-link">1</a></li>
  <li class="page-item active"><a class="page-link">2</a></li>
  <li class="page-item"><a class="page-link">3</a></li>
</ul>

사용자 피드백:

"지금 몇 페이지에 있는지 한눈에 안 들어와요."

"이전/다음 버튼도 그냥 텍스트라 클릭 가능한지 헷갈려요."


2. 원인 분석

색만 바꾸는 걸로는 안 되겠다 싶어 문제를 세 갈래로 쪼갰습니다.

2.1 색상 충돌

활성 페이지의 파란색이 사이트의 어떤 파란색인지 구별이 안 됐습니다. 헤더도 파랑, 링크도 파랑, 활성 페이지도 파랑.

2.2 모양 차이 없음

활성/비활성이 똑같은 직사각형에 색만 반전된 형태입니다. 시각적 hierarchy가 색 하나에만 매달려 있었습니다.

2.3 "이전/다음"의 클릭 가능성 애매

< > 문자만 있어 버튼이라기보다 구분자처럼 보였습니다. 비활성 상태(첫/마지막 페이지)에서는 회색 텍스트라 더 그랬습니다.


3. 리디자인 방향

색상 하나가 아니라 형태·그림자·아이콘 세 축을 동시에 써서 hierarchy를 만들기로 했습니다.

요소비활성호버활성
배경흰색연한 회색진한 파란색
테두리회색 1px진한 회색 1px없음
그림자없음얕은 그림자진한 그림자
글자진한 회색검정흰색
모양평면약간 떠보임카드처럼 떠있음

"카드형"이라 부른 이유는 활성 페이지가 약간 떠올라 보이는 입체감을 줬기 때문입니다. 그림자가 있으면 색만 볼 때보다 "이게 지금 위치"라는 게 훨씬 빠르게 인지됩니다.


4. 구현

Bootstrap 기본 클래스를 오버라이드하는 대신 별도 클래스를 만들어 섞었습니다. 유지보수할 때 "내가 건드린 것"과 "Bootstrap 기본"을 구분하려는 의도였습니다.

/* static/css/pagination.css */
.pagination--card .page-link {
  min-width: 40px;
  height: 40px;
  display: flex;
  align-items: center;
  justify-content: center;
  border: 1px solid #d9d9d9;
  border-radius: 8px;
  color: #333;
  background: #fff;
  margin: 0 4px;
  transition: all 0.15s ease;
}

.pagination--card .page-link:hover {
  background: #f5f5f5;
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08);
  color: #000;
}

.pagination--card .page-item.active .page-link {
  background: #2a4dd0;       /* 사이트 파랑보다 한 톤 진하게 */
  border-color: transparent;
  color: #fff;
  box-shadow: 0 4px 12px rgba(42, 77, 208, 0.35);
  transform: translateY(-1px);
}

.pagination--card .page-item.disabled .page-link {
  background: #f7f7f7;
  color: #bbb;
}

포인트:

  • 활성 색은 브랜드 파랑보다 한 톤 진하게 잡았습니다. 완전히 다른 색으로 바꾸면 브랜드 일관성이 깨지고, 똑같이 쓰면 본래 문제로 되돌아갑니다.
  • transform: translateY(-1px) — 1px만 띄워도 "지금 여기"가 확 살아납니다. 과하게 띄우면 버튼이 잘려 보이니 조심해야 합니다.
  • min-width — 숫자 페이지와 아이콘 페이지(<, >)의 너비를 통일해 라인이 흔들리지 않게 했습니다.

5. 이전/다음 버튼 — 텍스트에서 아이콘으로

<, > 문자는 폰트마다 다르게 그려지고 안티에일리어싱이 어중간할 때 삐뚤어 보입니다. 아이콘으로 교체했습니다.

<li class="page-item {% if not page.has_previous %}disabled{% endif %}">
  <a class="page-link" href="?page={{ page.previous_page_number }}" aria-label="이전">
    <i class="bi bi-chevron-left"></i>
  </a>
</li>
...
<li class="page-item {% if not page.has_next %}disabled{% endif %}">
  <a class="page-link" href="?page={{ page.next_page_number }}" aria-label="다음">
    <i class="bi bi-chevron-right"></i>
  </a>
</li>
  • aria-label — 아이콘만 있으면 스크린 리더가 읽을 게 없습니다. "이전", "다음"을 명시합니다.
  • <&lt; 로 직접 쓸 때의 위험: 템플릿 엔진에 따라 이스케이프 동작이 달라집니다. 아이콘 쪽이 안전합니다.

6. 페이지 범위 표시 — "1 ... 4 5 6 ... 20"

글이 많아지면 페이지 번호가 화면을 꽉 채워서, 현재 페이지 기준 앞뒤 2개씩만 보여주고 나머지는 ... 으로 축약했습니다.

# board/templatetags/pagination_extras.py
from django import template

register = template.Library()


@register.simple_tag
def pagination_range(page_obj, window=2):
    current = page_obj.number
    total = page_obj.paginator.num_pages
    start = max(1, current - window)
    end = min(total, current + window)
    pages = list(range(start, end + 1))
    if start > 1:
        pages = [1, '...'] + pages
    if end < total:
        pages = pages + ['...', total]
    return pages

템플릿에서는 '...' 이면 클릭 불가한 span으로, 숫자면 링크로 렌더링합니다. 이 패턴은 어느 프레임워크에 갖다 붙여도 동일하게 돌아갑니다.


7. 회고 — "기능은 되는데 안 보이는" 것도 버그다

처음에는 이걸 "사소한 CSS 이슈"로 미뤄뒀습니다. 클릭하면 페이지가 넘어가니까요. 그런데 실제로 사용자 테스트를 해 보니 "지금 몇 페이지인지 모르겠다"는 피드백이 가장 먼저 나왔습니다. 기능이 완성이어도 현재 상태를 인지할 수 없으면 그건 실패입니다.

작업하며 챙긴 메모:

  • 색 하나로 상태를 표현하면 테마와 충돌하기 쉽습니다. 형태·그림자·아이콘을 보조 축으로 가져가면 훨씬 안전합니다.
  • Bootstrap 기본 스타일을 오버라이드할 때는 별도 클래스로. .page-item.active 를 직접 덮어쓰지 말고 .pagination--card .page-item.active 식으로 스코프를 좁히는 편이 유지보수에 유리합니다.
  • aria-label은 기능 구현의 일부입니다. 아이콘을 넣는 순간 세트로 들어가야 합니다.

기능이 완성된 다음에 오히려 "지금 여기가 어디인지 보여주는" 작은 UI가 체감 품질을 크게 끌어올린다는 게 이번 작업의 수확이었습니다.