본문으로 건너뛰기
데이터 / data-table

DataTable

정렬·페이지 슬라이싱·행 클릭을 갖춘 제네릭 데이터 표.
데이터data-tableStatic specimendata table데이터테이블정렬
01 · Specimen

먼저 보고, 그다음 계약을 읽습니다

정렬·페이지 슬라이싱·행 클릭을 갖춘 제네릭 데이터 표.

Workbench 불러오는 중…
Component intent의도와 경계 읽기

columns 정의(key·header·accessor·render·sortable·align·width)와 rows 배열로 구동되는 SSOT 표. 정렬은 controlled(sortBy+sortDir+onSort)와 uncontrolled(defaultSortBy) 두 모드를 지원하고, accessor 가 있으면 그 값으로, 없으면 row[key] 로 비교한다(숫자·Date·한국어 로케일 자동 판별). page+pageSize 를 주면 자체적으로 slice 만 하며, 페이지 이동 UI 는 Pagination 컴포넌트가 별도로 담당한다. loading 시 skeleton 행, 빈 rows 시 emptyState 를 렌더한다.

02 · Use

예제

SSOT에 등록된 실제 API 기준 snippet입니다. standalone 배지만 독립 실행 단위이며, fragment는 주변 state·handler 문맥을 생략합니다.

01uncontrolled 정렬 + 커스텀 셀tsxfragment
uncontrolled 정렬 + 커스텀 셀
import { DataTable } from "@axe/ui";import type { DataTableColumn } from "@axe/ui";type Deal = { name: string; stage: string; amount: number };const columns: DataTableColumn<Deal>[] = [  { key: "name", header: "딜", sortable: true },  { key: "stage", header: "단계" },  {    key: "amount",    header: "투자액",    sortable: true,    align: "right",    render: (r) => `${r.amount.toLocaleString()}원`,  },];<DataTable  columns={columns}  rows={deals}  getRowKey={(r) => r.name}  defaultSortBy="amount"  defaultSortDir="desc"  onRowClick={(r) => openDeal(r)}/>
02controlled 정렬 + Pagination 짝tsxfragment
controlled 정렬 + Pagination 짝
import { DataTable, Pagination } from "@axe/ui";const [sortBy, setSortBy] = useState("amount");const [sortDir, setSortDir] = useState<"asc" | "desc">("desc");const [page, setPage] = useState(1);const pageSize = 10;<>  <DataTable    columns={columns}    rows={rows}    sortBy={sortBy}    sortDir={sortDir}    onSort={(key, dir) => { setSortBy(key); setSortDir(dir); }}    page={page}    pageSize={pageSize}    stickyHeader    loading={isLoading}  />  <Pagination    page={page}    pageSize={pageSize}    total={rows.length}    onPageChange={setPage}  /></>
03CSS-only 정적 표 (비-React)htmlfragment
CSS-only 정적 표 (비-React)
<div class="axe-data-table">  <div class="axe-data-table__scroll">    <table class="axe-data-table__table">      <thead class="axe-data-table__thead">        <tr>          <th class="axe-data-table__th" scope="col">딜</th>          <th class="axe-data-table__th axe-data-table__th--right" scope="col">투자액</th>        </tr>      </thead>      <tbody>        <tr class="axe-data-table__tr">          <td class="axe-data-table__td">AXE 포트A</td>          <td class="axe-data-table__td axe-data-table__td--right">3,000,000원</td>        </tr>      </tbody>    </table>  </div></div>
03 · React

Props

TSX 소스가 진실입니다. 주요 export 컴포넌트의 public API만 노출합니다.

이름타입필수기본값설명
columnsDataTableColumn<Row>[]필수열 정의 배열. 각 항목: key(정렬/aria 식별자) · header(ReactNode) · accessor?(정렬값 추출, 미지정 시 row[key]) · render?(셀 커스텀, 미지정 시 row[key]) · sortable? · align?("left"|"right"|"center") · width?(CSS 폭).
rowsRow[]필수표시할 데이터 행. Row 는 제네릭 — 어떤 객체든 가능.
getRowKey(row: Row, index: number) => stringReact key 로만 사용(onRowClick 은 row 객체를 직접 받으므로 무관). 미지정 시 배열 index 사용.
sortBystringcontrolled 정렬 — 현재 정렬 열의 key. onSort 와 함께 줘야 controlled 로 인식.
sortDir"asc" | "desc"controlled 정렬 방향. controlled 인데 미지정이면 "asc" 로 취급.
onSort(key: string, dir: SortDir) => void정렬 헤더 클릭 콜백. sortBy 와 함께 주면 controlled. sortBy 만 주고 이걸 안 주면 controlled 로 인식되지 않아 dev 경고와 함께 sortBy 값이 무시되고, 헤더 클릭 정렬은 내부 state 로 폴백해 계속 동작한다.
defaultSortBystringuncontrolled 초기 정렬 열. 이후 정렬 state 는 컴포넌트가 보유.
defaultSortDir"asc" | "desc""asc"uncontrolled 초기 정렬 방향.
pagenumber1-based 현재 페이지. pageSize 와 함께 있어야 slice 발생.
pageSizenumber페이지당 행 수. page 와 함께 주면 (page-1)*pageSize 부터 slice. loading skeleton 행 수의 기본값이기도 함(미지정 시 5).
onPageChange(page: number) => void시그니처 대칭을 위해 노출되나 컴포넌트가 내부에서 호출하지 않는다 — 페이지 전환은 짝을 이루는 Pagination 이 발생시키고, 그 값을 부모가 page 로 되돌려 준다.
onRowClick(row: Row) => void행 클릭 콜백. 지정 시 행에 `--clickable` 클래스와 클릭 핸들러가 붙는다.
rowClickablebooleanfalseonRowClick 이 있을 때 행 전체를 role=button+tabIndex 위젯으로 만들지. 기본 false(table 시맨틱 보존) — 셀 안에 `<a>`/`<button>` 등 interactive 가 있으면 nested interactive 충돌을 피하려 false 로 두고, 그 경우 마우스 클릭은 되지만 키보드/role 위임은 셀 내부 컨트롤이 책임진다.
loadingbooleantrue 면 tbody 자리에 skeleton 행(pageSize 또는 5개)을 렌더.
emptyStateReact.ReactNoderows 가 비었을 때 placeholder. 미지정 시 muted "데이터 없음".
stickyHeaderbooleanthead 를 sticky 로 고정. `.axe-data-table__scroll` 에 max-height(480px)가 걸려 세로 스크롤이 생길 때 유효.
...restOmit<React.HTMLAttributes<HTMLDivElement>, "onClick">className 병합 후 나머지는 루트 div 로 전파. onClick 은 행 단위 onRowClick 과 혼동을 막으려 제외됨. forwardRef 는 루트 div 를 가리킨다.
04 · Any stack

.axe-* 클래스 계약

React 밖에서도 같은 표면을 그리는 공개 계약입니다. stable은 minor 버전 안에서 이름을 바꾸지 않습니다.

클래스안정성용도
.axe-data-tablestable루트 div — elevated 배경·border·radius-lg·overflow hidden(radius clip).
.axe-data-table--stickystablestickyHeader 모디파이어 — scroll 영역 max-height + thead sticky 활성.
.axe-data-table__scrollstable가로(및 sticky 시 세로) 스크롤 컨테이너.
.axe-data-table__tablestable`<table>` — width 100%·min-width 600px·border-collapse.
.axe-data-table__theadstablethead — subtle 배경.
.axe-data-table__thstable헤더 셀 — xs·uppercase·secondary. `--right/--center` 로 정렬.
.axe-data-table__th--sortablestable정렬 가능 헤더 — padding 을 0 으로 두고 내부 sort-btn 이 padding 을 갖는다.
.axe-data-table__sort-btnstable정렬 헤더의 실제 `<button>` — 전폭 flex·hover/focus 시 강조.
.axe-data-table__tdstable본문 셀 — tabular-nums·middle 정렬. `--right/--center` 로 정렬.
.axe-data-table__trstable본문 행 — 하단 border·hover 시 accent 4% 배경.
.axe-data-table__tr--clickablestableonRowClick 이 있을 때 — cursor pointer·focus-visible outline.
.axe-data-table__emptystable빈 상태 셀 — 중앙 muted 텍스트(colSpan 전폭).
.axe-data-table__th--activeinternal현재 정렬 중인 헤더 — 컴포넌트 정렬 state 로만 토글되는 내부 훅.
.axe-data-table__sort-arrowinternal정렬 방향 화살표(∧/∨/↕) — aria-hidden 장식.
.axe-data-table__tr--emptyinternal빈 상태 행 마커 — hover 무효화.
.axe-data-table__tr--skeletoninternalloading skeleton 행.
.axe-data-table__skelinternalshimmer 플레이스홀더 바(indicators.css 의 axe-shimmer 공유).
비-React 소비 노트
정적 표 골격은 `.axe-data-table*` 클래스로 서버 렌더에서 재현 가능하나, 정렬·페이지 슬라이싱·skeleton·aria-sort 토글은 React 컴포넌트의 state 로직이 필요하다. CSS-only 로 갈 때는 aria-sort 와 정렬 화살표를 직접 세팅해야 한다.
05 · Inclusive

접근성

키보드, ARIA, 구현 노트를 함께 검토합니다.

Keyboard
  • 정렬 헤더는 네이티브 `<button>` — Tab 포커스 후 Enter/Space 로 정렬 토글(asc↔desc)
  • rowClickable=true 인 행은 Enter/Space 로 onRowClick 실행(preventDefault 로 스크롤 방지)
ARIA

정렬 가능 열의 `<th>` 는 aria-sort=ascending|descending|none, scope="col". 정렬 화살표는 aria-hidden. rowClickable=true 인 행만 role="button"+tabIndex=0 를 받고, 기본(false)은 table 시맨틱을 보존한다.

Notes

dev 모드에서 sortBy 만 주고 onSort 를 빠뜨리면 console.warn 으로 controlled 오설정을 경고한다. 셀 내부에 링크/버튼이 있으면 rowClickable 을 false 로 둬 nested interactive 충돌을 피한다.

06 · Judgment

권장 · 지양

권장
  • 숫자 열은 align="right" 로 tabular-nums 정렬 효과를 살린다.
  • 정렬을 서버/URL 과 동기화해야 하면 controlled(sortBy+sortDir+onSort)로, 화면 내 정렬이면 defaultSortBy 로.
  • 페이지 이동 UI 는 Pagination 을 짝으로 두고 page state 만 부모가 소유한다.
지양
  • sortBy 를 주면서 onSort 를 빠뜨리지 말 것 — controlled 로 인식되지 않아 sortBy 값이 무시되고(내부 state 로 폴백) dev 경고가 뜬다.
  • 셀에 링크/버튼을 넣으면서 rowClickable 을 true 로 두지 말 것(중첩 위젯 충돌).
  • onPageChange 로 slice 를 기대하지 말 것 — slice 는 page+pageSize 만으로 일어난다.
검색창을 열면 컴포넌트 인덱스를 불러옵니다.