Skip to content

버튼 (Buttons) ​

버튼의 모양과 상태를 확인하고, 사용 기준과 적용 방법을 찾아보세요.

CSS 기반 · Web / Mobile · 문서 포맷 시범 적용

Interactive playground ​

옵션을 바꾸면 미리보기와 코드가 함께 바뀌어요.
미리보기 테마
실제 DS CSS와 아이콘을 사용해요. Hover · Tab 포커스 · 누르기로 상태를 확인하세요. 데모 클릭 0회 · 저장 요청은 보내지 않아요.
<button type="button" class="ds-btn ds-btn--solid ds-btn--primary ds-btn--md">
  <span>저장</span>
</button>

아이콘을 켠 복사 코드는 기존 앱의 Alpine 등록을 전제로 합니다. 적용 방법을 확인하세요. 변형별 색·크기·모서리·상태는 위 Playground에서 선택해 확인할 수 있어요.

규칙 (강제) · 코딩 에이전트용

버튼 강제 규칙. 상세·미리보기·전체 조합표는 아래 본문.

적용 범위 — 신규 코드 기준. 아래 규칙은 새로 작성·수정하는 버튼에 강제됩니다. 앱에 남아 있는 레거시 버튼(ds-btn--secondary·icon-btn·날 유틸 조합)은 "규칙 위반"이 아니라 마이그레이션 대상입니다 — 마이그레이션 매핑대로 교체하되, 이미 작업 중인 파일 안에 있을 때만 손대세요. 레거시 버튼을 찾아 다니며 전수 교체하지 마세요(별도 PR 예정). 특히 icon-btn(28px) → ds-btn--icon(24px)은 크기가 바뀌는 시각 변경이라 임의로 밀면 안 됩니다.

조합 (신규 — 강제)

  • 버튼은 베이스 + 변형 + 색 + 크기 4축을 모두 명시: ds-btn ds-btn--solid ds-btn--primary ds-btn--md. 축을 빠뜨리면 색·크기가 결정되지 않습니다.
  • --line은 반드시 명시적 톤과 함께(--normal/--dark/--red …). 톤 없는 ds-btn--line은 색이 안 정해집니다.
  • text 변형은 ds-btn-text 별도 패밀리(ds-btn--text 아님) — 폰트 스케일이 달라서 클래스 세트가 분리돼 있습니다.
  • 날 유틸 즉석 조합 금지: bg-orange-500 px-3 py-1.5 text-white rounded 같은 조합으로 버튼을 새로 만들지 마세요. 즉석 색 pill(text-[10px] bg-orange-100 …)도 새로 만들지 마세요.
  • 상태(hover/focus/pressed/disabled)에 마크업 금지 — CSS 공통 인터랙션 오버레이가 처리합니다. hover:bg-* 유틸을 붙이지 마세요.
  • 맥락 → 조합 매핑(모달 푸터·삭제·툴바 추가 등)은 사용 규칙 표를 따르세요.
  • "여러 선택지 중 하나를 고르는 2줄 카드"는 ds-option-card — 테두리·hover를 유틸로 직접 그리지 마세요. 크기는 compact(기본) / --lg 2종뿐이고(크기 2종) 중간값을 새로 만들지 않습니다. min-height 금지(내용만큼 늘어남), 제목줄 아이콘(__icon)은 선택이며 카드마다 같은 아이콘이면 넣지 마세요.

⚠️ 트랩 — 모르면 반드시 깨지는 것

  • <button> radius는 4px로 전역 강제입니다. design-system.css의 blanket 규칙 3개가 각각 걸어요 — button:not([class*="bg-"]):not(.ds-tab-item)… · button[class*="border"]:not(.ds-tab-item) · button[class*="bg-"]:not([class*="border"]). Tailwind rounded-*를 줘도 덮어써집니다. 제외 명단: .ds-tab-item · .ds-sub-tab__item · .ds-menu__item · .icon-btn · .ds-btn--icon · .ds-btn--circle · .org-tree__action-btn · .ds-page-header__avatar. 모서리를 바꾸려면 모서리 축 ds-r-8/12/16/999(+ 되돌리기용 ds-r-4)를 쓰세요 — 세 규칙이 값을 var(--ds-btn-radius, 4px)로 읽으므로 !important 없이 이깁니다. rounded-*·!important·<div role="button"> 우회는 쓰지 마세요(축에 없는 값이 꼭 필요할 때만 엘리먼트 교체 고려). 상세: 모서리 → 트랩.
  • bg-* 클래스가 없는 <button>은 DS 크기 clamp에 걸립니다(button:not([class*="bg-"])): 폰트 크기 유틸에 따라 min-height/padding이 강제돼요 — text-[10px]→14px · text-xs/text-[12px]→21px · text-[13px]→22px · text-sm→24px · text-base→26px, padding 2px 고정. 통합 체계(ds-btn--*)를 쓰면 걸리지 않습니다.
  • 아이콘 버튼 white의 hover 는 회색이 아니라 반투명 흰색(hover · focus 20% · pressed 30%)입니다 — 색 면 위 전용 변형이라 나눴습니다. 흰 바탕 위에서는 쓰지 마세요(hover 가 안 보입니다). 다른 색 위에 놓인다고 hover:bg-* 를 붙이지 말고 white 를 쓰세요.
  • disabled 배경: .ds-btn:disabled는 background: var(--color-line-normal)(#e5e5e5 회색 박스) + color: var(--color-label-disable)로 강제됩니다. 투명 배경이 필요하면 ds-btn--icon을 쓰세요(.ds-btn--icon:disabled는 background: transparent).
  • line 버튼 disabled 보더는 #c1c1c1 solid가 아니라 40% 불투명도(var(--ds-tint-light-40) = rgba(193,193,193,0.4), Figma 9032:2225). 직접 #c1c1c1로 칠하지 마세요.
  • !important 금지 — 앱 main.css는 @import 뒤에 오므로 소스 순서로 이미 이깁니다. 캐스케이드가 안 먹으면 !important 대신 셀렉터/엘리먼트를 바꾸세요.
  • 버튼 CSS 복제 주의: 버튼은 정본(design-system.css)에만 있지만, 같은 계열인 .ds-page-btn(페이지네이션)은 앱 5곳에 복제돼 있습니다. 상세: .claude/rules/frontend/design-system-architecture.md.
  • 다크 모드(2026-10-19) — 연한 주요(--primary-soft = --lightor) 면 --color-action-primary-soft 는 다크에서 bg-brand-tint(주황 22% 섞음, 전 orange-900 진한 적갈색). 비활성 글자 --color-action-disabled-fg 다크 neutral-450(바탕 700 위 2.8). 라인 위험(--line.--danger / --red) 글자는 --color-secondary-red(라이트 red-600 같음 · 다크 red-400) — 채움용 action-danger 는 다크에서 어두운 빨강이라 글자에 쓰지 않는다. 확인: Playground 「미리보기 테마 → 다크」.

개요 ​

Hereby 버튼은 하나의 통합 체계예요. 단일 베이스 ds-btn 위에 변형 × 색 × 크기 modifier만 조합해서 만들어요. "모달 버튼" 처럼 맥락별로 따로 만든 클래스는 없어요 — 맥락은 사용 규칙으로 풀고, 버튼 자체는 변수 조합이에요.

html
<button class="ds-btn  ds-btn--solid  ds-btn--primary  ds-btn--md">저장</button>
<!--          베이스    변형           색              크기        -->

상태(hover·focus·pressed·disabled)는 마크업이 필요 없어요. 전부 CSS에서 공통 인터랙션 오버레이로 처리돼요. hover:bg-* 같은 유틸을 따로 붙이지 마세요.

기본 ds-btn과 다르게 쓰이는 조합이 있어요

버튼 변형을 여러 개 조합해 만든 실제 컴포넌트(소속 칩·리스트 카드)는 단일 버튼과 쓰임새가 달라요 — 값을 보여주면서 동시에 조작하는 역할을 하고, 맥락에 따라 구성 조각이 빠지기도 해요. 단일 버튼 규칙만 보고 만들면 어긋나니, 이런 조합을 만들기 전에 아래 예시를 먼저 보세요.

예시 1 · 소속 칩 ↓예시 2 · 리스트 카드 ↓

출처

구현: packages/static/styles/design-system.css 의 Hereby Button System (unified) 블록. 설계 narrative: docs/packages/reference/button-system-spec.md. Figma 5노드(solid 9010-10531 · line 9017-10430 · text 9036-1306 · interaction 9010-10532 · secondary 팔레트 9014-11311 · icon 9803-8531).

사용 규칙 (맥락 → 조합) ​

버튼은 변수 조합이고, 맥락은 아래 규칙으로 정해요.

맥락조합
모달·얼럿 푸터 권장 컨펌solid · dark · md
모달·얼럿 푸터 크리티컬 컨펌solid · lightor · md
모달·얼럿 푸터 취소·닫기solid · white-bk · md
표/툴바 "추가"solid · primary · sm
인라인 링크 CTAtext · primary
삭제(위험) — 인라인/툴바line · red · md
리스트 행 상세·보기(텍스트 버튼)line · normal · sm
모바일 풀폭 제출solid · primary · lg + ds-btn--full
모바일 모달 푸터 CTA (<768)위 푸터 조합과 같은 색·변형, 크기만 ds-btn--lg + 풀폭 (권장 기본값)
표 행 아이콘 액션icon · dark (또는 muted)

리스트 "보기/상세"는 라인 버튼으로 (즉석 색 pill 금지)

결재/문서 목록 행의 "보기·상세" 는 ds-btn ds-btn--line ds-btn--normal ds-btn--sm으로 통일해요. text-[10px] bg-orange-100 text-orange-600 px-2 py-0.5 rounded 같은 즉석 색 pill을 새로 만들지 마세요. 그리고 --line은 항상 명시적 톤(--normal/--dark/--red …)을 함께 붙여야 해요(톤 없는 ds-btn--line은 색이 안 정해짐).

모달·얼럿 푸터는 위치가 아니라 맥락으로

가장 우측이라고 항상 orange/dark를 쓰지 않아요. 확정 버튼은 위계(어느 버튼) × weight 티어(중요도: 기본=dark·크리티컬=lightor·물러남=white-bk) 2축으로 정합니다. 의미색 red(위험)·green(승인)은 모달 밖 인라인·툴바·워크플로에 남습니다. 전체 규칙·예시는 모달 → 푸터 버튼 강조를 참고하세요.

변형 (variation) ​

같은 색 modifier라도 변형마다 다르게 표현돼요.

SolidLineTextIcon
형태채움 + 흰 글자보더 + 글자색, 투명 bg글자만24px 원형 아이콘
채움 옵션(기본 채움)ds-btn--bg = 흰색 채움——
좌우 아이콘✅ leading/trailing✅✅— (아이콘 자체가 본체)
용도주요 확정보조·위험인라인 CTA·링크표·툴바 아이콘 액션

색 (color) ​

색은 변형마다 쓸 수 있는 팔레트가 달라요.

변형구분색 옵션
Solid기본primary · gradient · dark · assistive · muted · white-bk · white-or · lightor
Solid의미색red · green · blue · purple · yellow · aquablue
Solid연한 의미색lightred · lightgreen · lightblue · lightpurple · lightyellow · lightaquablue
Line기본primary · dark · assistive · muted · normal · white
Line의미색red · green · blue · purple · yellow · aquablue
Text기본primary · dark · assistive · muted · white
Text의미색red · green · blue · purple · yellow · aquablue
Icon기본dark · white · muted · primary
Icon의미색red · green · blue · purple · yellow · aquablue
적용 대상클래스 표기참고
Solid · Line · Iconds-btn--{색}Line은 색을 반드시 명시합니다. Icon은 기본 dark이며 의미색은 아이콘 색에 적용됩니다.
Textds-btn-text--{색}muted는 기본값이므로 색 modifier를 생략합니다.
Line의 흰 배경ds-btn--bg색과 별도 옵션입니다. 보더만 표시하거나 흰 배경을 채울 수 있습니다.

Playground의 변형 → 색에서 확인하세요. 흰색 계열을 고르면 식별을 위해 미리보기 배경만 어두워집니다.

레거시 색은 secondary로 매핑해요

green·purple·blue·yellow·aquablue는 Figma secondary 공식 색이라 정상이에요. 단 gray-*→neutral, amber-*→yellow, emerald/indigo/teal는 해당 secondary로 바꿔야 해요. 자세한 건 색 토큰 컨벤션을 참고하세요.

크기 (size) ​

solid·line은 높이 기준 6단계예요.

token높이
ds-btn--xs20px
ds-btn--sm28px
ds-btn--md32px (기본)
ds-btn--ml40px
ds-btn--lg48px
ds-btn--xl52px

모바일에서는 ds-btn--lg를 기본으로 권합니다 — 데스크톱의 ds-btn--md·ds-btn--ml은 마우스 포인터 기준 크기라 손가락으로는 작습니다(크기는 픽셀이 아니라 이 클래스로 지정하세요. 버튼은 영문 표기, 폼 컨트롤은 숫자 표기 ds-input--48). 모바일 화면(직원 앱)과 모바일 풀페이지 모달의 CTA는 ds-btn--lg + 풀폭(ds-btn--full, 또는 푸터의 flex: 1)이 출발점이에요(강제는 아니니 화면이 요구하면 다른 크기를 골라도 됩니다). 데스크톱과 같은 마크업을 쓰는 화면에서 크기만 바꾸는 법은 → Modal → 모바일.

icon은 같은 --xs/--sm/--md/--lg 토큰을 쓰되 글리프(아이콘) ↔ 호버 박스가 ×1.5로 묶여요(원형 호버 영역 = 아이콘의 1.5배). 기본은 --sm(16↔24)이에요.

token글리프호버 박스
ds-btn--xs12px18px
ds-btn--sm16px24px (기본)
ds-btn--md20px30px
ds-btn--lg24px36px

text는 별도 폰트 스케일이에요: ds-btn-text--sm(12px) · --md(13px) · --lg(14px) · --xl(16px), 기본 10px. 높이 없는 인라인이에요.

모서리 (radius) ​

버튼 모서리는 기본 4px이에요(2026-07-13 확정 — 8/12/16/999를 비교한 뒤 기본값은 4px로 유지하기로 했어요). 다른 모서리가 필요한 버튼에만 ds-r-* 를 하나 얹으세요. 변형·색·크기와 독립된 축이라 어떤 조합에도 붙일 수 있어요.

html
<button class="ds-btn ds-btn--solid ds-btn--primary ds-btn--md  ds-r-12">저장</button>
<!--                                                             모서리 12px -->
클래스모서리
(없음)4px (기본)
ds-r-44px — 기본값과 같아요. 아래 「컨테이너 상속」으로 다른 모서리가 걸린 안에서 이 버튼만 기본으로 되돌릴 때만 씁니다
ds-r-88px
ds-r-1212px
ds-r-1616px
ds-r-999999px — 알약(pill)

Playground의 모서리 컨트롤로 현재 선택한 색·크기 조합에 적용해볼 수 있어요. 원형 Icon에는 모서리 컨트롤이 표시되지 않습니다.

컨테이너에 얹으면 그 안의 버튼이 전부 바뀌어요 — 레거시 버튼까지

ds-r-* 는 --ds-btn-radius 변수만 세팅하고, 버튼의 border-radius 선언이 그 변수를 읽어요(굵기 축 ds-fw-* 와 같은 구조). CSS 변수는 상속되므로 버튼을 감싼 컨테이너에 얹으면 그 안의 버튼에 일괄 적용돼요.

텍스트필드도 같은 축을 따라요(2026-10-01). ds-r-* 는 --ds-textfield-radius 도 함께 세팅하므로, 입력칸에 직접 붙이거나 입력칸·버튼을 감싼 컨테이너에 얹으면 둘의 모서리가 같이 바뀝니다. 모바일에서 버튼을 16으로 올린 화면(로그인·도메인 설정 팝업)은 입력칸도 16으로 맞춥니다 — 입력칸만 4로 남으면 두 요소의 모양이 어긋나요.

여기서 "그 안의 버튼"은 DS 버튼만이 아니라 <button> 전부예요 — 날 유틸(bg-*/border-*)로 만든 레거시 버튼도 전역 강제 규칙을 통해 같은 변수를 읽기 때문이에요. 그래서 툴바·카드에 얹으면 의도하지 않은 주변 버튼까지 모서리가 바뀝니다.

  • 기본은 버튼 엘리먼트에 직접 얹으세요. 컨테이너 적용은 그 안의 버튼을 전부 확인한 뒤에만.
  • 컨테이너 적용 중 특정 버튼만 되돌리려면 그 버튼에 ds-r-4(기본값)를 얹으세요.
  • 화면 전체를 한 번에 바꿔보는 실험(예: 앱 전체를 12px로 미리보기)에는 이 상속이 유용해요 — <body>에 얹으면 됩니다.
  • 변수를 읽지 않는 버튼 외 요소(카드·입력 등)는 영향이 없어요.

원형 아이콘 버튼은 제외예요

ds-btn--icon · ds-btn--circle 은 원형(999px)이 정체성이라 이 축을 무시해요.

상태 (state) ​

마크업 없이 CSS가 처리해요. 공통 인터랙션 오버레이를 얹는 단일 모델이에요.

상태solidline · text · icon오버레이
hover채움 위 오버레이bg 오버레이20%
focus전역 링 + 오버레이전역 링 + bg 오버레이30%
pressed오버레이bg 오버레이10%
disabled회색 박스line=보더 40% / text·icon=흐리게—
  • 오버레이 톤: 일반 색은 muted(hover/focus)→black(pressed), primary 계열은 orange, light 변형(lightred·lightgreen·lightor 등)은 각자 의미색(빨강·초록·주황…)으로 진해져요 — 연한 -50 채움이 hover에 회색으로 바래지 않게.
  • focus 링은 전역 button:focus-visible로 모든 버튼에 자동 적용돼요(키보드 접근성).
  • 아이콘 버튼 white만 예외 — 색 면(툴팁 · 배너 등) 위에 놓이는 변형이라, 회색 원 대신 반투명 흰 원이 깔려요(hover · focus 흰색 20% · pressed 30%). 회색은 보라 · 주황 면 위에서 불투명한 얼룩처럼 떠 보여서 나눴어요. 나머지 아이콘 색은 그대로 회색(neutral-100 → pressed neutral-200)이에요.

적용 방법 ​

버튼은 CSS 기반이에요. 이미 DS를 사용하는 화면에는 버튼용 패키지나 새 컴포넌트를 설치하지 않습니다.

기존 앱에서 사용 ​

위 Playground의 코드를 기존 화면에 붙여 넣고, 화면의 기존 이벤트 처리와 연결하세요. 데모의 클릭 횟수는 문서 확인용이며 복사 코드에는 포함하지 않습니다.

CSS 연결 확인 ​

apps/<app>/src/styles/main.css 기준으로 기존 import를 확인하세요. 이미 있다면 중복으로 추가하지 않습니다.

css
@import "../../../../packages/components/html/design-system.css";

스타일의 원본은 packages/static/styles/design-system.css입니다. 문서 편집을 위해 앱 CSS나 원본 버튼 규격을 변경하지 않습니다.

아이콘을 켠 경우 ​

Playground의 아이콘 코드는 기존 Alpine 앱의 $icon을 사용해요. apps/admin/src/main.ts처럼 createAuthComponentsPlugin()이 등록된 환경을 전제로 합니다. 기존 등록을 중복 호출하지 마세요.

이 문서의 Playground는 Vue로 옵션을 조작하지만, 버튼 스타일과 아이콘은 실제 DS 원본을 사용합니다. 복사하는 코드는 Vue 컴포넌트가 아닌 HTML / Alpine 마크업이에요.

클래스 API ​

축클래스필수
베이스ds-btn✅
변형ds-btn--solid · --line · --icon · (text는 ds-btn-text 패밀리)✅ (1개)
색변형별 팔레트 (색)✅ (1개)
크기solid/line: --xs --sm --md --ml --lg --xl · text: ds-btn-text--sm/md/lg/xl · icon: --xs --sm --md --lg (글리프↔호버박스 ×1.5)✅
모서리ds-r-8 · ds-r-12 · ds-r-16 · ds-r-999 (+ 되돌리기용 ds-r-4) (모서리)— (기본 4px)
옵션ds-btn--full(너비 100%) · ds-btn--bg(line 흰색 채움)—

Text 변형은 별도 클래스 패밀리예요

text 변형은 폰트 스케일이 달라서 ds-btn--text가 아니라 ds-btn-text 클래스 세트를 써요. 베이스 ds-btn-text + 크기 + 색 + 굵기 ds-fw-regular/medium/semibold/bold 로 조합해요.

아이콘 버튼 (icon) ​

원형, 아이콘 전용이에요. 버튼 박스가 곧 원형 호버 영역이고, 글리프의 1.5배 크기예요(--sm이면 글리프 16 / 호버 박스 24). 사이즈는 --xs/--sm/--md/--lg(크기), 기본 --sm. title을 주면 싱글톤 툴팁이 자동으로 붙어요.

html
<button class="ds-btn ds-btn--icon ds-btn--dark ds-btn--sm" title="수정" aria-label="수정"><span class="ds-ico" aria-hidden="true" x-html="$icon('ic-edit')"></span></button>

지원 색은 색상 표의 Icon 항목을 참고하세요. Playground에서 Icon을 선택해 확인할 수 있어요. 아이콘 슬롯의 크기는 사이즈 클래스가 CSS로 잡아요. 아이콘은 레지스트리에서 가져오고 .ds-ico 래퍼를 사용하세요.

좌우 아이콘 (leading / trailing) ​

solid·line·text는 라벨 좌우에 아이콘을 넣을 수 있어요(Figma left/right 슬롯). 라벨 <span> 앞/뒤에 .ds-ico 래퍼를 형제로 두고 $icon으로 레지스트리 아이콘을 넣어요 — 간격(gap)과 아이콘 크기는 사이즈 클래스가 CSS로 잡아요. 별도 클래스나 gap-*·w-* 유틸을 붙이지 마세요.

html
<!-- leading(왼쪽) -->
<button class="ds-btn ds-btn--solid ds-btn--primary ds-btn--md">
  <span class="ds-ico" aria-hidden="true" x-html="$icon('ic-plus')"></span><span>추가</span>
</button>

<!-- trailing(오른쪽) -->
<button class="ds-btn ds-btn--line ds-btn--dark ds-btn--md">
  <span>다음</span><span class="ds-ico" aria-hidden="true" x-html="$icon('ic-arrow-right')"></span>
</button>

<!-- 좌우 동시 + text 변형 -->
<button class="ds-btn-text ds-btn-text--primary ds-btn-text--md">
  <span class="ds-ico" aria-hidden="true" x-html="$icon('ic-filter')"></span><span>필터</span><span class="ds-ico" aria-hidden="true" x-html="$icon('ic-chevron-down')"></span>
</button>

사이즈마다 gap × 아이콘 박스가 묶여 있어요(Figma button/solid 9010-10531 · line 9017-10430 · text 9036-1306). solid·line은 같은 값이고, text만 폰트 스케일에 맞춰 gap이 좁아요.

토큰solid · line gap / 아이콘text gap / 아이콘
--xs (text는 기본)2px / 14px2px / 14px
--sm4px / 18px2px / 18px
--md6px / 20px2px / 20px
--ml6px / 20px— (text엔 ml 없음)
--lg8px / 24px4px / 24px
--xl8px / 24px4px / 24px

아이콘만 필요하면 icon 변형

라벨 없이 아이콘 하나만 쓰는 표·툴바 액션은 좌우 슬롯이 아니라 원형 아이콘 버튼(ds-btn--icon)을 쓰세요. 좌우 아이콘은 라벨이 있는 버튼의 보조 표식이에요.

레지스트리 아이콘과 직계 래퍼를 사용하세요

아이콘을 직접 SVG로 작성하지 마세요. .ds-btn--solid > .ds-ico처럼 버튼의 직계 .ds-ico 래퍼를 사이즈 클래스가 처리합니다. 라벨과 같은 레벨에 두고, 이름은 레지스트리에 존재하는 값을 사용하세요. 앱 HTML에서는 $icon(), TypeScript에서는 getIcon()을 사용합니다. 칸(슬롯) 크기만 정하고 아이콘 안쪽 여백은 강제하지 않습니다.

조합 예시 ​

조합 예시 — 소속 칩 (.ds-member-chip) ​

버튼 변형을 조합한 실제 컴포넌트예요. pill 컨테이너 .ds-member-chip 안에 팀·역할 세그먼트(.ds-member-seg--team/--role)와 제거 아이콘(.ds-member-remove)을 담고, 바깥에 ds-btn-text(muted) "소속 추가" 버튼을 둬요. 표는 기본(small), 모달은 .ds-member-shell--lg(large) 두 사이즈예요. 추가 버튼의 라벨 노출은 공간과 맥락에 따라 달라요 — 표(small)에서 칩이 이미 있으면 + 아이콘만(라벨은 title 툴팁), 칩이 하나도 없는 빈 상태거나 **여유 있는 모달(large)**에서는 "+ 소속 추가" 라벨을 함께 노출해요.

small 22
표 기본
·
large 40
모달 · --shell--lg
·
html
<!-- small (표): 기본 .ds-member-shell / large (모달): + .ds-member-shell--lg -->
<div class="ds-member-shell">
  <span class="ds-member-chip">
    <button class="ds-member-seg ds-member-seg--team"><span class="truncate">개발 1팀</span></button>
    <span class="ds-member-sep">·</span>
    <button class="ds-member-seg ds-member-seg--role"><span class="truncate">일반 직원</span></button>
    <button class="ds-member-remove" aria-label="제거">&times;</button>
  </span>
  <!-- 추가 버튼 라벨: 표에 칩이 있으면 아이콘만 (라벨은 title 툴팁) -->
  <!-- 빈 상태(<span x-show="memberships.length === 0">소속 추가</span>)와 모달(large)에서는 라벨을 함께 노출 -->
  <button class="ds-btn-text ds-btn-text--sm" title="소속 추가">
    <span class="ds-ico" aria-hidden="true" x-html="$icon('ic-plus')"></span>
  </button>
</div>

위계는 팀=--team(진한 라벨색) · 역할=--role(회색), 강조는 채움이 아니라 색·굵기 축으로. 실제 인라인 편집(팀/역할 피커)은 ds-menu를 참조하세요.

확장성 — 고정된 한 벌이 아니에요 ​

이 조합은 조각을 골라 쓰는 구조예요. 단일 버튼처럼 "이 마크업 그대로"가 아니라, 화면 맥락에 맞춰 아래 축으로 늘이고 줄이세요.

축쓸 수 있는 형태
세그먼트 클릭기존 값이 있으면 그 값을 보여주고 클릭 시 변경(피커 오픈) · 값이 없으면 빈 상태로 두고 클릭 시 새로 지정 — 즉 세그먼트는 표시와 입력을 겸해요
(+) 추가 버튼항목을 늘릴 수 있는 화면에만. 단일 값만 갖는 맥락(하나만 고르는 필드)에서는 없어도 돼요
(×) 제거 버튼지울 수 있을 때만. 필수 값이거나 읽기 전용이면 빼세요
읽기 전용두 버튼을 모두 빼면 값 표시 전용 칩이 돼요

조각을 뺄 때도 남는 조각의 클래스는 그대로 두세요(ds-member-chip / --team / --role). 빠진 자리를 채우려고 여백·크기를 손보지 말고, 필요한 조각만 렌더하면 shell이 정렬을 맞춰줘요.

조합 예시 2 — 리스트 카드 (.ds-list-card) ​

표로 담기엔 열이 적고 「펼쳐서 상세」가 필요한 목록용 카드예요. 헤더 행에 이름 · 구분선 · 설명 · 상태 라벨을 한 줄로 놓고, 우측에 아이콘 버튼을 나란히 둬요 — 카드 자체는 색·크기 축을 갖지 않고 버튼을 조합해서 쓰는 껍데기예요.

재직증명서현재 재직 중임을 증명하는 문서
퇴직증명서비활성퇴직 사실을 증명하는 문서
조각클래스역할
목록ds-list-card-group카드 세로 나열(gap 8px)
카드ds-list-card테두리·라운드. 상태 is-expanded · is-inactive
헤더 행ds-list-card__header클릭 영역. hover/pressed 틴트 포함
좌측 묶음ds-list-card__info이름·구분선·설명·라벨을 한 줄로
이름 / 구분선 / 설명__name · __divider · __desc넘치면 말줄임
우측 액션ds-list-card__actions아이콘 버튼들을 담는 자리
셰브론ds-list-card__chevron회전만 담당(is-flipped). 버튼 스타일은 ds-btn--icon
펼친 상세ds-list-card__body안쪽 내용은 화면마다 다름 — 여백만 정해요

액션은 필요한 것만

세 버튼이 고정 세트가 아니에요. 수정만 있어도, 셰브론만 있어도 됩니다(위 두 번째 카드처럼). 빠진 자리를 채우려고 여백을 손보지 말고 필요한 버튼만 렌더하면 __actions가 정렬을 맞춰줘요.

표(Table 1)와 헷갈리지 마세요

열이 3개 이상이고 값을 세로로 비교해야 하면 Table 1이 맞아요. 리스트 카드는 한 줄 요약 + 펼쳐서 상세가 필요할 때예요. 목록이 길어지면 카드 목록도 스크롤 박스(.ds-table-scroll)에 넣어 상단 액션 줄을 고정하세요.

사용처: admin 역할 관리(권한 pill을 펼쳐 보여줌), 문서 양식 › 문서 제출 유형(양식 미리보기를 펼쳐 보여줌).

조합 예시 3 — 선택 카드 (.ds-option-card) ​

여러 액션 중 하나를 고르는 2줄(제목 + 보조설명) 카드예요. 카드 전체가 하나의 클릭 대상(=액션 실행)이라, 실제 ds-btn 은 아니지만 아웃라인·폰트·hover·pressed 를 ds-btn--line(normal 색)과 1:1로 맞춘 껍데기예요. 버튼은 단일 라인이라 "제목+설명 2줄"이 안 들어가서 별도 카드로 두되, 인터랙션은 버튼과 똑같이 읽히게 했어요.

크기 · 아이콘 · 배치를 클릭해서 바로 비교하세요. 두 크기가 항상 같은 조건으로 나란히 렌더돼요.

설명
배치
compact (기본)패딩 8/12 · 제목 13
확대 --lg패딩 12/16 · 제목 14

실제 .ds-option-card 클래스를 그대로 렌더해요 — 마우스 hover · click-hold pressed · disabled 토글이 라이브로 동작합니다. 크기는 2종뿐(compact / --lg)이고 패딩과 제목 크기만 달라요 — 테두리·라운드·hover·보조설명(12px)은 동일. 중간 크기를 새로 만들지 마세요. 제목줄 아이콘은 선택 — 카드마다 다른 아이콘일 때만 켜세요(전부 같은 아이콘이면 정보량이 0이라 끕니다). 그리드 배치에서 설명 길이를 여러 줄로 바꿔 보면, 카드 높이는 줄 단위로 맞춰지되 내용은 위로 정렬되는 걸 확인할 수 있어요(min-height 금지 이유).

html
<button class="ds-option-card" @click="exportCsv()">
  <div class="ds-option-card__title">CSV — 현재 보이는 행</div>
  <div class="ds-option-card__desc">검색 / 필터로 좁힌 결과만 내보냅니다</div>
</button>
조각클래스역할
카드ds-option-card테두리(line-normal)·라운드 4px·13px/500·hover·pressed. 카드 전체가 클릭 대상
제목ds-option-card__title첫 줄. 카드 base(13px/500 · label-normal)를 그대로 씀
보조설명ds-option-card__desc둘째 줄. 12px(--text-b6) · label-muted
제목줄 아이콘 (선택)ds-option-card__icon제목 왼쪽 16px(lg 18px) 슬롯. label-muted. 넣을 땐 제목에 has-icon 을 함께 단다(그때만 제목이 flex 행)

크기 2종 ​

크기클래스패딩 · 제목언제
compact (기본)ds-option-card8/12px · 13px세로로 쌓는 목록형 선택지. 한 줄 설명, 모달 하단에 여러 개
확대ds-option-card--lg12/16px · 14px그리드 갤러리형 카드. 설명이 2~3줄, 카드가 그 화면의 주요 콘텐츠

늘어나는 건 패딩과 제목 크기뿐이에요. 테두리·라운드·hover·pressed·보조설명(12px)은 두 크기가 같습니다. 그 사이 값을 새로 만들지 마세요 — 둘 중 가까운 쪽을 고릅니다. 두 크기를 나란히 놓고 보려면 위 탐색기의 배치 · 설명 · 아이콘 토글을 쓰세요.

html
<!-- 확대 + 제목줄 아이콘 -->
<button class="ds-option-card ds-option-card--lg" @click="applyTemplate(tpl)">
  <div class="ds-option-card__title has-icon">
    <span class="ds-option-card__icon ds-ico" x-html="$icon('ic-file-text')"></span>
    <span x-text="tpl.name"></span>
  </div>
  <div class="ds-option-card__desc" x-text="tpl.description"></div>
</button>

아이콘은 "끄는 게 기본"이에요

아이콘 슬롯은 선택입니다. 카드마다 같은 아이콘이 반복될 뿐이면(예: 템플릿 목록의 문서 아이콘) 정보량이 0이라 넣지 마세요. 카드마다 다른 아이콘이 붙어서 훑을 때 분류가 되는 경우에만 씁니다. 슬롯을 비우면 제목은 원래대로 한 줄 텍스트로 렌더돼요(빈 슬롯을 위한 자리 확보 같은 건 없습니다).

카드 높이는 고정하지 않습니다. 내용만큼 늘어나고, 그리드에 깔면 같은 줄끼리 자동으로 높이가 맞아요(min-height를 박으면 설명이 긴 카드가 잘립니다).

리스트 카드(.ds-list-card)와 헷갈리지 마세요

.ds-list-card 는 "한 줄 요약 + 펼쳐서 상세"(레코드 + 우측 아이콘버튼 + 펼침)예요. .ds-option-card 는 "펼침 없이 카드 전체가 하나의 액션" — 라디오/메뉴처럼 여러 선택지 중 하나를 고를 때 씁니다. 액션이 한 줄로 충분하면 카드 대신 ds-btn--line 버튼을, 목록/펼침이 필요하면 ds-list-card 를 쓰세요.

사용처: hr-system 다운로드(내보내기) 모달의 CSV / XLSX / JSON 선택지.

마이그레이션 매핑 (old → new) ​

레거시 버튼은 두 갈래로 남아 있어요: ① bg-orange-500 px-3 py-1.5 … 같은 유틸 즉석 조합, ② ds-btn--secondary·icon-btn 같은 옛 명명 클래스. 둘 다 아래처럼 통합 체계로 바꿔요. 새 코드는 아래 오른쪽 열만 쓰세요.

현재 (레거시)→ 새
ds-btn ds-btn--primary (모달)ds-btn ds-btn--solid ds-btn--dark ds-btn--md (기본 확인), 크리티컬 확인은 ds-btn--lightor
ds-btn ds-btn--secondary (모달 취소)ds-btn ds-btn--solid ds-btn--white-bk ds-btn--md
유틸 solid bg-orange-500 px-3 py-1.5 text-white roundedds-btn ds-btn--solid ds-btn--primary ds-btn--md
유틸 line border border-red-600 text-red-600 px-3 py-1.5ds-btn ds-btn--line ds-btn--red ds-btn--md
ds-btn-text ds-btn-text--primary ds-btn-text--sm(그대로 유지 — text 패밀리가 정식)
icon-btn (28px)ds-btn ds-btn--icon ds-btn--dark (24px)
icon-btn icon-btn--danger (28px)ds-btn ds-btn--icon ds-btn--red (24px — 색은 #e63224로 동일)
토글 칩 (:class 조건부 bg-orange-500 / bg-orange-600)버튼 아님 → 선택 칩으로 별도 처리
마이그레이션 진행 메모

유틸 즉석 조합(bg-orange-500 …, border …)과 옛 명명 클래스는 앱별로 점진 치환 중이에요. DS 연결된 앱부터 옮기고, button[class*="bg-"] … !important override 블록과 앱 main.css 복제본을 걷어내요. 자세한 순서는 docs/packages/reference/button-system-spec.md §12를 참고하세요.