Skip to content

SubTab

진행 중 (SubTab component — work in progress)

  • 낱개 하위(sub) 탭. 상위 TabNav(밑줄형) 아래에서 보조 탭으로 쓰는 설정 기반(config-driven) 컴포넌트.
  • 호스트가 getTabs / getActive / onSelect 만 넘기면 렌더. 선택(active) 탭만 채움/라인으로 강조되고, 비선택은 neutral 라인 테두리.
  • 모든 색·radius·폰트는 .ds-sub-tab* 토큰에서 옴 (마크업에 인라인 스타일·하드코딩 hex·!important 없음).
규칙 (강제) · 코딩 에이전트용 — 사람은 접힌 채로 두어도 됩니다

SubTab 강제 규칙. 상세는 아래 본문.

마크업

  • 호스트가 x-data="subTab({...})"로 감싸고 packages/components/html/sub-tab.html을 include 합니다. 손으로 짓지 마세요.
  • 활성 클래스는 is-active 입니다. ⚠️ TabNavtab-active다릅니다 — 복붙하다 자주 틀립니다.
  • 그룹 modifier(ds-sub-tab--sm/--ml · --r4/--r8/--r16 · --primary · --line)는 .ds-sub-tab 컨테이너에만 붙습니다. 아이템(.ds-sub-tab__item)에 붙이지 마세요.
  • 기본값: size='md' · radius='8' · color='dark' · fill='solid'.
    • ⚠️ SubTab.tsSubTabRadius 타입 주석은 아직 "16이 기본"이라고 잘못 적혀 있습니다(파일 헤더와 sub-tab.html 헤더는 2026-07-27에 정정됨). 실제 기본값은 8(SubTab.tsopts.radius ?? "8")이에요.
  • 표 위 기간 프리셋 등 「보조 필터」 자리에는 size='sm' + fill='line'을 씁니다 — 날 유틸(px-2 py-1 border rounded-lg)로 비슷한 칩을 새로 만들지 마세요. nav용 서브탭(md·solid)과 한 화면에 놓일 때 위계가 무너집니다. 자세한 건 sm은 어디에 쓰나.
  • SubTabItembadge·updateDot없습니다 — 슬롯은 leadingIcon/trailingIcon/leadingText/trailingText/disabled 뿐이에요.

⚠️ 트랩 (최우선) — 이 버튼에 border-* 유틸을 붙이면 radius 축이 통째로 죽습니다.ds-sub-tab__item은 전역 <button> 4px radius 강제 1번 규칙(design-system.cssbutton:not([class*="bg-"]):not(.ds-tab-item):not(.ds-sub-tab__item)…)에서는 제외돼 있습니다 — 자체 radius 3단계 --r4/--r8/--r16를 갖기 때문이고, 그 이유가 규칙 안의 주석에 적혀 있어요. 그런데 2번 규칙 button[class*="border"]:not(.ds-tab-item)에서는 제외돼 있지 않습니다. 지금 안 터지는 유일한 이유는 파셜이 그 버튼에 Tailwind border-* 클래스를 안 붙였기 때문이에요. → 그 버튼에 border·border-2·border-neutral-200 같은 클래스를 하나라도 추가하는 순간 4px !important 강제가 되살아나서 radius:'16' 같은 설정이 전부 무시됩니다. 테두리는 이미 .ds-sub-tab__item 블록이 CSS로 갖고 있어요 — 유틸을 덧붙이지 마세요.

기타

  • 복제본 없음 — 앱 main.css 어디에도 .ds-sub-tab 재정의가 없습니다(탭 계열 중 유일하게 깨끗해요). 정본만 고치면 돼요.
  • 호출처는 admin leave-management.html(nav 서브탭 leaveSubTab + 기간 프리셋 leaveApprovalDatePresetdocuments.html(자격증 기간 프리셋)이고, 전부 URL 라우트가 아니라 페이지 내 상태를 씁니다. 서브탭을 URL 라우트 키로 승격시키면 TabNav의 id 일치·popstate 트랩그대로 적용됩니다.

.ds-segment(세그먼트)와 다른 형태예요

세그먼트(.ds-segment, iOS 스타일)는 하나의 회색 통 안에 항목이 붙어 있고 선택 항목만 흰 pill로 이동해요. SubTab낱개 탭이 간격을 두고 나열되고, 선택 탭을 검정/주황으로 강조해요 — 상위 탭 아래 "하위 탭" 용도예요.

시각 미리보기

아래는 실제로 클릭되는 라이브 미리보기입니다 — 프로덕션과 동일한 .ds-sub-tab* 클래스로 렌더돼요. 상단 라디오(size·radius) + 체크박스(color·fill) 로 4축을 바꿔 보고, 탭을 클릭해 선택(active) 전환도 확인하세요.

sizeradius

선택 탭: 마리떼프랑소와저버 — 탭을 클릭해 전환해 보세요

상위 탭과의 계층 구조

SubTab은 상위 TabNav(밑줄형) 바로 아래에 놓이는 하위 탭이에요. 상위 탭으로 큰 영역을 가르고, 그 안을 SubTab으로 다시 나눠요. 아래는 상위(밑줄) → 하위 를 한 컨테이너에 쌓은 예시예요 — 상위·하위 탭이 동일한 배경색을 공유해요. 둘 다 실제 클래스로 렌더돼요.

여백은 컴포넌트가 아니라 호출처가 정합니다

.ds-sub-tab바깥 여백(margin)이 없어요 — 소유한 간격은 탭 사이 gap: 8px 하나뿐입니다. 그래서 위아래 여백은 전부 감싸는 래퍼가 결정하고, "SubTab 기본 여백" 같은 건 존재하지 않아요.

현재 값(2026-07-27 기준):

위치아래
근태 현황 > 연차 — nav 서브탭 (leave-management.html)20px (pt-5)20px (아래 패널 첫 블록의 pt-5)
문서 결재 · tm 문서 관리 — 기간 프리셋12px (mt-3, 필터 줄 바로 아래)20px (mb-5, 표와의 간격)
  • nav 서브탭 = 위아래 20px, 보조 필터(프리셋) = 위 12px · 아래 20px 이 현재 규약이에요. 새 화면도 이 값을 따르세요.
  • 서브탭 바 하나를 여러 패널이 공유하면(연차현황·연차/휴가 결재처럼) 각 패널 첫 블록의 pt반드시 같은 값으로 맞춰야 해요 — 다르면 탭을 바꿀 때마다 아래 내용이 미세하게 튑니다.

⚠️ 이 문서는 오랫동안 "상위·하위 탭 사이 간격 24px"이라고 적어 뒀지만 그 값을 쓰는 화면은 하나도 없었습니다(전부 20px 또는 12px). 2026-07-27에 실제 값으로 정정했어요.

┌─ nav.ds-tab-nav (상위 탭, 밑줄형) ──────────────┐
│  연차 관리 ‾‾‾‾   외근 관리                      │
├──────────────────────────────────────────────┤
│  ( 1 전체 )  ( 2 대기 )  ( 3 완료 )   ← .ds-sub-tab (하위 탭) │
└──────────────────────────────────────────────┘

단일 출처

파트파일
Alpine 컴포넌트packages/components/src/components/SubTab.ts
HTML partialpackages/components/html/sub-tab.html
스타일packages/static/styles/design-system.css (.ds-sub-tab*)
Alpine 등록subTab (plugin.ts)

토큰 소유권: color·radius·typography·탭 geometry(높이/패딩)는 모두 DS 소유(.ds-sub-tab* 클래스). arbitrary [...]·하드코딩 hex·새 !important 없음.

구조

┌─ .ds-sub-tab (그룹: inline-flex, gap 8) ───────────────────────┐
│  [leadingText] [leadingIcon]  라벨  [trailingText] [trailingIcon] │
│  └─ .ds-sub-tab__item (각 슬롯은 present일 때만 렌더) ──────────┘ │
│   선택 탭만 .is-active → 채움/라인 강조, 비선택은 neutral 라인  │
└─────────────────────────────────────────────────────────────────┘
  • 탭 단위 슬롯 (있으면 켜짐): leadingIcon / trailingIcon(SVG), leadingText / trailingText(보조 텍스트), disabled.
  • 이미지의 "1 / 2 / 3" 번호는 자동 넘버링이 아니라 라벨 앞 leadingText(또는 라벨 문자열)로 넣어요.

축(axis) — 4가지

그룹(.ds-sub-tab)에 모디파이어를 조합해요. subTab({ size, radius, color, fill }) 옵션이 그대로 subTabGroupClass로 반영돼요.

1) size — sm / md / ml

tokenmin-heightpadding라벨 폰트클래스
sm28px5px 12px--text-b6 (12px).ds-sub-tab--sm
md (기본)36px8px 14px--text-b5 (13px).ds-sub-tab (modifier 없음)
ml44px11px 18px--text-b4 (14px).ds-sub-tab--ml

sm — 28px · 라벨 12px

md — 36px · 라벨 13px (기본)

ml — 44px · 라벨 14px

sm은 어디에 쓰나

보조 필터 자리예요. 대표 사례가 표 위의 기간 프리셋(이번 주 · 지난 1주 · … · 전체)입니다.

핵심은 nav용 서브탭(md · solid)과 한 화면에 같이 놓일 수 있다는 점이에요. 근태 현황 > 연차 > 「연차/휴가 결재」가 그런 화면인데, 위엔 페이지를 가르는 서브탭이 있고 아래엔 기간 프리셋이 있어요. 둘 다 md · solid똑같이 생긴 알약 줄이 두 개 놓여서 "누르면 페이지가 바뀌는 것"과 "표만 다시 그리는 것"을 구분할 수 없어요.

그래서 두 축을 함께 씁니다:

역할설정읽히는 것
nav (페이지 이동)md · solid크고 검정 채움 — "지금 이 페이지"
보조 필터 (기간 프리셋)sm · line작고 테두리만 — "이 필터가 켜짐"

sm이 없던 동안 각 화면이 이 모양을 날 유틸(px-2 py-1 border rounded-lg)로 손수 만들어 쓰고 있었어요(근태 4개 화면). 새로 만들지 말고 이 단을 쓰세요.

선택 없음(getActive()"")도 정상 상태예요. 기간 프리셋은 "아무것도 안 고름"이 기본이고, 사용자가 달력으로 날짜를 직접 지정하면 다시 해제돼요. SubTab은 세그먼트(.ds-segment)와 달리 항상 하나 선택을 강제하지 않으니 그대로 두면 됩니다 — 이 상태를 만들려고 「직접 지정」 같은 항목을 지어내지 마세요.

2) radius — 4 / 8 / 16

시스템 radius 토큰 3단계로 모서리를 바꿔요(기본 8. 16은 첨부 이미지처럼 더 둥근 형태).

토큰클래스
4--radius-4.ds-sub-tab--r4
8 (기본)--radius-8.ds-sub-tab--r8
16--radius-16.ds-sub-tab--r16

radius 4

radius 8 (기본)

radius 16

3) color × 4) fill — 선택 강조 4종

선택(active) 탭의 강조를 color(dark / primary) × fill(solid / line) 로 조합해요. 비선택은 4종 모두 공통 neutral 라인 테두리예요.

color · fill선택(active)클래스
dark · solid (기본)neutral-900 채움 + 흰 글자(modifier 없음)
dark · lineneutral-900 테두리 + neutral-900 글자.ds-sub-tab--line
primary · solid--ds-color-primary(주황) 채움 + 흰 글자.ds-sub-tab--primary
primary · line주황 테두리 + 주황 글자.ds-sub-tab--primary.ds-sub-tab--line

dark · solid (기본)

dark · line

primary · solid

primary · line

호출 패턴

html
<div x-data="subTab({
  getTabs: () => mySubTabs,         // () => SubTabItem[]
  getActive: () => activeSubId,     // () => string
  onSelect: (id) => switchSub(id),  // (id) => void
  size: 'md',                       // 'sm' | 'md'(기본) | 'ml' — 선택
  radius: '8',                      // '4' | '8'(기본) | '16' — 선택
  color: 'dark',                    // 'dark'(기본) | 'primary' — 선택
  fill: 'solid',                    // 'solid'(기본) | 'line' — 선택
})">
  <!-- include "../../packages/components/html/sub-tab.html" -->
</div>

→ partial 한 줄로 .ds-sub-tab 그룹 전체가 렌더됨. 탭 목록·선택 ID는 호스트 상태에 반응(reactive).

API

subTab(opts: SubTabOptions) Alpine.data factory.

SubTabOptions (모두 optional)

Prop타입설명
getTabs() => SubTabItem[]반응형 탭 목록 source
getActive() => string반응형 선택 탭 id source
onSelect(id: string) => void비활성 아닌 탭 클릭 시 id로 호출
size'sm' | 'md' | 'ml'탭 크기. 생략 시 md(36px); sm은 보조 필터용 작은 탭(28px), ml은 큰 탭(44px)
radius'4' | '8' | '16'모서리 단계. 생략 시 8
color'dark' | 'primary'선택 강조색. 생략 시 dark
fill'solid' | 'line'선택 채움 방식. 생략 시 solid

SubTabItem (탭 1개)

필드타입필수설명
idstringonSelect에 넘어가는 고유 id
labelstring메인 탭 텍스트
leadingIconstring앞 SVG 아이콘 이름(레지스트리). 생략 시 숨김
trailingIconstring뒤 SVG 아이콘 이름. 생략 시 숨김
leadingTextstring라벨 앞 보조 텍스트(예: 번호). 생략 시 숨김
trailingTextstring라벨 뒤 보조 텍스트. 생략 시 숨김
disabledboolean클릭 무시 + 흐리게

사용된 토큰

카테고리토큰
Color (비선택)--color-neutral-200(테두리) · --color-neutral-500(글자) · --color-neutral-300/700·--color-neutral-50(hover)
Color (선택)--color-neutral-900(dark) · --ds-color-primary(primary) · --ds-bw-white(solid 글자)
Color (보조 텍스트)--color-neutral-400
Typography--text-b6(12px, sm) · --text-b5(13px, md) · --text-b4(14px, ml) · --text-b6(12px, 보조 텍스트)
Radius--radius-4 / --radius-8 / --radius-16

검토 필요 사항

  • radius 중간값 = 8 (기본값) — 현황 유지 확정(2026-07-13). 원래 디자인 요청은 12였고 당시엔 12px 토큰이 없어 가장 인접한 --radius-8을 채택했는데, --radius-12가 신설되어(모서리) 그 제약은 사라졌습니다. 그럼에도 12px 상향은 시각 변경이라 현행 8px를 유지하기로 결정했습니다 — 이 항목은 닫혔습니다(다시 열려면 디자인 재검토가 선행).
  • 🟢 탭 높이 28/36/44 — 상위 TabNav(40/48)보다 한 단계 작게 잡아 "상위 > 하위" 위계를 시각적으로 분리. sm(28)은 그보다 또 한 단 아래로, nav 서브탭(md)과 같은 화면에 놓이는 보조 필터용.

변경 이력

날짜내용
2026-07-27size='sm'(28px / --text-b6 12px) 추가 — 표 위 기간 프리셋 같은 「보조 필터」 자리를 위한 단. nav용 서브탭(md·solid)과 한 화면에 놓일 때 sm·line으로 위계를 가른다. 이 단이 없어 근태 4개 화면이 날 유틸로 같은 칩을 손수 만들어 쓰던 것을 정본으로 회수. <DemoSubTab> size 컨트롤이 체크박스(ml on/off) → 라디오(sm/md/ml)로 바뀜
2026-06-30SubTab 컴포넌트 신규 — 낱개 하위 탭(.ds-sub-tab* + subTab + sub-tab.html). 축 4종(size sm/md/ml · radius 4/8/16 · color dark/primary · fill solid/line), 비선택 공통 neutral 라인. <DemoSubTab> 라이브 미리보기 + 상위 TabNav와의 계층 데모