Buttons
Hereby 버튼은 하나의 통합 체계예요. 단일 베이스 ds-btn 위에 변형 × 색 × 크기 modifier만 조합해서 만들어요. "모달 버튼" 처럼 맥락별로 따로 만든 클래스는 없어요 — 맥락은 사용 규칙으로 풀고, 버튼 자체는 변수 조합이에요.
<button class="ds-btn ds-btn--solid ds-btn--primary ds-btn--md">저장</button>
<!-- 베이스 변형 색 크기 -->상태(hover·focus·pressed·disabled)는 마크업이 필요 없어요. 전부 CSS에서 공통 인터랙션 오버레이로 처리돼요. hover:bg-* 같은 유틸을 따로 붙이지 마세요.
기본 ds-btn과 다르게 쓰이는 조합이 있어요
버튼 변형을 여러 개 조합해 만든 실제 컴포넌트(소속 칩·리스트 카드)는 단일 버튼과 쓰임새가 달라요 — 값을 보여주면서 동시에 조작하는 역할을 하고, 맥락에 따라 구성 조각이 빠지기도 해요. 단일 버튼 규칙만 보고 만들면 어긋나니, 이런 조합을 만들기 전에 아래 예시를 먼저 보세요.
출처
구현: packages/static/styles/design-system.css 의 Hereby Button System (unified) 블록. 설계 narrative: docs/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).
규칙 (강제) · 코딩 에이전트용 — 사람은 접힌 채로 두어도 됩니다
버튼 강제 규칙. 상세·미리보기·전체 조합표는 아래 본문.
적용 범위 — 신규 코드 기준. 아래 규칙은 새로 작성·수정하는 버튼에 강제됩니다. 앱에 남아 있는 레거시 버튼(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(기본) /--lg2종뿐이고(크기 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"]). Tailwindrounded-*를 줘도 덮어써집니다. 제외 명단:.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--*)를 쓰면 걸리지 않습니다.- 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 보더는
#c1c1c1solid가 아니라 40% 불투명도(var(--ds-tint-light-40)=rgba(193,193,193,0.4), Figma9032:2225). 직접#c1c1c1로 칠하지 마세요. !important금지 — 앱main.css는@import뒤에 오므로 소스 순서로 이미 이깁니다. 캐스케이드가 안 먹으면!important대신 셀렉터/엘리먼트를 바꾸세요.- 버튼 CSS 복제 주의: 버튼은 정본(
design-system.css)에만 있지만, 같은 계열인.ds-page-btn(페이지네이션)은 앱 5곳에 복제돼 있습니다. 상세:.claude/rules/frontend/design-system-architecture.md.
버튼 탐색기
변형을 클릭으로 골라 모든 색 × 크기(52 → 20px) 를 한눈에 보세요. 실제 design-system.css 클래스가 그대로 붙어 있어서 hover·focus·pressed·disabled가 라이브로 동작해요.
통합 체계 ds-btn + --변형 + --색 + --크기를 실 design-system.css 클래스로 렌더. 사이즈 52 → 20px 내림차순. 상태는 버튼에서 직접 — 마우스 hover · Tab focus · click-hold pressed. disabled 토글. line은 bg 채움으로 보더만 ↔ 흰색 채움 전환. icon은 Figma Button/Icon — 원형, 글리프↔호버박스 ×1.5(12↔18·16↔24·20↔30·24↔36), 4색. 왼쪽/오른쪽 아이콘 토글로 라벨 좌우 아이콘 슬롯을 켜고 끄며 확인 — 사이즈마다 간격·아이콘 크기가 Figma값(xs14·sm18·md/ml20·lg/xl24)으로 자동 적용돼요. 모서리: 기본 4 · ds-r-8 · ds-r-12 · ds-r-16 · ds-r-999(알약). 크기·색과 독립된 축이라 어떤 조합에도 얹을 수 있어요(원형 icon 변형은 999 고정이라 제외). 색: solid = Figma 8(primary·gradient·dark·assistive·muted·white-bk·white-or·lightor) · line = 7 + secondary 팔레트(red·green·blue·purple·yellow·aquablue, --color-secondary-* Figma 정확값).
라이브 미리보기
대표 조합을 변형별로 모았어요. 마우스 hover·Tab focus·click-hold pressed로 실동작을 확인하세요.
한 베이스 ds-btn 에 변형 × 색 × 크기 modifier만 붙여요. 상태는 마크업이 필요 없어요 — 마우스 hover · Tab focus · click-hold pressed로 공통 오버레이(hover 20% · focus 30% · pressed 10%)가 그대로 보여요.
Solid 주요 확정 액션 (저장·등록) — 대표 32px(md)
Line 보조·위험 액션 · 색으로 의미 구분 — 대표 32px(md)
Text 인라인 CTA · 링크 · 모달 닫기 (별도 폰트 스케일)
Icon 표·툴바 인라인 액션 · 24px 원형 · title 툴팁
클래스 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-btn-text--sm/md/lg/xl + 색 ds-btn-text--primary/dark/assistive/white + 굵기 ds-fw-regular/medium/semibold/bold 로 조합해요.
변형 (variation)
같은 색 modifier라도 변형마다 다르게 표현돼요.
| Solid | Line | Text | Icon | |
|---|---|---|---|---|
| 형태 | 채움 + 흰 글자 | 보더 + 글자색, 투명 bg | 글자만 | 24px 원형 아이콘 |
| 채움 옵션 | (기본 채움) | ds-btn--bg = 흰색 채움 | — | — |
| 좌우 아이콘 | ✅ leading/trailing | ✅ | ✅ | — (아이콘 자체가 본체) |
| 용도 | 주요 확정 | 보조·위험 | 인라인 CTA·링크 | 표·툴바 아이콘 액션 |
조합 예시 — 소속 칩 (.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)**에서는 "+ 소속 추가" 라벨을 함께 노출해요.
표 기본
모달 ·
--shell--lg<!-- 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="제거">×</button>
</span>
<!-- 추가 버튼 라벨: 표에 칩이 있으면 아이콘만 (라벨은 title 툴팁) -->
<!-- 빈 상태(<span x-show="memberships.length === 0">소속 추가</span>)와 모달(large)에서는 라벨을 함께 노출 -->
<button class="ds-btn-text ds-btn-text--sm" title="소속 추가">
<svg class="shrink-0 ds-member-add-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M12 4v16m8-8H4" stroke-linecap="round"/></svg>
</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줄"이 안 들어가서 별도 카드로 두되, 인터랙션은 버튼과 똑같이 읽히게 했어요.
크기 · 아이콘 · 배치를 클릭해서 바로 비교하세요. 두 크기가 항상 같은 조건으로 나란히 렌더돼요.
실제 .ds-option-card 클래스를 그대로 렌더해요 — 마우스 hover · click-hold pressed · disabled 토글이 라이브로 동작합니다. 크기는 2종뿐(compact / --lg)이고 패딩과 제목 크기만 달라요 — 테두리·라운드·hover·보조설명(12px)은 동일. 중간 크기를 새로 만들지 마세요. 제목줄 아이콘은 선택 — 카드마다 다른 아이콘일 때만 켜세요(전부 같은 아이콘이면 정보량이 0이라 끕니다). 그리드 배치에서 설명 길이를 여러 줄로 바꿔 보면, 카드 높이는 줄 단위로 맞춰지되 내용은 위로 정렬되는 걸 확인할 수 있어요(min-height 금지 이유).
<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 |
크기 2종
| 크기 | 클래스 | 패딩 · 제목 | 언제 |
|---|---|---|---|
| compact (기본) | ds-option-card | 8/12px · 13px | 세로로 쌓는 목록형 선택지. 한 줄 설명, 모달 하단에 여러 개 |
| 확대 | ds-option-card--lg | 12/16px · 14px | 그리드 갤러리형 카드. 설명이 2~3줄, 카드가 그 화면의 주요 콘텐츠 |
늘어나는 건 패딩과 제목 크기뿐이에요. 테두리·라운드·hover·pressed·보조설명(12px)은 두 크기가 같습니다. 그 사이 값을 새로 만들지 마세요 — 둘 중 가까운 쪽을 고릅니다. 두 크기를 나란히 놓고 보려면 위 탐색기의 배치 · 설명 · 아이콘 토글을 쓰세요.
<!-- 확대 + 제목줄 아이콘 -->
<button class="ds-option-card ds-option-card--lg" @click="applyTemplate(tpl)">
<div class="ds-option-card__title">
<span class="ds-option-card__icon" 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 선택지.
색 (color)
색은 변형마다 쓸 수 있는 팔레트가 달라요.
Solid
ds-btn--primary · --dark · --assistive · --muted · --white-bk · --white-or · --lightor · --gradient
Line
ds-btn--primary · --dark · --assistive · --muted · --normal · --white · --red · --green · --blue · --purple · --yellow · --aquablue (+ ds-btn--bg = 흰색 채움)
Icon
기본 dark, 그리고 ds-btn--white · --muted · --primary.
secondary 의미색도 solid·line과 똑같이 확장돼요: ds-btn--red · --green · --blue · --purple · --yellow · --aquablue. 아이콘 글리프 색이 해당 secondary 토큰으로 바뀌어요(예: 위험한 인라인 삭제 아이콘 = ds-btn--red).
Text
ds-btn-text--primary · --dark · --assistive · --white (기본은 muted).
레거시 색은 secondary로 매핑해요
green·purple·blue·yellow·aquablue는 Figma secondary 공식 색이라 정상이에요. 단 gray-*→neutral, amber-*→yellow, emerald/indigo/teal는 해당 secondary로 바꿔야 해요. 자세한 건 색 토큰 컨벤션을 참고하세요.
크기 (size)
solid·line은 높이 기준 6단계예요.
| token | 높이 |
|---|---|
ds-btn--xs | 20px |
ds-btn--sm | 28px |
ds-btn--md | 32px (기본) |
ds-btn--ml | 40px |
ds-btn--lg | 48px |
ds-btn--xl | 52px |
모바일에서는 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--xs | 12px | 18px |
ds-btn--sm | 16px | 24px (기본) |
ds-btn--md | 20px | 30px |
ds-btn--lg | 24px | 36px |
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-* 를 하나 얹으세요. 변형·색·크기와 독립된 축이라 어떤 조합에도 붙일 수 있어요.
<button class="ds-btn ds-btn--solid ds-btn--primary ds-btn--md ds-r-12">저장</button>
<!-- 모서리 12px -->| 클래스 | 모서리 |
|---|---|
| (없음) | 4px (기본) |
ds-r-4 | 4px — 기본값과 같아요. 아래 「컨테이너 상속」으로 다른 모서리가 걸린 안에서 이 버튼만 기본으로 되돌릴 때만 씁니다 |
ds-r-8 | 8px |
ds-r-12 | 12px |
ds-r-16 | 16px |
ds-r-999 | 999px — 알약(pill) |
버튼 탐색기의 모서리 컨트롤로 색·크기 전 조합에 실시간으로 적용해볼 수 있어요.
컨테이너에 얹으면 그 안의 버튼이 전부 바뀌어요 — 레거시 버튼까지
ds-r-* 는 --ds-btn-radius 변수만 세팅하고, 버튼의 border-radius 선언이 그 변수를 읽어요(굵기 축 ds-fw-* 와 같은 구조). CSS 변수는 상속되므로 버튼을 감싼 컨테이너에 얹으면 그 안의 버튼에 일괄 적용돼요.
여기서 "그 안의 버튼"은 DS 버튼만이 아니라 <button> 전부예요 — 날 유틸(bg-*/border-*)로 만든 레거시 버튼도 전역 강제 규칙을 통해 같은 변수를 읽기 때문이에요. 그래서 툴바·카드에 얹으면 의도하지 않은 주변 버튼까지 모서리가 바뀝니다.
- 기본은 버튼 엘리먼트에 직접 얹으세요. 컨테이너 적용은 그 안의 버튼을 전부 확인한 뒤에만.
- 컨테이너 적용 중 특정 버튼만 되돌리려면 그 버튼에
ds-r-4(기본값)를 얹으세요. - 화면 전체를 한 번에 바꿔보는 실험(예: 앱 전체를 12px로 미리보기)에는 이 상속이 유용해요 —
<body>에 얹으면 됩니다. - 변수를 읽지 않는 버튼 외 요소(카드·입력 등)는 영향이 없어요.
원형 아이콘 버튼은 제외예요
ds-btn--icon · ds-btn--circle 은 원형(999px)이 정체성이라 이 축을 무시해요.
상태 (state)
마크업 없이 CSS가 처리해요. 공통 인터랙션 오버레이를 얹는 단일 모델이에요.
| 상태 | solid | line · 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로 모든 버튼에 자동 적용돼요(키보드 접근성).
아이콘 버튼 (icon)
원형, 아이콘 전용이에요. 버튼 박스가 곧 원형 호버 영역이고, 글리프의 1.5배 크기예요(--sm이면 글리프 16 / 호버 박스 24). 사이즈는 --xs/--sm/--md/--lg(크기), 기본 --sm. title을 주면 싱글톤 툴팁이 자동으로 붙어요.
<button class="ds-btn ds-btn--icon ds-btn--dark ds-btn--sm" title="수정" aria-label="수정"><svg>…</svg></button>색은 dark(기본)·ds-btn--white·ds-btn--muted·ds-btn--primary 4종에 secondary 의미색 ds-btn--red·--green·--blue·--purple·--yellow·--aquablue를 더해 10종이에요(색). svg 크기는 사이즈 클래스가 CSS로 잡으니 마크업은 그대로 두면 돼요.
좌우 아이콘 (leading / trailing)
solid·line·text는 라벨 좌우에 아이콘을 넣을 수 있어요(Figma left/right 슬롯). 라벨 <span> 앞/뒤에 <svg>를 형제로 두면 끝이에요 — 간격(gap)과 아이콘 크기는 사이즈 클래스가 CSS로 잡아요. 별도 클래스나 gap-*·w-* 유틸을 붙이지 마세요.
<!-- leading(왼쪽) -->
<button class="ds-btn ds-btn--solid ds-btn--primary ds-btn--md">
<svg>…</svg><span>추가</span>
</button>
<!-- trailing(오른쪽) -->
<button class="ds-btn ds-btn--line ds-btn--dark ds-btn--md">
<span>다음</span><svg>…</svg>
</button>
<!-- 좌우 동시 + text 변형 -->
<button class="ds-btn-text ds-btn-text--primary ds-btn-text--md">
<svg>…</svg><span>필터</span><svg>…</svg>
</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 / 14px | 2px / 14px |
--sm | 4px / 18px | 2px / 18px |
--md | 6px / 20px | 2px / 20px |
--ml | 6px / 20px | — (text엔 ml 없음) |
--lg | 8px / 24px | 4px / 24px |
--xl | 8px / 24px | 4px / 24px |
아이콘만 필요하면 icon 변형
라벨 없이 아이콘 하나만 쓰는 표·툴바 액션은 좌우 슬롯이 아니라 원형 아이콘 버튼(ds-btn--icon)을 쓰세요. 좌우 아이콘은 라벨이 있는 버튼의 보조 표식이에요.
svg 직접 자식만 사이징돼요
gap·크기 규칙은 .ds-btn--solid > svg처럼 직계 <svg> 를 기준으로 해요. 아이콘을 <i>·<span> 래퍼로 감싸면 크기가 안 잡혀요 — <svg>를 라벨과 같은 레벨에 형제로 두세요. 칸(슬롯) 크기만 정하고 안쪽 여백은 강제하지 않아요 — 아이콘마다 자체 여백이 달라서, 드롭한 <svg>가 칸을 그대로 채워요.
사용 규칙 (맥락 → 조합)
버튼은 변수 조합이고, 맥락은 아래 규칙으로 정해요.
| 맥락 | 조합 |
|---|---|
| 모달·얼럿 푸터 권장 컨펌 | solid · dark · md |
| 모달·얼럿 푸터 크리티컬 컨펌 | solid · lightor · md |
| 모달·얼럿 푸터 취소·닫기 | solid · white-bk · md |
| 표/툴바 "추가" | solid · primary · sm |
| 인라인 링크 CTA | text · 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(승인)은 모달 밖 인라인·툴바·워크플로에 남습니다. 전체 규칙·예시는 모달 → 푸터 버튼 강조를 참고하세요.
마이그레이션 매핑 (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--primary ds-btn--md |
ds-btn ds-btn--secondary (취소) | ds-btn ds-btn--line ds-btn--dark ds-btn--md |
유틸 solid bg-orange-500 px-3 py-1.5 text-white rounded | ds-btn ds-btn--solid ds-btn--primary ds-btn--md |
유틸 line border border-red-600 text-red-600 px-3 py-1.5 | ds-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/reference/button-system-spec.md §12를 참고하세요.