Components · 내비게이션
Tab (탭)
같은 계층의 콘텐츠 묶음 중 하나를 골라 같은 자리에서 전환한다. 제안서·상세페이지 섹션 전환에 적합하다.
정의
같은 계층의 여러 콘텐츠 묶음 중 하나만 골라 같은 자리에서 전환해 보여 주는 컴포넌트다.
Anatomy
| 부위 | 토큰 |
|---|---|
| ① 탭 목록 | 하단 보더 --tab-border · 탭 사이 간격 --tab-gap |
| ② 비활성 탭 | fg --tab-fg · 패딩 --tab-pad-y/--tab-pad-x · --text-label |
| ③ 활성 탭 | fg --tab-fg-active · 하단 인디케이터 --tab-indicator 두께 --tab-indicator-size |
| ④ 탭 패널 | 본문 --text-body · 상단 여백 --space-6 |
변형
| 변형 | 용도 |
|---|---|
| 수평 탭(기본) | 같은 화면의 3~6개 섹션 전환 — 제안서·상세페이지 섹션 |
size·수직·중첩 변형은 제공하지 않는다 — 콘텐츠가 계층이면 Accordion을 쓴다.
상태
| 상태 | 비활성 탭 | 활성 탭 |
|---|---|---|
| Default | fg --tab-fg | fg --tab-fg-active + 인디케이터 --tab-indicator |
| Hover | fg --tab-fg-active | — |
| Focus | box-shadow: var(--focus-ring) | 동일 |
| Selected | aria-selected="false" | aria-selected="true"(인디케이터 표시) |
| Disabled | opacity: var(--opacity-disabled) + cursor: not-allowed | — |
Do / Don't
Do
- 탭은 3~6개로 유지한다 — 항목이 많으면 묶거나 사이드 메뉴를 쓴다.
- 탭 라벨은 1~2단어 명사로, 순서를 바꿔도 뜻이 통하게 병렬로 짓는다.
- 활성 탭은 색 + 인디케이터(밑줄) 둘 다로 구분한다.
Don't
- 탭 안에 또 탭을 넣지 말 것(중첩 금지) — 계층은 Accordion으로.
- 순서·인과가 있는 흐름에 탭을 쓰지 말 것 — 그건 Step Indicator다.
- 탭 전환에 페이지 새로고침을 걸지 말 것 — 같은 화면에서 콘텐츠만 바꾼다.
콘텐츠 규칙
- 탭 라벨은 1~2단어 명사구(≤10자) — 문장·물음표 금지.
- 라벨은 병렬 구조로 통일한다("규격 / 소재 / 시공사례"처럼 같은 결).
- 탭 개수는 3~6개 권장, 2개면 탭 대신 세그먼트·토글을 검토한다.
키보드 인터랙션
| 키 | 동작 |
|---|---|
Tab | 탭 목록으로 진입 → 다시 Tab이면 활성 패널로 이동(탭 사이 이동은 방향키) |
← / → | 이전/다음 탭으로 이동하며 즉시 선택(자동 활성화) |
Home / End | 첫 번째 / 마지막 탭으로 이동 |
Enter / Space | 포커스된 탭 선택(수동 활성화 방식일 때) |
접근성
role="tablist"컨테이너 + 각 탭role="tab"·aria-selected·aria-controls(패널 id) + 패널role="tabpanel"·aria-labelledby(탭 id).- 로빙 tabindex: 활성 탭만
tabindex="0", 나머지는tabindex="-1"—Tab한 번에 탭 목록을 지나가고 방향키로 탭을 옮긴다(아래 JS). - 활성 표시는 색만이 아니라 인디케이터(밑줄)로도 — 색맹·저시력에 대비한다.
- 포커스 링 제거 금지, 각 탭 히트영역 ≥
--target-min. 기준:../guidelines/accessibility.md.
코드
표준 규격 600×600mm, 친환경 그래픽 노면표시재로 제작합니다.
GREENGUARD GOLD(UL 2818) 인증 라텍스 잉크를 사용합니다.
어린이보호구역 노면표시·안전표지 시공사례를 담았습니다.
html
<div class="pi-tabs">
<div class="pi-tabs__list" role="tablist" aria-label="안전표지 상세">
<button class="pi-tabs__tab" role="tab" id="tab-spec" aria-selected="true" aria-controls="panel-spec" tabindex="0">규격</button>
<button class="pi-tabs__tab" role="tab" id="tab-material" aria-selected="false" aria-controls="panel-material" tabindex="-1">소재</button>
<button class="pi-tabs__tab" role="tab" id="tab-case" aria-selected="false" aria-controls="panel-case" tabindex="-1">시공사례</button>
</div>
<div class="pi-tabs__panel" role="tabpanel" id="panel-spec" aria-labelledby="tab-spec">
<p>표준 규격 600×600mm, 친환경 그래픽 노면표시재로 제작합니다.</p>
</div>
<div class="pi-tabs__panel" role="tabpanel" id="panel-material" aria-labelledby="tab-material" hidden>
<p>GREENGUARD GOLD(UL 2818) 인증 라텍스 잉크를 사용합니다.</p>
</div>
<div class="pi-tabs__panel" role="tabpanel" id="panel-case" aria-labelledby="tab-case" hidden>
<p>어린이보호구역 노면표시·안전표지 시공사례를 담았습니다.</p>
</div>
</div>css
.pi-tabs__list { display: flex; gap: var(--tab-gap); border-bottom: var(--border-thin) solid var(--tab-border); }
.pi-tabs__tab {
padding: var(--tab-pad-y) var(--tab-pad-x);
border: none; background: none; cursor: pointer;
color: var(--tab-fg);
font: var(--text-label);
letter-spacing: var(--text-label-letter-spacing);
border-bottom: var(--tab-indicator-size) solid transparent; margin-bottom: calc(var(--border-thin) * -1);
min-height: var(--target-min);
word-break: keep-all;
transition: color var(--duration-base) var(--easing-out), border-color var(--duration-base) var(--easing-out);
}
.pi-tabs__tab:hover { color: var(--tab-fg-active); }
.pi-tabs__tab:focus-visible { outline: none; box-shadow: var(--focus-ring); border-radius: var(--radius-sm); }
.pi-tabs__tab[aria-selected="true"] { color: var(--tab-fg-active); border-bottom-color: var(--tab-indicator); }
.pi-tabs__tab:disabled { opacity: var(--opacity-disabled); cursor: not-allowed; }
.pi-tabs__panel { padding-top: var(--space-6); color: var(--color-text-secondary); font: var(--text-body); word-break: keep-all; }
.pi-tabs__panel[hidden] { display: none; }
@media (prefers-reduced-motion: reduce) { .pi-tabs__tab { transition: none; } }js
document.querySelectorAll(".pi-tabs").forEach((tabs) => {
const tablist = tabs.querySelector('[role="tablist"]');
const tabsArr = [...tablist.querySelectorAll('[role="tab"]')];
const select = (tab) => {
tabsArr.forEach((t) => {
const on = t === tab;
t.setAttribute("aria-selected", on ? "true" : "false");
t.tabIndex = on ? 0 : -1;
document.getElementById(t.getAttribute("aria-controls")).hidden = !on;
});
};
tablist.addEventListener("click", (e) => {
const tab = e.target.closest('[role="tab"]');
if (tab) select(tab);
});
tablist.addEventListener("keydown", (e) => {
const i = tabsArr.indexOf(document.activeElement);
const moves = { ArrowRight: i + 1, ArrowLeft: i - 1, Home: 0, End: tabsArr.length - 1 };
if (i < 0 || !(e.key in moves)) return;
e.preventDefault();
const n = (moves[e.key] + tabsArr.length) % tabsArr.length;
select(tabsArr[n]);
tabsArr[n].focus();
});
});Changelog
| 날짜 | 변경 |
|---|---|
| 2026-07-15 | 문서 계약(10섹션) 적용 — Anatomy 도해·상태 매트릭스·콘텐츠 규칙·방향키/로빙 tabindex 실코드 신설. KRDS 벤치마킹 규격 승계 |