API
tabs(TabItem[]) · activeKey · onChange(key) · baseId(a11y id prefix)
variant
line(기본) · contained
orientation
horizontal(기본) · vertical — Radix keyboard semantics 유지
TabItem
key · label · badge?(number) · disabled?(boolean)
TabsContent
tabKey + baseId — aria-controls/aria-labelledby 자동 연결
배지
badge > 0일 때 자동 표시, 99 초과 시 '99+' 처리
접근성
Radix가 tablist/tab/tabpanel + arrow-key 네비게이션 자동 관리

기본

activeKey와 onChange로 탭 상태 관리

시험 기간, 접근 규칙, 준비 상태를 확인합니다.

코드 보기tsx
import { useState } from "react";
import { Tabs, TabsContent } from "@olundot/ui";

export function TabsBasic() {
  const [active, setActive] = useState("overview");
  const tabs = [
    { key: "overview", label: "개요" },
    { key: "questions", label: "문항" },
    { key: "students", label: "수강생" },
  ];
  return (
    <Tabs tabs={tabs} activeKey={active} onChange={setActive} baseId="basic-tabs">
      <TabsContent tabKey="overview" baseId="basic-tabs">
        <p style={{ padding: "16px 0", color: "var(--text-secondary)", fontSize: "0.875rem" }}>
          시험 기간, 접근 규칙, 준비 상태를 확인합니다.
        </p>
      </TabsContent>
      <TabsContent tabKey="questions" baseId="basic-tabs">
        <p style={{ padding: "16px 0", color: "var(--text-secondary)", fontSize: "0.875rem" }}>
          32개 문항 목록입니다.
        </p>
      </TabsContent>
      <TabsContent tabKey="students" baseId="basic-tabs">
        <p style={{ padding: "16px 0", color: "var(--text-secondary)", fontSize: "0.875rem" }}>
          128명의 수강생이 등록되어 있습니다.
        </p>
      </TabsContent>
    </Tabs>
  );
}

배지 포함

탭 label 옆에 카운트 배지 표시

응시 대기 12명

코드 보기tsx
import { useState } from "react";
import { Tabs, TabsContent } from "@olundot/ui";

export function TabsWithBadges() {
  const [active, setActive] = useState("pending");
  const tabs = [
    { key: "all", label: "전체", badge: 48 },
    { key: "pending", label: "대기", badge: 12 },
    { key: "submitted", label: "제출", badge: 36 },
    { key: "graded", label: "채점 완료", badge: 0 },
  ];
  return (
    <Tabs tabs={tabs} activeKey={active} onChange={setActive} baseId="badge-tabs">
      <TabsContent tabKey="all" baseId="badge-tabs">
        <p style={{ padding: "16px 0", color: "var(--text-secondary)", fontSize: "0.875rem" }}>전체 48명</p>
      </TabsContent>
      <TabsContent tabKey="pending" baseId="badge-tabs">
        <p style={{ padding: "16px 0", color: "var(--text-secondary)", fontSize: "0.875rem" }}>응시 대기 12명</p>
      </TabsContent>
      <TabsContent tabKey="submitted" baseId="badge-tabs">
        <p style={{ padding: "16px 0", color: "var(--text-secondary)", fontSize: "0.875rem" }}>제출 완료 36명</p>
      </TabsContent>
      <TabsContent tabKey="graded" baseId="badge-tabs">
        <p style={{ padding: "16px 0", color: "var(--text-secondary)", fontSize: "0.875rem" }}>채점 완료 0명</p>
      </TabsContent>
    </Tabs>
  );
}

Contained

bounded surface 안에서 쓰는 segmented tab

전체 항목을 표시합니다.

코드 보기tsx
import { useState } from "react";
import { Tabs, TabsContent } from "@olundot/ui";

export function TabsContained() {
  const [active, setActive] = useState("all");
  const tabs = [
    { key: "all", label: "전체" },
    { key: "ready", label: "준비됨" },
    { key: "blocked", label: "보류" },
  ];
  return (
    <Tabs variant="contained" tabs={tabs} activeKey={active} onChange={setActive} baseId="contained-tabs">
      <TabsContent tabKey="all" baseId="contained-tabs">
        <p style={{ padding: "16px 0", color: "var(--text-secondary)", fontSize: "0.875rem" }}>전체 항목을 표시합니다.</p>
      </TabsContent>
      <TabsContent tabKey="ready" baseId="contained-tabs">
        <p style={{ padding: "16px 0", color: "var(--text-secondary)", fontSize: "0.875rem" }}>준비된 항목입니다.</p>
      </TabsContent>
      <TabsContent tabKey="blocked" baseId="contained-tabs">
        <p style={{ padding: "16px 0", color: "var(--text-secondary)", fontSize: "0.875rem" }}>보류 중인 항목입니다.</p>
      </TabsContent>
    </Tabs>
  );
}

Vertical

긴 레이블과 넓은 레이아웃을 위한 세로 탭

프로필 정보를 확인합니다.

코드 보기tsx
import { useState } from "react";
import { Tabs, TabsContent } from "@olundot/ui";

export function TabsVertical() {
  const [active, setActive] = useState("profile");
  const tabs = [
    { key: "profile", label: "프로필" },
    { key: "security", label: "보안 설정" },
    { key: "billing", label: "결제 정보" },
  ];
  return (
    <Tabs orientation="vertical" tabs={tabs} activeKey={active} onChange={setActive} baseId="vertical-tabs">
      <TabsContent tabKey="profile" baseId="vertical-tabs">
        <p style={{ color: "var(--text-secondary)", fontSize: "0.875rem" }}>프로필 정보를 확인합니다.</p>
      </TabsContent>
      <TabsContent tabKey="security" baseId="vertical-tabs">
        <p style={{ color: "var(--text-secondary)", fontSize: "0.875rem" }}>보안 설정을 관리합니다.</p>
      </TabsContent>
      <TabsContent tabKey="billing" baseId="vertical-tabs">
        <p style={{ color: "var(--text-secondary)", fontSize: "0.875rem" }}>결제 정보를 확인합니다.</p>
      </TabsContent>
    </Tabs>
  );
}

비활성 탭

선택할 수 없는 탭 상태

발행된 강의 목록입니다.

코드 보기tsx
import { useState } from "react";
import { Tabs, TabsContent } from "@olundot/ui";

export function TabsDisabled() {
  const [active, setActive] = useState("published");
  const tabs = [
    { key: "published", label: "발행됨" },
    { key: "draft", label: "초안" },
    { key: "archived", label: "보관됨", disabled: true },
    { key: "deleted", label: "삭제됨", disabled: true },
  ];
  return (
    <Tabs tabs={tabs} activeKey={active} onChange={setActive} baseId="disabled-tabs">
      <TabsContent tabKey="published" baseId="disabled-tabs">
        <p style={{ padding: "16px 0", color: "var(--text-secondary)", fontSize: "0.875rem" }}>
          발행된 강의 목록입니다.
        </p>
      </TabsContent>
      <TabsContent tabKey="draft" baseId="disabled-tabs">
        <p style={{ padding: "16px 0", color: "var(--text-secondary)", fontSize: "0.875rem" }}>
          초안 목록입니다.
        </p>
      </TabsContent>
      <TabsContent tabKey="archived" baseId="disabled-tabs">
        <p style={{ padding: "16px 0", color: "var(--text-secondary)", fontSize: "0.875rem" }}>
          보관된 항목 (비활성)
        </p>
      </TabsContent>
      <TabsContent tabKey="deleted" baseId="disabled-tabs">
        <p style={{ padding: "16px 0", color: "var(--text-secondary)", fontSize: "0.875rem" }}>
          삭제된 항목 (비활성)
        </p>
      </TabsContent>
    </Tabs>
  );
}

긴 탭 목록

좁은 영역에서 가로 스크롤로 처리

긴 레이블과 많은 탭은 탭 행 안에서 가로 스크롤됩니다.

코드 보기tsx
import { useState } from "react";
import { Tabs, TabsContent } from "@olundot/ui";

export function TabsOverflow() {
  const [active, setActive] = useState("overview");
  const tabs = [
    { key: "overview", label: "시험 개요" },
    { key: "questions", label: "문항 구성" },
    { key: "students", label: "수강생 현황" },
    { key: "scores", label: "점수 분포" },
    { key: "appeals", label: "이의 신청" },
    { key: "history", label: "변경 이력" },
  ];
  return (
    <div style={{ maxWidth: "320px" }}>
      <Tabs tabs={tabs} activeKey={active} onChange={setActive} baseId="overflow-tabs">
        <TabsContent tabKey="overview" baseId="overflow-tabs">
          <p style={{ padding: "16px 0", color: "var(--text-secondary)", fontSize: "0.875rem" }}>
            긴 레이블과 많은 탭은 탭 행 안에서 가로 스크롤됩니다.
          </p>
        </TabsContent>
      </Tabs>
    </div>
  );
}