표 Table
표는 데이터를 하나 이상의 행과 열로 조직화하여 표현하는 형식으로 사용자가 빠르게 많은 양의 정보를 확인하고 비교할 수 있도록 도와준다. 기본적으로 대화형 요소가 아니기 때문에 열 제목에 데이터를 정렬하기 위한 컨트롤 요소가 포함된 상황 외에 행 전체나 데이터 셀이 대화형으로 작동하지 않는다.
기본 테이블
columns로 열을, rows로 행 데이터를 정의합니다. 열의 field는 행의 속성 이름이나 행에서 값을 만드는 함수입니다. 각 행의 첫 번째 셀은 행 제목(th scope="row")으로 렌더되고, headerStyle의 width는 colgroup의 열 너비로도 쓰입니다. KRDS 표 스타일은 <table>에 data 클래스가 있을 때 적용되므로 원본 KRDS 마크업처럼 tableClass="col data"를 지정합니다. caption은 화면에 보이지 않고 화면낭독기로 읽히므로 표의 구성을 설명하는 문장으로 씁니다.
import { Component } from '@angular/core'
import { KrdsTable, type KrdsTableColumn } from '@krds.ui/angular'
interface Row {
title: string
content: string
}
@Component({
selector: 'demo-table-basic',
imports: [KrdsTable],
template: `
<krds-table
caption="000에 대한 표로 제목1,제목2에 대한 내용으로 구성되어 있으며 제목1은 제목1-1,제목1-2,제목1-3으로 구성되어있다."
tableClass="col data"
[columns]="columns"
[rows]="rows"
/>
`
})
export default class TableBasic {
readonly columns: KrdsTableColumn<Row>[] = [
{ name: 'title', label: '제목1', field: 'title', headerStyle: 'width: 30%' },
{ name: 'content', label: '제목2', field: 'content' }
]
readonly rows: Row[] = [
{
title: '제목1-1',
content:
'내용이 들어갑니다. 내용이 들어갑니다. 내용이 들어갑니다. 내용이 들어갑니다. 내용이 들어갑니다. 내용이 들어갑니다. 내용이 들어갑니다. 내용이 들어갑니다. 내용이 들어갑니다. 내용이 들어갑니다. 내용이 들어갑니다. 내용이 들어갑니다. 내용이 들어갑니다.'
},
{ title: '제목1-2', content: '내용이 들어갑니다.' },
{ title: '제목1-3', content: '내용이 들어갑니다. 내용이 들어갑니다. 내용이 들어갑니다. 내용이 들어갑니다.' }
]
}행 클릭
clickable을 줄 때만 행이 초점을 받고(tabindex="0") 클릭이나 Enter·Space 키로 선택되며, (rowClick)으로 행과 순서({ row, index })를 받습니다. 셀 안의 링크·버튼·폼 요소(label 포함)를 누른 것은 행 선택으로 보지 않습니다. clickable이 없으면 행은 대화형으로 동작하지 않습니다.
import { Component, signal } from '@angular/core'
import { KrdsTable, type KrdsTableColumn, type KrdsTableRowClick } from '@krds.ui/angular'
interface Row {
name: string
score: number
}
@Component({
selector: 'demo-table-row-click',
imports: [KrdsTable],
template: `
<krds-table
caption="행을 클릭하거나 Enter·Space로 선택하는 테이블"
tableClass="col data"
[columns]="columns"
[rows]="rows"
clickable
(rowClick)="onRowClick($event)"
/>
<p style="margin-top: 1.6rem">선택된 행: {{ selected() }}</p>
`
})
export default class TableRowClick {
readonly columns: KrdsTableColumn<Row>[] = [
{ name: 'name', label: '이름', field: 'name' },
{ name: 'score', label: '점수', field: 'score' }
]
readonly rows: Row[] = [
{ name: '김철수', score: 95 },
{ name: '이영희', score: 78 },
{ name: '박민수', score: 88 }
]
readonly selected = signal('없음')
onRowClick({ row, index }: KrdsTableRowClick<Row>) {
this.selected.set(`${row.name} (${index})`)
}
}스타일 & 클래스 기능
열마다 헤더 셀에는 headerStyle·headerClasses를, 바디 셀에는 style·classes를 지정합니다. 바디 셀의 style·classes는 행 데이터를 받는 함수로도 줄 수 있습니다. align을 지정하면 헤더와 바디 셀에 text-<align> 클래스가 붙습니다. 클래스의 스타일은 앱의 CSS에서 정의합니다.
import { Component } from '@angular/core'
import { KrdsTable, type KrdsTableColumn } from '@krds.ui/angular'
interface Row {
id: number
name: string
score: number
status: 'PASS' | 'FAIL'
}
@Component({
selector: 'demo-table-style-and-class',
imports: [KrdsTable],
template: ` <krds-table caption="헤더와 바디에 스타일/클래스가 적용된 테이블" tableClass="col data" [columns]="columns" [rows]="rows" /> `
})
export default class TableStyleAndClass {
readonly columns: KrdsTableColumn<Row>[] = [
{
name: 'id',
label: 'ID',
field: 'id',
headerStyle: 'width: 80px',
headerClasses: 'bg-gray-100 text-center font-bold',
style: 'width: 80px',
classes: 'text-center',
align: 'center'
},
{
name: 'name',
label: '이름',
field: 'name',
headerStyle: 'width: 150px; color: #333',
classes: 'font-medium'
},
{
name: 'score',
label: '점수',
field: 'score',
headerStyle: 'width: 100px',
headerClasses: 'text-right',
classes: row => (row.score >= 80 ? 'score-high text-right' : 'score-low text-right'),
align: 'right'
},
{
name: 'status',
label: '상태',
field: 'status',
headerClasses: 'text-center',
classes: row => `status-${row.status.toLowerCase()} text-center`,
align: 'center'
}
]
readonly rows: Row[] = [
{ id: 1, name: '김철수', score: 95, status: 'PASS' },
{ id: 2, name: '이영희', score: 78, status: 'FAIL' },
{ id: 3, name: '박민수', score: 88, status: 'PASS' },
{ id: 4, name: '정수진', score: 92, status: 'PASS' }
]
}셀 템플릿
배지·버튼·체크박스처럼 글자가 아닌 내용은 열 이름(name)을 준 템플릿으로 그립니다. <ng-template krdsCell="열 이름">은 본문 셀 내용을 바꾸고 let-row(행), let-value="value"(field로 꺼낸 값), let-column="column", let-index="index"를 받습니다. [krdsCellOf]="rows"를 함께 주면 row가 행 타입이 되어 row.title 같은 접근을 템플릿 타입 검사가 확인합니다. <ng-template krdsHeader="열 이름">은 헤더 셀 내용을 바꿔 화면에 숨긴 열 제목 등에 씁니다. 셀 태그(첫 열은 th scope="row")와 style·classes는 그대로 유지되고, 템플릿으로만 그리는 열은 field를 생략합니다. 버튼처럼 같은 이름이 행마다 반복되면 숨김 텍스트로 어느 행의 버튼인지 알려 줍니다.
템플릿 안의 row는 object, value는 unknown 타입이므로 행의 속성을 쓰려면 예제의 titleOf처럼 행 타입으로 바꿔 씁니다.
import { Component } from '@angular/core'
import {
KrdsBadge,
KrdsButton,
KrdsTable,
KrdsTableCell,
KrdsTableHeader,
type KrdsBadgeColor,
type KrdsTableColumn
} from '@krds.ui/angular'
interface Row {
id: number
title: string
status: string
date: string
}
@Component({
selector: 'demo-table-cell-template',
imports: [KrdsTable, KrdsTableCell, KrdsTableHeader, KrdsBadge, KrdsButton],
template: `
<krds-table caption="민원 신청 내역: 민원명, 처리 상태, 신청일, 관리로 구성" tableClass="col data" [columns]="columns" [rows]="rows">
<ng-template krdsCell="status" [krdsCellOf]="rows" let-row>
<span krdsBadge type="bg-light" [color]="badgeColor[row.status] ?? 'gray'">{{ row.status }}</span>
</ng-template>
<ng-template krdsHeader="action"><span class="sr-only">관리</span></ng-template>
<ng-template krdsCell="action" [krdsCellOf]="rows" let-row>
<button krdsButton text size="small">
상세 보기<span class="sr-only">: {{ row.title }}</span>
</button>
</ng-template>
</krds-table>
`
})
export default class TableCellTemplate {
readonly columns: KrdsTableColumn<Row>[] = [
{ name: 'title', label: '민원명', field: 'title', headerStyle: 'width: 40%' },
{ name: 'status', label: '처리 상태', field: 'status' },
{ name: 'date', label: '신청일', field: 'date' },
// 템플릿으로만 그리는 열은 field를 생략한다
{ name: 'action', label: '관리' }
]
readonly rows: Row[] = [
{ id: 1, title: '주민등록표 등본 발급', status: '완료', date: '2026.10.02' },
{ id: 2, title: '전입 신고', status: '처리 중', date: '2026.10.05' },
{ id: 3, title: '여권 재발급', status: '반려', date: '2026.10.07' }
]
readonly badgeColor: Record<string, KrdsBadgeColor> = { 완료: 'success', '처리 중': 'information', 반려: 'danger' }
}빈 데이터 (기본)
rows가 비어 있으면 모든 열을 합친 셀에 "데이터가 없습니다."를 표시합니다.
import { Component } from '@angular/core'
import { KrdsTable, type KrdsTableColumn } from '@krds.ui/angular'
interface Row {
id: number
name: string
email: string
}
@Component({
selector: 'demo-table-no-data',
imports: [KrdsTable],
template: ` <krds-table caption="데이터가 없는 테이블" tableClass="col data" [columns]="columns" [rows]="[]" /> `
})
export default class TableNoData {
readonly columns: KrdsTableColumn<Row>[] = [
{ name: 'id', label: 'ID', field: 'id' },
{ name: 'name', label: '이름', field: 'name' },
{ name: 'email', label: '이메일', field: 'email' }
]
}빈 데이터 (커스텀 템플릿)
<ng-template krdsNoData>로 빈 상태 내용을 바꿉니다.
import { Component } from '@angular/core'
import { KrdsTable, KrdsTableNoData, type KrdsTableColumn } from '@krds.ui/angular'
interface Row {
id: number
name: string
email: string
}
@Component({
selector: 'demo-table-no-data-template',
imports: [KrdsTable, KrdsTableNoData],
template: `
<krds-table caption="빈 목록 템플릿이 있는 테이블" tableClass="col data" [columns]="columns" [rows]="[]">
<ng-template krdsNoData>
<div style="padding: 2rem; text-align: center; color: #666">
<div style="font-size: 1.2rem; margin-bottom: 0.5rem">데이터를 찾을 수 없습니다</div>
<div style="font-size: 0.9rem">새로운 데이터를 추가해보세요</div>
</div>
</ng-template>
</krds-table>
`
})
export default class TableNoDataTemplate {
readonly columns: KrdsTableColumn<Row>[] = [
{ name: 'id', label: 'ID', field: 'id' },
{ name: 'name', label: '이름', field: 'name' },
{ name: 'email', label: '이메일', field: 'email' }
]
}KrdsTable
선택자 krds-table
Inputs
| 이름 | 타입 | 기본값 | 설명 |
|---|---|---|---|
caption | string | - | 표 제목 (caption) |
columns (필수) | KrdsTableColumn<T>[] | - | 열 |
rows (필수) | T[] | - | 행 |
tableClass | string | - | table 요소에 더 붙일 KRDS 클래스 (예: 'col data') |
clickable | boolean | false | 행을 클릭·Enter·Space로 고를 수 있게 함 |
Outputs
| 이름 | 값 | 설명 |
|---|---|---|
rowClick | KrdsTableRowClick<T> | 행을 골랐을 때 (clickable일 때) |
타입
/** 표의 열 */
export interface KrdsTableColumn<T = object> {
/** 열 이름 (셀·머리글 템플릿을 고를 때 씀) */
name: string
/** 머리글 */
label: string
/** 셀 값: 행의 속성 이름 또는 행에서 값을 만드는 함수 */
field?: (keyof T & string) | ((row: T) => unknown)
/** 정렬 */
align?: 'left' | 'center' | 'right'
/** 셀 스타일 */
style?: string | ((row: T) => string)
/** 셀 클래스 */
classes?: string | ((row: T) => string)
/** 머리글 스타일 (width가 있으면 colgroup 너비로도 씀) */
headerStyle?: string
/** 머리글 클래스 */
headerClasses?: string
}
/** 행 클릭 이벤트 */
export interface KrdsTableRowClick<T = object> {
row: T
index: number
}
KrdsTableCell
선택자 ng-template[krdsCell]
Inputs
| 이름 | 타입 | 기본값 | 설명 |
|---|---|---|---|
krdsCell (필수) | string | - | |
krdsCellOf | readonly T[] | - | 행 목록 (템플릿의 row 타입을 정하는 데만 씀) |
템플릿에서 받는 변수입니다.
| 템플릿 변수 | 타입 | 설명 |
|---|---|---|
let-row | 행 타입 | 행 ([krdsCellOf]로 정한 행 타입, 없으면 object) |
let-value="value" | unknown | field로 꺼낸 셀 값 |
let-column="column" | KrdsTableColumn | 열 |
let-index="index" | number | 행 순서 (0부터) |
KrdsTableHeader
선택자 ng-template[krdsHeader]
Inputs
| 이름 | 타입 | 기본값 | 설명 |
|---|---|---|---|
krdsHeader (필수) | string | - |
let-column으로 열(KrdsTableColumn)을 받습니다.
KrdsTableNoData
선택자 ng-template[krdsNoData]
Inputs
없음