테이블·데이터 그리드 UX: 복잡한 데이터를 탐색하는 표 설계

UX 디자인

데이터 테이블그리드 UX관리자 UI데이터 시각화테이블 컴포넌트

이 글은 누구를 위한 것인가

  • 관리자 화면의 데이터 테이블이 복잡해서 사용자가 못 찾는 팀
  • 10만 행 데이터를 테이블에 표시하려고 고민하는 개발자
  • 모바일에서 테이블을 어떻게 보여줄지 모르는 팀

들어가며

데이터 테이블은 B2B·관리자 화면의 핵심 컴포넌트다. 정렬·필터·선택·일괄 처리가 자연스럽게 동작해야 한다. 10만 행 이상은 가상화(Virtualization) 없이는 브라우저가 멈춘다.

이 글은 bluefoxdev.kr의 데이터 테이블 UX 가이드 를 참고하여 작성했습니다.


1. 데이터 테이블 UX 패턴

[테이블 기능 우선순위]

필수:
  정렬 (클릭으로 오름/내림차순 토글)
  페이지네이션 (행 수 선택 가능)
  컬럼 헤더 고정 (세로 스크롤 시)
  반응형 (모바일 대응)

권장:
  필터 (컬럼별 또는 전체 검색)
  행 선택 + 일괄 처리
  컬럼 너비 조정
  컬럼 순서 변경
  내보내기 (CSV, Excel)

선택:
  인라인 편집 (셀 직접 수정)
  컬럼 숨김/표시 토글
  고정 컬럼 (수평 스크롤 시)
  그룹화 (Row Grouping)
  피벗

[모바일 테이블 전략]
  방법 1: 수평 스크롤 (명확한 스크롤 힌트)
  방법 2: 카드 뷰로 변환 (좁은 화면에서)
  방법 3: 중요 컬럼만 표시 + "더 보기" 확장
  
  권장: 넓이에 따라 테이블 ↔ 카드 자동 전환

[컬럼 정렬 시각 언어]
  정렬 안됨: 비활성 화살표 (↕)
  오름차순: 위 화살표 (↑) 강조
  내림차순: 아래 화살표 (↓) 강조
  정렬 기준 컬럼: 헤더 배경색 살짝 변경

2. 고성능 데이터 테이블

import { useVirtual } from "@tanstack/react-virtual";
import { useRef } from "react";

// TanStack Table + 가상 스크롤
function VirtualDataTable<T>({
  data,
  columns,
  rowHeight = 48,
}: {
  data: T[];
  columns: ColumnDef<T>[];
  rowHeight?: number;
}) {
  const parentRef = useRef<HTMLDivElement>(null);

  const table = useReactTable({
    data,
    columns,
    getCoreRowModel: getCoreRowModel(),
    getSortedRowModel: getSortedRowModel(),
    getFilteredRowModel: getFilteredRowModel(),
  });

  const { rows } = table.getRowModel();

  // 가상 스크롤 (10만 행도 부드럽게)
  const rowVirtualizer = useVirtual({
    count: rows.length,
    getScrollElement: () => parentRef.current,
    estimateSize: () => rowHeight,
    overscan: 10,
  });

  return (
    <div ref={parentRef} className="overflow-auto" style={{ height: "600px" }}>
      <table className="w-full text-sm">
        {/* 고정 헤더 */}
        <thead className="sticky top-0 bg-white z-10 border-b">
          {table.getHeaderGroups().map((headerGroup) => (
            <tr key={headerGroup.id}>
              {headerGroup.headers.map((header) => (
                <th
                  key={header.id}
                  className="px-4 py-3 text-left font-medium text-gray-600 whitespace-nowrap cursor-pointer select-none"
                  onClick={header.column.getToggleSortingHandler()}
                >
                  <div className="flex items-center gap-1">
                    {flexRender(header.column.columnDef.header, header.getContext())}
                    {header.column.getIsSorted() === "asc" ? " ↑" :
                     header.column.getIsSorted() === "desc" ? " ↓" : " ↕"}
                  </div>
                </th>
              ))}
            </tr>
          ))}
        </thead>

        {/* 가상 스크롤 바디 */}
        <tbody
          style={{ height: `${rowVirtualizer.totalSize}px`, position: "relative" }}
        >
          {rowVirtualizer.virtualItems.map((virtualRow) => {
            const row = rows[virtualRow.index];
            return (
              <tr
                key={row.id}
                style={{
                  position: "absolute",
                  top: 0,
                  transform: `translateY(${virtualRow.start}px)`,
                  width: "100%",
                }}
                className="border-b hover:bg-gray-50"
              >
                {row.getVisibleCells().map((cell) => (
                  <td key={cell.id} className="px-4 py-3 whitespace-nowrap">
                    {flexRender(cell.column.columnDef.cell, cell.getContext())}
                  </td>
                ))}
              </tr>
            );
          })}
        </tbody>
      </table>
    </div>
  );
}

마무리

데이터 테이블에서 성능이 문제가 된다면 먼저 서버 사이드 페이지네이션을 시도하라. 페이지당 100행으로 제한하면 가상화 없이도 부드럽게 동작한다. 가상 스크롤은 "전체 데이터를 한 화면에서 스크롤해야 하는" 경우에만 필요하다.