Table

대량의 데이터를 행과 열의 그리드 형식으로 정리하여 보여주는 요소입니다. 구조화된 정보를 통해 사용자가 데이터 간의 관계를 쉽게 비교하고 패턴을 파악할 수 있도록 돕습니다. 화면 너비를 확보할 수 있는 데스크톱 환경에서만 사용합니다.

Basic table

대량의 데이터를 행과 열의 그리드 형식으로 정리하여 보여줍니다.

제목제목제목
텍스트텍스트텍스트
텍스트텍스트텍스트
텍스트텍스트텍스트
import {
  Table,
  TableRow,
  TableHead,
  TableHeadCell,
  TableCell,
  TableBody,
  Checkbox,
  ContentBadge,
} from '@wanteddev/wds';

const Demo = () => {
  return (
    <Table sx={{ width: '85%' }}>
      <colgroup>
        <col width="36px" />
        <col width="auto" />
        <col width="auto" />
        <col width="100px" />
      </colgroup>

      <TableHead>
        <TableRow>
          <TableHeadCell>
            <Checkbox size="small" />
          </TableHeadCell>
          <TableHeadCell>제목</TableHeadCell>
          <TableHeadCell>제목</TableHeadCell>
          <TableHeadCell>제목</TableHeadCell>
        </TableRow>
      </TableHead>

      <TableBody>
        <TableRow>
          <TableCell>
            <Checkbox size="small" />
          </TableCell>
          <TableCell>텍스트</TableCell>
          <TableCell>텍스트</TableCell>
          <TableCell>
            <ContentBadge>
              텍스트
            </ContentBadge>
          </TableCell>
        </TableRow>
        <TableRow>
          <TableCell>
            <Checkbox size="small" />
          </TableCell>
          <TableCell>텍스트</TableCell>
          <TableCell>텍스트</TableCell>
          <TableCell>
            <ContentBadge>
              텍스트
            </ContentBadge>
          </TableCell>
        </TableRow>
        <TableRow>
          <TableCell>
            <Checkbox size="small" />
          </TableCell>
          <TableCell>텍스트</TableCell>
          <TableCell>텍스트</TableCell>
          <TableCell>
            <ContentBadge>
              텍스트
            </ContentBadge>
          </TableCell>
        </TableRow>
      </TableBody>
    </Table>
  )
}

export default Demo;

Anatomy

기본적으로 아래와 같은 구조로 사용합니다.

<Table>
  <TableHead>
    <TableRow>
      <TableHeadCell />
      <TableHeadCell />
      <TableHeadCell />
    </TableRow>
  </TableHead>

  <TableBody>
    <TableRow>
      <TableCell />
      <TableCell />
      <TableCell />
    </TableRow>
  </TableBody>

  <TableFoot>
    <TableRow>
      <TableCell />
      <TableCell />
      <TableCell />
    </TableRow>
  </TableFoot>
</Table>

Interaction

interaction 옵션을 통해 인터렉션을 줄 수 있습니다.

  • 인터렉션 색상, opacity를 변경하고 싶을 땐 &::after 스타일을 변경합니다.
  • interaction이 true일 때는 tabIndex를 자동으로 지정하여 키보드로 조작할 수 있게 설정합니다.
제목제목제목제목
텍스트텍스트텍스트텍스트
텍스트텍스트텍스트텍스트
텍스트텍스트텍스트텍스트
import {
  Table,
  TableRow,
  TableHead,
  TableHeadCell,
  TableCell,
  TableBody,
} from '@wanteddev/wds';

const Demo = () => {
  return (
    <Table sx={{ width: '85%' }}>
      <TableHead>
        <TableRow>
          <TableHeadCell>제목</TableHeadCell>
          <TableHeadCell>제목</TableHeadCell>
          <TableHeadCell>제목</TableHeadCell>
          <TableHeadCell>제목</TableHeadCell>
        </TableRow>
      </TableHead>

      <TableBody>
        <TableRow interaction>
          <TableCell>텍스트</TableCell>
          <TableCell>텍스트</TableCell>
          <TableCell>텍스트</TableCell>
          <TableCell>텍스트</TableCell>
        </TableRow>
        <TableRow interaction>
          <TableCell>텍스트</TableCell>
          <TableCell>텍스트</TableCell>
          <TableCell>텍스트</TableCell>
          <TableCell>텍스트</TableCell>
        </TableRow>
        <TableRow interaction>
          <TableCell>텍스트</TableCell>
          <TableCell>텍스트</TableCell>
          <TableCell>텍스트</TableCell>
          <TableCell>텍스트</TableCell>
        </TableRow>
      </TableBody>
    </Table>
  )
}

export default Demo;

With pagination

Pagination 컴포넌트와 함께 사용할 수 있습니다.

제목제목제목제목
텍스트텍스트텍스트텍스트
텍스트텍스트텍스트텍스트
텍스트텍스트텍스트텍스트
텍스트텍스트텍스트텍스트
텍스트텍스트텍스트텍스트
import {
  Table,
  TableRow,
  TableHead,
  TableHeadCell,
  TableCell,
  TableBody,
  Pagination
} from '@wanteddev/wds';

const rows = new Array(5).fill(null)

const Demo = () => {
  return (
    <Table
      sx={{ width: '85%' }}
      pagination={(
        <Pagination totalPages={1} defaultPage={1} />
      )}
    >
      <TableHead>
        <TableRow>
          <TableHeadCell>제목</TableHeadCell>
          <TableHeadCell>제목</TableHeadCell>
          <TableHeadCell>제목</TableHeadCell>
          <TableHeadCell>제목</TableHeadCell>
        </TableRow>
      </TableHead>

      <TableBody>
        {rows.map((_, i) => (
          <TableRow key={i}>
            <TableCell>텍스트</TableCell>
            <TableCell>텍스트</TableCell>
            <TableCell>텍스트</TableCell>
            <TableCell>텍스트</TableCell>
          </TableRow>
        ))}
      </TableBody>
    </Table>
  )
}

export default Demo;

Virtual scroll

@tanstack/react-virtual 등을 이용하여 가상 스크롤을 적용할 수 있습니다.

가상 스크롤을 사용할 때 sticky 영역을 보존하기 위해 임의의 영역을 차지하는 TableRow를 지정해야 합니다.

제목제목제목제목
import {
Table,
TableRow,
TableHead,
TableHeadCell,
TableCell,
TableBody
} from '@wanteddev/wds';
import { useVirtualizer } from '@tanstack/react-virtual';
import { useRef } from 'react';

const rows = new Array(5000)
.fill(true)

const Demo = () => {
const ref = useRef<HTMLDivElement>(null);

const rowVirtualizer = useVirtualizer({
  count: rows.length,
  getScrollElement: () => ref.current,
  estimateSize: () => 56,
  overscan: 20,
});

 const virtualRows = rowVirtualizer.getVirtualItems();

const paddingTop = virtualRows.length > 0 ? virtualRows?.[0]?.start || 0 : 0;
const paddingBottom =
  virtualRows.length > 0
    ? rowVirtualizer.getTotalSize() - (virtualRows?.[virtualRows.length - 1]?.end || 0)
    : 0;

return (
  <Table
    sx={{ width: '85%' }}
    sx={{
      height: '400px',
      ['[data-radix-scroll-area-content]']: {
        minHeight: `${rowVirtualizer.getTotalSize()}px`,
      },
    }}
    viewportRef={ref}
  >
    <TableHead>
      <TableRow>
        <TableHeadCell>제목</TableHeadCell>
        <TableHeadCell>제목</TableHeadCell>
        <TableHeadCell>제목</TableHeadCell>
        <TableHeadCell>제목</TableHeadCell>
      </TableRow>
    </TableHead>

    <TableBody>
      {paddingTop > 0 ? (
          <TableRow>
            <TableCell style={{ height: paddingTop }} />
          </TableRow>
        ) : null}
      {rowVirtualizer.getVirtualItems().map((virtualRow, index) => (
        <TableRow key={virtualRow.index}>
          <TableCell>텍스트</TableCell>
          <TableCell>텍스트</TableCell>
          <TableCell>텍스트</TableCell>
          <TableCell>텍스트</TableCell>
        </TableRow>
      ))}
      {paddingBottom > 0 ? (
          <TableRow>
            <TableCell style={{ height: paddingBottom }} />
          </TableRow>
        ) : null}
    </TableBody>
  </Table>
)
}

export default Demo;

API

Table

NameTypesdefaultValue
pagination
ReactNode
-
viewportRef
null | RefObject | (instance: null | HTMLDivElement) => void | () => VoidOrUndefinedOnly
-
children
ReactNode
-
sx
SxProp
-

TableHead

NameTypesdefaultValue
children
ReactNode
-
sx
SxProp
-

TableHeadCell

NameTypesdefaultValue
xl
Merge<Pick<TypographyDefaultProps, "variant" | "weight" | "align">, { sx?: CSSInterpolation; }> | undefined
-
lg
Merge<Pick<TypographyDefaultProps, "variant" | "weight" | "align">, { sx?: CSSInterpolation; }> | undefined
-
md
Merge<Pick<TypographyDefaultProps, "variant" | "weight" | "align">, { sx?: CSSInterpolation; }> | undefined
-
sm
Merge<Pick<TypographyDefaultProps, "variant" | "weight" | "align">, { sx?: CSSInterpolation; }> | undefined
-
xs
Merge<Pick<TypographyDefaultProps, "variant" | "weight" | "align">, { sx?: CSSInterpolation; }> | undefined
-
color
ThemeColorsToken
-
children
ReactNode
-
sx
SxProp
-
variant
"display1" | "display2" | "display3" | "title1" | "title2" | "title3" | "heading1" | "heading2" | "headline1" | "headline2" | "body1" | "body1-reading" | "body2" | "body2-reading" | "label1" | "label1-reading" | "label2" | "caption1" | "caption2"
-
weight
"medium" | "regular" | "bold"
-
align
Property.TextAlign | undefined
-
noWrap
boolean
-
display
Property.Display | undefined
-

TableBody

NameTypesdefaultValue
children
ReactNode
-
sx
SxProp
-

TableRow

NameTypesdefaultValue
interaction
boolean
false
children
ReactNode
-
sx
SxProp
-

TableCell

NameTypesdefaultValue
xl
Merge<Pick<TypographyDefaultProps, "variant" | "weight" | "align">, { sx?: CSSInterpolation; }> | undefined
-
lg
Merge<Pick<TypographyDefaultProps, "variant" | "weight" | "align">, { sx?: CSSInterpolation; }> | undefined
-
md
Merge<Pick<TypographyDefaultProps, "variant" | "weight" | "align">, { sx?: CSSInterpolation; }> | undefined
-
sm
Merge<Pick<TypographyDefaultProps, "variant" | "weight" | "align">, { sx?: CSSInterpolation; }> | undefined
-
xs
Merge<Pick<TypographyDefaultProps, "variant" | "weight" | "align">, { sx?: CSSInterpolation; }> | undefined
-
color
ThemeColorsToken
-
children
ReactNode
-
sx
SxProp
-
variant
"display1" | "display2" | "display3" | "title1" | "title2" | "title3" | "heading1" | "heading2" | "headline1" | "headline2" | "body1" | "body1-reading" | "body2" | "body2-reading" | "label1" | "label1-reading" | "label2" | "caption1" | "caption2"
-
weight
"medium" | "regular" | "bold"
-
align
Property.TextAlign | undefined
-
noWrap
boolean
-
display
Property.Display | undefined
-

TableFoot

NameTypesdefaultValue
children
ReactNode
-
sx
SxProp
-

© 2026 Wanted Lab, Inc.