Groo Design

Button

명확한 액션을 쉽게 실행하게 돕는 기본 인터랙션 컴포넌트. 폼 제출 · 대화상자 닫기 · 다음 단계처럼 사용자가 무엇을 하는지 분명한 동작에 쓴다.

구조는 당근 SEED Action Button(Apache-2.0)을 따른다 — 크기 4 · 배치 2 · 변형 7 · 상태 6. 값은 groo 토큰이고, SEED 와 다른 자리는 맨 아래 "SEED 와 다른 점" 에 적었다(2026-09-30 사용자 결정).

수치 원본은 button.yaml 이다. 아래 수치 표는 사이트가 그 파일로 그리고, GitHub 에서는 표 자리가 그 파일로 가는 링크로 보인다. 그림 자리([그림: …])도 같다 — 사이트는 그 자리에 스펙 값으로 그린 버튼과 화면 예시를 둔다.

변형 일곱 가지 — 위는 라이트, 아래는 다크

직접 골라 보기

변형 · 크기 · 배치 · 상태를 고르면 스펙대로 그린 버튼과 그 코드가 바뀐다. 버튼은 실제로 눌러 볼 수 있다(호버 · 누름 · Tab 포커스).

변형 variant
크기 size
배치 layout
상태
너비
모드
브랜드
import { Button } from "@/components/ui/button"

<Button>저장</Button>

Anatomy

ⓑⓒ라벨ⓓⓐⓔ
ⓐ Containerⓑ Prefix Iconⓒ Labelⓓ Suffix Iconⓔ Focus ring
Button 은 Label 을 감싼 Container 로 이뤄지고, 앞 · 뒤 아이콘을 가질 수 있다 — 부위를 보이려고 앞 · 뒤 아이콘을 함께 그렸다(실제로는 함께 쓰지 않는다)
부위설명
ⓐ Container배경 · 테두리 · 모양. 글자와 아이콘은 이 색(currentColor)을 따른다.
ⓑ Prefix Icon라벨 앞 아이콘 — 동작의 뜻을 돕는다. 선택.
ⓒ Label무엇을 하는지 동사로. 한 줄(white-space: nowrap).
ⓓ Suffix Icon라벨 뒤 아이콘 — chevron 처럼 동작을 돕는다. 선택.
ⓔ Focus ring키보드 포커스에만 2px 링 · 2px 띄움(v106).

부위

부위설명
root버튼 상자(Container) — 배경 · 테두리 · 모양, 글자 · 아이콘 색(currentColor)
label글자(Label)
prefixIcon앞 아이콘(Prefix Icon) — 동작의 뜻을 돕는다
suffixIcon뒤 아이콘(Suffix Icon) — chevron 처럼 동작을 돕는다
icon아이콘만 있는 버튼의 아이콘
progressCircle로딩 원
focusRing키보드 포커스 링(웹 · 앱 — 키보드로 옮길 때만)

Properties

Size

네 가지다. 크기는 이름이 아니라 높이로 고른다.

  • small(36) · medium(40) — 화면 안에서 두루 쓴다. 모달 footer 는 small.
  • large(48) — CTA. 모바일 하단 고정 버튼 · 모바일 모달 footer.
  • xsmall(32) — 좁은 자리에서 쓰는 알약 모양.
xsmall높이 32
small높이 36
medium높이 40
large높이 48
크기 네 가지 — 높이 32 · 36 · 40 · 48
144라벨1432
xsmall아이콘 14 · 글자 13px · 모서리 알약
144라벨1436
small아이콘 14 · 글자 14px · 모서리 8px
166라벨1640
medium아이콘 16 · 글자 14px · 모서리 8px
208라벨2048
large아이콘 22 · 글자 18px · 모서리 12px
크기마다의 치수 — 좌우 여백 · 아이콘과 글자 사이 · 높이

누르는 영역은 보이는 크기와 따로 가로 · 세로 44 까지 넓힌다(v106 — 44 는 반드시).

xsmall보이는 32 · 누르는 44
small보이는 36 · 누르는 44
medium보이는 40 · 누르는 44
보이는 크기가 44 보다 작아도 누르는 영역(분홍)은 44 다

크기

size높이모서리로딩 원로딩 원 두께터치 (AA · AAA)설명
xsmall32px — 최소 높이 — 글이 상자보다 크면(큰 글자) 상자가 글을 따라 늘어난다(2026-10-22 사용자 결정 — 버튼 큰 글자 A, SEED action-button root.minHeight)radius-full 9999px14px2px✓ · ✓32 — 좁은 자리, 알약 모양
small36px — 최소 높이 — 글이 상자보다 크면(큰 글자) 상자가 글을 따라 늘어난다(2026-10-22 사용자 결정 — 버튼 큰 글자 A, SEED action-button root.minHeight)radius-r2 8px14px2px✓ · ✓36 — 화면 안 범용, 모달 footer
medium (기본)40px — 최소 높이 — 글이 상자보다 크면(큰 글자) 상자가 글을 따라 늘어난다(2026-10-22 사용자 결정 — 버튼 큰 글자 A, SEED action-button root.minHeight)radius-r2 8px16px2px✓ · ✓40 — 화면 안 범용, 아이콘만 있는 버튼의 기본
large48px — SEED 52 — groo 는 48(사용자 결정). 최소 높이 — 글이 상자보다 크면(큰 글자) 상자가 글을 따라 늘어난다(2026-10-22 사용자 결정 — 버튼 큰 글자 A, SEED action-button root.minHeight)radius-r3 12px18px2px✓ · ✓48 — CTA(모바일 하단 · 모바일 모달 footer)

글자와 함께 쓸 때(withText):

크기별 여백 · 글자 · 아이콘 — 글자

속성xsmallsmallmedium (기본)large
상하 여백spacing-x1_5 6pxspacing-x2 8pxspacing-x2_5 10pxspacing-x3 12px — (48 − 줄 높이 24) ÷ 2 — SEED 52 는 14
좌우 여백spacing-x3_5 14pxspacing-x3_5 14pxspacing-x4 16pxspacing-x5 20px
간격spacing-x1 4pxspacing-x1 4pxspacing-x1_5 6pxspacing-x2 8px
글자text-t3 13px / 18pxtext-t4 14px / 19pxtext-t4 14px / 19pxtext-t6 18px / 24px
앞 아이콘14px14px16px22px
뒤 아이콘14px14px16px22px

아이콘만 쓸 때(iconOnly):

크기별 여백 · 아이콘 — 아이콘만

속성xsmallsmallmedium (기본)large
너비32px36px40px48px
여백spacing-x1_5 6pxspacing-x2 8pxspacing-x2_5 10pxspacing-x3 12px
아이콘14px16px18px22px

모든 조합에 공통인 값:

공통

부위상태속성값비고
rootenabled커서pointer
rootenabled터치 영역44 × 44보이는 크기와 따로 — 누르는 영역을 가로 · 세로 44 까지 넓힌다(v106). AA ✓ · AAA ✓
rootenabled전환 대상background-color, color
rootenabled전환 시간motion-duration-color-transition 150ms
rootenabled전환 곡선motion-ease-easing cubic-bezier(0.35, 0, 0.35, 1)
rootenabled누름누른 동작이 끝날 때까지 한 번onClick 이 Promise 를 돌려주면 이행 · 거부될 때까지 다음 누름(포인터 · Enter · Space)을 버린다 — 시간 창은 없다. 비동기 동작은 Promise 를 돌려준다(mutateAsync). 돌려주지 않으면 잠그지 않는다. 앱은 Future 가 끝날 때까지. 사용자 결정 2026-10-08
labelenabled글꼴font-sans
labelenabled굵기700
focusRingfocused링 두께2px웹 · 앱 같다 — 앱은 키보드로 옮길 때만 그린다(Flutter FocusHighlightMode.traditional), 손가락으로 누를 때는 없다. 앱 버튼도 포커스를 받아 Tab 순서에 든다(사용자 결정 2026-10-08)
focusRingfocused링 간격2px
focusRingfocused링 색color-stroke-focus-ring #1A1F2E · 다크 #F5F6FA
rootpressed배율세로 2px 축소기준 길이 max(높이, 폭 ÷ 4, 24) 에서 2px — Feedback 의 눌림 피드백(v104)
rootpressed축소 시간motion-duration-pressed-scale 150ms
rootpressed축소 곡선motion-ease-pressed-scale cubic-bezier(0, 0, 0.15, 1)
rootloading커서progress
rootloading누름막음누르기(포인터 · Enter · Space)를 삼켜 두 번 제출을 막는다 — 축소도 없다. aria-busy=true. SEED 는 막지 않는다
labelloading색transparent글자 · 아이콘을 투명하게 해 폭을 그대로 둔다
rootdisabled커서not-allowed포인터 이벤트는 켜 둔다 — 끄면 이 커서가 안 보인다(SEED 와 같다)
rootdisabled배경color-bg-disabled #F5F6FA · 다크 #353B4D
rootdisabled글자·아이콘color-fg-disabled #8A91A0 · 다크 #838997

Layout

라벨과 아이콘의 조합이다 — 글자만 · 앞 아이콘 + 글자 · 글자 + 뒤 아이콘 · 아이콘만.

글자만
앞 아이콘 + 글자
글자 + 뒤 아이콘
아이콘만
배치 네 가지
  • 앞 · 뒤 아이콘을 함께 쓰지 않는다. 앞은 동작의 뜻을 돕고(추가의 +), 뒤는 동작을 돕는다(다음의 chevron).
  • 아이콘은 꼭 필요할 때만 쓴다 — 라벨을 읽기 어려워진다.
  • 아이콘만은 아이콘만으로 뜻을 전해 접근성이 떨어진다. 꼭 필요할 때만 쓰고, 이름(aria-label 또는 옆 Tooltip)을 반드시 단다. 정사각이고, 아이콘 버튼의 보이는 크기 기본은 medium(40 — v106).
  • 상단 바의 아이콘 버튼은 이 Button 이 아니다 — Top Navigation 의 아이콘 버튼(상자 44 = 누르는 영역 · 아이콘 24 · 버튼끼리 붙음)이다(2026-10-04). 본문 안의 아이콘 버튼은 그대로 medium · iconOnly(40 · 18)다.

Variant

일곱 가지다. 화면에서 강조하려는 정도에 따라 고른다.

brandSolid
neutralSolid
neutralWeak
criticalSolid
brandOutline
neutralOutline
ghost
brandSolid
neutralSolid
neutralWeak
criticalSolid
brandOutline
neutralOutline
ghost
변형 일곱 가지 — 라이트는 흰 표면, 다크는 다크 표면 위
변형생김새쓰는 곳
brandSolid브랜드 색 채움서비스의 핵심 액션 하나 — Desk "거래 추가", HR "휴가 신청". 한 화면에 하나
neutralSolid짙은 회색 채움대부분의 CTA — 저장 · 확인 · 다음. 한 화면에 하나
criticalSolid빨강 채움삭제 · 초기화처럼 되돌릴 수 없는 작업의 확정. 주로 Alert Dialog
neutralWeak옅은 회색 채움CTA 를 뺀 대부분의 액션. CTA 옆에서는 보조(취소 · 닫기)
brandOutline테두리 + 브랜드 글자Solid 보다 낮은 위계. Solid 와 함께 쓰지 않고 neutralOutline 과 짝
neutralOutline테두리 + 본문 글자가장 낮은 위계의 보조 액션. Solid 와 함께 쓰지 않고 brandOutline 과 짝
ghost배경 없음메뉴 · 툴바 · 목록 행의 가벼운 액션. 글자색을 바꿀 수 있다(아래)

변형별 색

속성brandSolidneutralSolid (기본)neutralWeakcriticalSolidbrandOutlineneutralOutlineghost
배경color-bg-brand-solid HR #075B3C · Desk #0147ADcolor-bg-neutral-inverted #1A1F2E · 다크 #F5F6FAcolor-bg-neutral-weak #F5F6FA · 다크 #353B4Dcolor-bg-critical-solid #D72323 · 다크 #CC0E17transparenttransparenttransparent
글자·아이콘color-static-white #FFFFFFcolor-fg-neutral-inverted #FFFFFF · 다크 #242938color-fg-neutral #1A1F2E · 다크 #F5F6FAcolor-static-white #FFFFFFcolor-fg-brand HR #075B3C · Desk #0147ADcolor-fg-neutral #1A1F2E · 다크 #F5F6FAcolor-fg-neutral #1A1F2E · 다크 #F5F6FA
테두리————color-stroke-neutral-weak #E5E8EF · 다크 #404757 — SEED stroke.neutral-muted(투명도 있는 검정)의 불투명 짝color-stroke-neutral-weak #E5E8EF · 다크 #404757—
테두리 두께————1px1px—
로딩 원 트랙color-static-white #FFFFFF × 30%color-fg-neutral-inverted #FFFFFF × 30% · 다크 #242938 × 30%color-gray-500 #8A91A0 · 다크 #656B78color-static-white #FFFFFF × 30%color-bg-brand-weak-pressed HR #D6E8DE · Desk #D7E5FCcolor-gray-500 #8A91A0 · 다크 #656B78color-gray-500 #8A91A0 · 다크 #656B78
로딩 원 채움color-static-white #FFFFFFcolor-fg-neutral-inverted #FFFFFF · 다크 #242938color-fg-neutral #1A1F2E · 다크 #F5F6FAcolor-static-white #FFFFFFcolor-stroke-brand-solid HR #075B3C · Desk #0147AD — Progress Circle brand 톤과 같다 — 다크는 밝은 짝(바탕 위 라이트 Desk 8.38 · HR 8.17, 다크 Desk 6.10 · HR 6.07 — 트랙과 Desk 6.59 · 4.73). 2026-10-08 까지 bg-brand-solid 라 다크 바탕 위 1.73 이었다(채움 색은 다크에서도 짙다)color-fg-neutral #1A1F2E · 다크 #F5F6FAcolor-fg-neutral #1A1F2E · 다크 #F5F6FA

ghost 는 글자색을 바꿀 수 있다(SEED ghost 의 color) — 배경 · 누름은 그대로다.

neutral본문 글자(기본)
neutralSubtle흐린 글자
brand브랜드 글자
critical위험 글자
ghost 글자색 네 가지
ghostColor쓰는 곳
neutral(기본)대부분의 ghost
neutralSubtle목록 행 · 툴바의 보조 아이콘 액션, 가장자리 텍스트 버튼(flush)
brand강조하는 가벼운 액션 — 본문 속 "자세히 보기"
critical확인 창을 여는 삭제 — 모달 footer · 시트 바닥 왼쪽. 확정은 확인 창의 criticalSolid

ghost 글자색

조합상태속성값
variant=ghost + ghostColor=neutralSubtleenabled글자·아이콘color-fg-neutral-subtle #62697A · 다크 #A2A8B7
variant=ghost + ghostColor=brandenabled글자·아이콘color-fg-brand HR #075B3C · Desk #0147AD
variant=ghost + ghostColor=criticalenabled글자·아이콘color-fg-critical #D72323 · 다크 #FF8477
size=xsmall + layout=withTextenabled좌우 여백spacing-x3_5 14px
size=xsmall + layout=withTextenabled상하 여백spacing-x1_5 6px
size=xsmall + layout=withTextenabled간격spacing-x1 4px
size=xsmall + layout=withTextenabled글자text-t3 13px / 18px
size=xsmall + layout=withTextenabled앞 아이콘14px
size=xsmall + layout=withTextenabled뒤 아이콘14px
size=xsmall + layout=iconOnlyenabled너비32px
size=xsmall + layout=iconOnlyenabled여백spacing-x1_5 6px
size=xsmall + layout=iconOnlyenabled아이콘14px
size=small + layout=withTextenabled좌우 여백spacing-x3_5 14px
size=small + layout=withTextenabled상하 여백spacing-x2 8px
size=small + layout=withTextenabled간격spacing-x1 4px
size=small + layout=withTextenabled글자text-t4 14px / 19px
size=small + layout=withTextenabled앞 아이콘14px
size=small + layout=withTextenabled뒤 아이콘14px
size=small + layout=iconOnlyenabled너비36px
size=small + layout=iconOnlyenabled여백spacing-x2 8px
size=small + layout=iconOnlyenabled아이콘16px
size=medium + layout=withTextenabled좌우 여백spacing-x4 16px
size=medium + layout=withTextenabled상하 여백spacing-x2_5 10px
size=medium + layout=withTextenabled간격spacing-x1_5 6px
size=medium + layout=withTextenabled글자text-t4 14px / 19px
size=medium + layout=withTextenabled앞 아이콘16px
size=medium + layout=withTextenabled뒤 아이콘16px
size=medium + layout=iconOnlyenabled너비40px
size=medium + layout=iconOnlyenabled여백spacing-x2_5 10px
size=medium + layout=iconOnlyenabled아이콘18px
size=large + layout=withTextenabled좌우 여백spacing-x5 20px
size=large + layout=withTextenabled상하 여백spacing-x3 12px — (48 − 줄 높이 24) ÷ 2 — SEED 52 는 14
size=large + layout=withTextenabled간격spacing-x2 8px
size=large + layout=withTextenabled글자text-t6 18px / 24px
size=large + layout=withTextenabled앞 아이콘22px
size=large + layout=withTextenabled뒤 아이콘22px
size=large + layout=iconOnlyenabled너비48px
size=large + layout=iconOnlyenabled여백spacing-x3 12px
size=large + layout=iconOnlyenabled아이콘22px

State

상태모습
enabled기본
hovered웹. 누름 색과 같다(v106), 축소는 없다
focused웹 · 앱. 키보드 포커스에만 링 2px · 띄움 2px(v106). 앱은 키보드로 옮길 때만 그리고 손가락으로 누를 때는 없다(2026-10-08)
pressed누름 색 + 세로 2px 거리 축소(v104 — 기초 Feedback 의 눌림 피드백)
loading누름 색 위 로딩 원. 라벨 자리는 그대로(폭 유지)
disabled전용 색(bg-disabled · fg-disabled, v106). 불투명도로 흐리게 하지 않는다
기본
enabled
호버
hovered
포커스
focused
누름
pressed
로딩
loading
비활성
disabled
brandSolid
neutralSolid
neutralWeak
criticalSolid
brandOutline
neutralOutline
ghost
변형 × 상태 — 호버 · 포커스 · 누름은 그 순간을 멈춰 그렸다

변형별 상태 변화

varianthovered 웹 — 누름 색과 같다(v106), 축소는 없다pressed 누름 색 + 세로 2px 거리 축소(v104)loading 누름 색 위 로딩 원, 라벨 자리 그대로 · 누름을 자동으로 막는다disabled 전용 색(v106) — 불투명도로 흐리게 하지 않는다
brandSolid배경 color-bg-brand-solid-pressed HR #064F33 · Desk #013D96배경 color-bg-brand-solid-pressed HR #064F33 · Desk #013D96배경 color-bg-brand-solid-pressed HR #064F33 · Desk #013D96—
neutralSolid배경 color-bg-neutral-inverted-pressed #535866 · 다크 #B7BDCC배경 color-bg-neutral-inverted-pressed #535866 · 다크 #B7BDCC배경 color-bg-neutral-inverted-pressed #535866 · 다크 #B7BDCC—
neutralWeak배경 color-bg-neutral-weak-pressed #EDEFF3 · 다크 #404757배경 color-bg-neutral-weak-pressed #EDEFF3 · 다크 #404757배경 color-bg-neutral-weak-pressed #EDEFF3 · 다크 #404757—
criticalSolid배경 color-bg-critical-solid-pressed #C01016 · 다크 #D82424배경 color-bg-critical-solid-pressed #C01016 · 다크 #D82424배경 color-bg-critical-solid-pressed #C01016 · 다크 #D82424—
brandOutline배경 color-bg-layer-default-pressed #F7F8FD · 다크 #353B4D배경 color-bg-layer-default-pressed #F7F8FD · 다크 #353B4D — SEED bg.transparent-pressed 의 불투명 짝—배경 transparent · 테두리 color-stroke-neutral-weak #E5E8EF · 다크 #404757
neutralOutline배경 color-bg-layer-default-pressed #F7F8FD · 다크 #353B4D배경 color-bg-layer-default-pressed #F7F8FD · 다크 #353B4D—배경 transparent · 테두리 color-stroke-neutral-weak #E5E8EF · 다크 #404757
ghost배경 color-bg-layer-default-pressed #F7F8FD · 다크 #353B4D배경 color-bg-layer-default-pressed #F7F8FD · 다크 #353B4D배경 color-bg-layer-default-pressed #F7F8FD · 다크 #353B4D배경 transparent

로딩

  • loading 이면 로딩 원(크기는 Size 표)을 라벨 자리 가운데 둔다. 라벨은 글자 · 아이콘 색만 투명하게 해서 버튼 폭이 그대로다 — 버튼이 자식을 따로 감싸지 않으므로 부르는 쪽의 [&>span] · [&>svg] 규칙이 그대로 먹는다.
  • 로딩 중에는 누르기를 자동으로 막는다(두 번 제출 방지) — 포인터 · Enter · Space 누르기를 삼키고(onClick 을 부르지 않는다), 누름 축소도 없다. 포커스는 그대로 두고 aria-busy="true" 를 단다. SEED 는 로딩이 비활성을 포함하지 않지만 groo 는 막는다 — 사용자 결정.
  • 로딩 원 색은 변형마다 다르다(Solid 는 흰 원, Brand Outline 은 옅은 브랜드 트랙 위 브랜드 선 색 stroke-brand-solid — Progress Circle brand 톤과 같고 다크에서는 밝은 짝, 나머지는 회색 트랙 위 본문색).
직접 눌러 보기 — 위는 누름 · 호버 · 키보드 포커스(Tab), 아래는 누르면 잠시 로딩이 된다

Width

  • 내용 맞춤(기본) — 라벨 길이만큼.
  • 채움 — 컨테이너 폭을 채운다(w-full). 모바일 하단 고정 CTA · 폼의 마지막 제출.
  • 최소 너비는 두지 않는다. 둘을 나란히 채울 때는 비율로 나눈다(아래 배치).
9:41
9월 가계부
최근 거래
점점심 식사식비, 오늘−12,000원
버버스교통, 오늘−1,500원
Hug글자에 맞춘 폭 — 화면 안의 액션
9:41
거래 입력
12,000원
점심 식사
Fill화면 폭을 채운다 — 아래 고정 CTA
내용 맞춤(Hug)과 채움(Fill)

Guidelines

Hierarchy

시각적 주목도는 배경 대비로 정해진다. 화면에서 강조하려는 정도에 따라 변형을 고른다.

강 — 대비가 강한 배경화면에 1개
가장 중요한 CTA
중 — 대비가 약한 배경여러 개
대부분의 액션, 강과 짝
약 — 투명한 배경여러 개
중요도가 낮은 보조 액션
강 · 중 · 약 — 대비가 강한 배경일수록 먼저 눈에 들어온다
강조변형화면 안 개수쓰는 곳
강 — 대비가 강한 배경brandSolid · neutralSolid · criticalSolid1개가장 중요한 CTA
중 — 대비가 약한 배경neutralWeak여러 개대부분의 액션, 강 버튼과 짝
약 — 투명한 배경brandOutline · neutralOutline · ghost여러 개중요도가 낮은 보조 액션

상황에 따라 알맞은 변형 쓰기

화면 안의 중요도에 따라 고른다. 같은 저장이라도 그 화면의 CTA 면 neutralSolid, 서비스의 중심 동작이면 brandSolid 다.

9:41
휴가
남은 연차12.5일
올해 15일 중 2.5일 사용
신청 내역
연연차10월 2일, 승인 대기
반반차9월 12일, 승인
brandSolidHR 휴가 신청 — 서비스의 핵심 액션 하나
9:41
예산 설정
2,000,000원
450,000원
120,000원
neutralSolid저장 · 확인 · 다음 — 대부분의 CTA
9:41
메모
장보기 목록
메모를 삭제할까요?
'장보기 목록' 을 지우면 되돌릴 수 없어요.
criticalSolid + neutralWeak되돌릴 수 없는 삭제의 확정 — Alert Dialog
9:41
고정 지출
넷넷플릭스매달 18일, 13,500원
휴휴대폰 요금매달 25일, 49,000원
neutralOutline + brandOutline한 화면에 여러 번 나오는 보조 액션
변형마다 쓰는 자리 — 핵심 액션 · 대부분의 CTA · 되돌릴 수 없는 확정 · 여러 번 나오는 보조 액션

브랜드 색은 꼭 필요한 곳에만

브랜드 색은 로고 · 대표 버튼 · 핵심 메시지 같은 브랜드 상징에 모아 쓴다. 많이 쓰면 뜻과 강조가 흩어진다.

  • brandSolid 는 서비스의 중심 동작 하나에만 — Desk "거래 추가", HR "휴가 신청"처럼. 어떤 화면이 여기에 드는지는 앱 적용 때 화면마다 정한다.
  • 저장 · 확인 · 다음 같은 대부분의 CTA 는 neutralSolid 다.
9:41
홈
9월에 쓴 돈1,284,500원
예산 2,000,000원의 64%
최근 거래
점점심 식사식비, 오늘−12,000원
버버스교통, 오늘−1,500원
월월급수입, 9월 25일+3,200,000원
이렇게 — 브랜드 색은 거래 추가 하나 — 서비스의 핵심 액션
9:41
홈
9월에 쓴 돈1,284,500원
예산 2,000,000원의 64%
최근 거래
점점심 식사식비, 오늘−12,000원
버버스교통, 오늘−1,500원
월월급수입, 9월 25일+3,200,000원
이렇게 하지 않는다 — 여기저기 브랜드 색을 쓰면 무엇이 중요한지 흩어진다
브랜드 색은 한 화면에 하나 — 나머지는 중립색

예외 — 증권 매매 단추

증권 종목 상세의 [매도] · [매수] 한 쌍만 국내 통념을 따른다 — [매도] brandSolid(파랑) · [매수] criticalSolid(빨강), large 한 줄에 반씩 · 사이 8. 국내 증권 앱 사용자가 매수 빨강 · 매도 파랑을 이미 익혀 있어 그 기대를 지킨다. 이 자리에서 criticalSolid 는 "되돌릴 수 없는 확정" 이 아니고 brandSolid 는 "서비스의 중심 동작" 이 아니다 — 이 한 쌍 밖으로 넓히지 않는다(2026-10-29 사용자 결정 C7 B — https://claude.ai/artifact/QLQiAkvfez7bqpc4DEuiCH). 거절: 매수 neutralSolid · 매도 neutralWeak(스펙 그대로).

버튼 조합

  • Solid 조합 — neutralWeak + neutralSolid(또는 brandSolid). 위계가 분명하고 부담이 적다.
  • Outline 조합 — neutralOutline + brandOutline. 강조가 낮은 보조 액션을 한 화면에 여러 번 둘 때.
  • Outline 은 Solid 와 함께 쓰지 않는다.
Solid 조합 — 옅은 회색(보조) + 짙은 회색 · 브랜드(CTA)
이렇게 — Outline 은 Outline 끼리 — 여러 번 나오는 보조 액션
이렇게 하지 않는다 — Outline 과 Solid 를 한 줄에 두면 위계가 흐려진다
Outline 조합과 섞지 말아야 할 조합

버튼 배치

  • 닫기 · 초기화처럼 Dismiss 뜻의 neutralWeak 와 CTA 를 나란히 채울 때는 3:7 로 나눈다 — 위계가 분명해진다. 모달 footer 는 Dialog · Bottom Sheet 의 폭 나누기(지금은 균등)를 따른다.
  • 비슷한 위계의 neutralWeak 둘은 나란히 둘 수 있다.
  • 셋 이상 나란히 두는 것은 피한다(SEED "세 개 이상 나란히 사용하는 것은 권장하지 않습니다") — 중요도가 비슷해 보여 고르기 어렵고, 큰 글자에서 라벨이 잘린다. 더 있으면 아이콘만 버튼(더보기)으로 넘긴다.
  • 인접한 버튼 사이는 8(spacing-x2).
9:41
필터
종류
전체지출수입
카테고리
식비교통쇼핑주거의료문화
기간
2026. 9. 1 ~ 9. 30
이렇게 — 닫기 · 초기화 같은 neutralWeak 와 CTA 를 화면 하단에 채울 땐 3:7
9:41
필터
종류
전체지출수입
카테고리
식비교통쇼핑주거의료문화
기간
2026. 9. 1 ~ 9. 30
이렇게 하지 않는다 — 반반이면 초기화와 적용의 무게가 같아 보인다
3:7 — Dismiss 와 CTA 를 화면 하단에 채울 때
팀팀 회식10월 2일 (목) 19:00
이렇게 — 위계가 비슷한 neutralWeak 둘은 나란히 둘 수 있다
이렇게 하지 않는다 — 셋 이상 나란히 두지 않는다 — 더 있으면 아이콘만 버튼(더보기)으로 넘긴다
나란히 둘 수 있는 것과 없는 것

놓는 바탕 — neutralWeak 의 채움과 비활성 채움(bg-disabled)은 라이트에서 회색 바닥(bg-layer-basement)과 같은 색(gray-200)이다(v108). 흰 표면(카드 · 흰 띠 · 흰 화면 · 시트 · 모달 — bg-layer-default · bg-layer-floating) 위에 두고, 회색 바닥에 바로 둘 땐 neutralOutline 을 쓴다 — 바닥 위의 neutralWeak 는 채움이 사라져 글자만 남는다(1.00:1). 데스크톱 화면 제목 줄의 버튼이 그 자리다. 기초 규칙은 DESIGN.md Elevation 의 "바닥 위의 채움"(2026-10-16 사용자 결정 — 기초 웹 01-040 B)이다.

최근 거래
점점심 식사식비, 오늘−12,000원
버버스교통, 오늘−1,500원
월월급수입, 9월 25일+3,200,000원
이렇게 — neutralWeak 는 흰 표면(카드 · 시트 · 모달) 위에
최근 거래
점점심 식사식비, 오늘−12,000원
버버스교통, 오늘−1,500원
월월급수입, 9월 25일+3,200,000원
이렇게 하지 않는다 — 라이트에서 페이지 바탕 위에 바로 두면 채움이 사라진다 — neutralOutline 을 쓴다
neutralWeak 를 놓는 바탕 — 라이트에서만 생기는 문제라 라이트로 그렸다

모달 footer(groo)

  • 오른쪽에 [취소 neutralWeak] [저장 neutralSolid] — 웹은 small(36), 모바일 전체 폭은 large(48).
  • 확인 창을 여는 삭제는 왼쪽에 ghost + critical 글자. 삭제의 확정은 Alert Dialog 의 criticalSolid.
  • 1280 미만 Bottom Sheet 바닥도 같은 꼴이다 — [삭제] 는 왼쪽 ghost + critical(large · 글자만큼의 폭), 주 버튼([저장] · [수정])이 남은 폭을 채운다.
  • 목록 줄을 눌러 들어간 화면(상세 · 수정)에서 동작이 삭제 하나뿐이면 이 바닥 왼쪽 [삭제] 로 둔다 — 머리 ⋮ 는 동작이 여럿일 때다(Menu 의 "줄의 동작", 2026-10-25 사용자 결정).
  • 모달 footer 의 버튼을 neutralOutline 둘로 두지 않는다 — 전체 폭 버튼 둘이 테두리로 서면 위계가 흐려진다.
desk.groo.app
홈
가계부
캘린더
메모
거래 상세
금액−12,000원
내용점심 식사
카테고리식비
상세 — 삭제 · 수정확인 창을 여는 삭제는 왼쪽 ghost + critical, 주 액션은 오른쪽 neutralSolid
desk.groo.app
홈
가계부
캘린더
메모
거래 수정
12,000원
편집 폼 — 취소 · 저장웹은 small(36), 취소는 neutralWeak
Desk 웹의 모달 footer — 상세(삭제 · 수정)와 편집 폼(취소 · 저장)

라벨

기초 Writing(v106)을 따른다 — 사용자가 할 행동을 동사로, 해요체 없이 짧게, 마침표 없이.

  • "다음" 보다 "저장" · "주문 확인"처럼 무엇을 하는지 알게.
  • 같은 동작에는 같은 말을 쓴다.
9:41
설정
알림 받을 시간
오전 9시오후 1시오후 9시
이렇게 — 무엇을 하는지 동사로 — 누르면 무슨 일이 일어나는지 보인다
9:41
설정
알림 받을 시간
오전 9시오후 1시오후 9시
이렇게 하지 않는다 — '확인' 만으로는 무엇이 저장되는지 모른다
라벨은 사용자가 할 행동으로

긴 라벨

긴 라벨 · 번역 · 큰 글자로 나란한 두 버튼이 넘치면 세로로 쌓는다(주 버튼이 위). 한 줄에 억지로 줄이지 않는다.

짝 버튼은 SEED ResponsivePair 를 따른다 — 어느 한 버튼의 라벨이 짝 폭의 절반을 넘으면 둘 다 세로로 쌓고, 라벨은 한 줄을 지킨다(버튼 안에서 줄을 바꾸지 않는다). 대가 — 글자 크기 200% 에서는 가장 긴 라벨이 한 줄을 지키느라 버튼 밖으로 넘칠 수 있다(약 170). 2026-10-17 사용자 결정 — 03A-013 A.

큰 글자에서 높이 — 크기마다의 높이(32 · 36 · 40 · 48)는 최소 높이다. 글자가 커져 라벨이 상자보다 높아지면 상자가 글을 따라 커지고 글을 자르지 않는다(앱 200% 에서 36 · 40 · 48 → 54 · 58 · 72, 100% 는 그대로). 웹은 높이가 rem 이라 이미 그렇다. SEED 디자인 원본(action-button root.minHeight)과 같고, 칩(03-E 결정 1 C)과 같은 규칙이다. 대가 — 같은 200% 에서 웹(72 · 80 · 96)이 앱보다 크다. 2026-10-22 사용자 결정 — 버튼 큰 글자 A(고르지 않은 안: 글 -static · 지금처럼 잘림 · 글 1.5배 상한).

이 판정은 부품 하나가 맡는다 — 웹 ResponsivePair · 앱 PResponsivePair(자식 둘 — 보조 버튼 · 주 버튼 순서, 쌓이면 주 버튼이 위). 시트 · 확인창 바닥의 짝 버튼도 이 부품을 쓴다 — 같은 판정을 바닥마다 따로 적지 않는다(2026-10-21 사용자 결정 — I-01 결과 확인 결정 6A).

이렇게 — 넘치면 세로로 쌓는다 — 주 버튼이 위
이렇게 하지 않는다 — 한 줄에 억지로 넣으면 라벨이 잘린다
넘칠 땐 세로로

아이콘

아이콘은 버튼의 동작을 눈으로 보이게 돕는다. 앞(Prefix)은 액션의 뜻을, 뒤(Suffix)는 chevron 처럼 동작을 돕는다. 라벨을 읽기 어려워지지 않게 꼭 필요할 때만 쓰고, 앞 · 뒤를 함께 쓰지 않는다.

이렇게 — 앞 아이콘은 동작의 뜻을 돕는다
이렇게 — 뒤 아이콘은 동작을 돕는다(다음 · 펼치기)
이렇게 하지 않는다 — 모든 버튼에 아이콘을 붙이지 않는다
이렇게 하지 않는다 — 앞 · 뒤 아이콘을 함께 쓰지 않는다
아이콘을 쓰는 자리와 쓰지 말아야 할 자리

로딩

저장처럼 시간이 걸리는 액션은 누른 버튼을 로딩으로 바꾼다. 라벨을 바꾸거나 비활성으로 돌리지 않는다.

이렇게 — 로딩 원만 바꾸고 폭은 그대로 — 눌러 보면 잠시 로딩이 된다
이렇게 하지 않는다 — 라벨을 바꾸면 폭이 흔들리고, 비활성으로 두면 고장처럼 보인다
로딩은 그 자리에서 — 폭은 그대로

Button 과 Chip

SEED 는 Button 과 Chip 을 이렇게 가른다. groo 도 같다 — 선택 · 필터는 Chip(같은 내용을 2 ~ 4가지로 바로 다르게 보면 Segmented Control)이 맡는다. Toggle Group 은 2026-10-09 걷었다.

ButtonChip
목적액션 실행정보 표현 + 선택
예완료 · 제출 · 다음 · 삭제필터(서초4동 외 34) · 옵션(0 ~ 6개월)
라벨보면 동작이 예상된다지금 켜진 조건 · 정보
쓰는 모양하나로도둘 이상 묶어서
Button액션을 실행한다 — 라벨만 봐도 무슨 일이 일어날지 안다
전체식비교통쇼핑
고르기(Chip)지금 켜진 조건을 보인다 — 둘 이상 묶어서 쓴다
Button 은 실행, 고르기는 지금 켜진 조건

groo 에만 있는 것

반반 바(split bar) — 액션 둘이 무게가 같고 한 묶음으로 읽힐 때, 본문 폭을 채운 네모 바를 반으로 갈라 각 칸에 하나씩 둔다(예: 내역 분할의 항목 추가 · 균등 분할).

  • 컨테이너: display:flex; width:100%; background:var(--color-bg-layer-basement); border:1px solid var(--color-stroke-neutral-weak); border-radius:var(--radius-r2); overflow:hidden.
  • 각 칸: flex:1 의 ghost 버튼, 모서리 없음(컨테이너가 깎는다).
  • 칸 사이는 1px stroke-neutral-weak 구분선을 글자 높이만큼만 긋는다 — 끝까지 그으면 두 칸이 벽으로 막힌 것처럼 보인다.
  • 얇게 — xsmall 높이(32). 목록에 줄을 더하는 성격이라 본문 행보다 무거우면 안 된다. 알약 모양은 쓰지 않는다(segmented 와 헷갈린다).
  • 선택이 아니라 실행이다. 눌린 상태(data-state=on)가 없다. 셋 이상으로 나누지 않는다.
점점심 식사3명, 36,000원
반반 바 — 내역 분할의 항목 추가 · 균등 분할

가장자리 맞춤(flush) — SEED 의 bleed 와 같은 기능이다.

  • flush="left" | "right" — 그 방향 가로 여백을 0 으로 한다.
  • 대상: ghost + 앞 아이콘이 컨테이너 가장자리의 첫 · 끝 요소일 때. 글자만 있는 ghost 에는 쓰지 않는다.
  • flush ghost 는 텍스트 버튼이다 — 누름 · 호버에 배경을 깔지 않고 글자색으로만 반응한다(neutralSubtle → 누르면 fg-neutral). 한쪽 여백만 0 이라 채움 상자가 좌우 비대칭이 되기 때문이다.
  • 그래서 flush 는 ghostColor 를 덮는다 — 빨간 글자를 지켜야 하는 삭제(critical)에는 쓰지 않는다.
  • 아이콘만 있는 단추(iconOnly)는 여백을 0 으로 하지 않고 상자째 번진다 — 줄의 첫 · 끝에 선 아이콘 단추(달 줄의 ‹ › · 줄 끝 닫기)는 상자를 바깥으로 (상자 − 아이콘) ÷ 2 만큼 내어(음수 여백) 아이콘이 화면 여백 선에 닿게 한다 — small 36 · 아이콘 16 이면 10, 앱 40 · 아이콘 18 이면 11. 상자가 대칭으로 남으므로 누름 · 호버 바탕은 보통 단추처럼 깐다. 누르는 영역 44 도 같이 번진다(줄 안이면 List 의 "줄 끝 작은 버튼" 처럼 줄보다 이긴다). 아이콘이 여백 선 안쪽으로 10 남짓 들어가 글 · 목록 줄과 왼쪽 · 오른쪽 선이 어긋나던 것을 막는다(2026-10-29 I-29 — I-08 7 달 줄 ← −10 · 앱 가계부 머리 _bleedEnd 선례).
할 일
장장보기오늘
관관리비 이체내일
가장자리 맞춤 — 앞 아이콘이 목록의 왼쪽 선(분홍)에 맞는다

코드

레시피 recipes/shadcn/components/ui/button.tsx 를 쓴다. 아래 미리보기는 스펙 값으로 그린 모습이다.

기본

기본 — neutralSolid · medium
import { Button } from "@/components/ui/button"

<Button>저장</Button>

변형

변형
<Button variant="brandSolid">거래 추가</Button>
<Button variant="neutralSolid">저장</Button>
<Button variant="neutralWeak">취소</Button>
<Button variant="criticalSolid">삭제</Button>
<Button variant="brandOutline">자세히</Button>
<Button variant="neutralOutline">건너뛰기</Button>
<Button variant="ghost">더보기</Button>

크기

크기
<Button size="xsmall">라벨</Button>
<Button size="small">라벨</Button>
<Button size="medium">라벨</Button>
<Button size="large">라벨</Button>

아이콘

아이콘
import { ChevronRight, Plus, Search } from "lucide-react"

<Button variant="brandSolid"><Plus />거래 추가</Button>
<Button variant="neutralWeak">전체 보기<ChevronRight /></Button>
<Button variant="ghost" ghostColor="neutralSubtle" layout="iconOnly" aria-label="검색">
  <Search />
</Button>

ghost 글자색

ghost 글자색
<Button variant="ghost">편집</Button>
<Button variant="ghost" ghostColor="neutralSubtle">더보기</Button>
<Button variant="ghost" ghostColor="brand">자세히 보기</Button>
<Button variant="ghost" ghostColor="critical">삭제</Button>

비활성 · 로딩

비활성 · 로딩 — 오른쪽을 눌러 보면 잠시 로딩이 된다
const [saving, setSaving] = useState(false)

<Button disabled>저장</Button>
<Button loading={saving} onClick={async () => { setSaving(true); await save(); setSaving(false) }}>
  저장
</Button>

채움

채움 — 화면 하단 CTA
<Button size="large" className="w-full">저장</Button>
모달 footer — 삭제는 왼쪽, 취소 · 저장은 오른쪽
<footer className="flex items-center gap-2">
  <Button variant="ghost" ghostColor="critical" size="small" className="mr-auto">삭제</Button>
  <Button variant="neutralWeak" size="small">취소</Button>
  <Button size="small">저장</Button>
</footer>

Behavior

인터랙션동작
Click / TaponClick 발화. disabled · loading 이면 무시.
누른 동작이 끝나기 전다시 눌러도 무시 — onClick 이 Promise 를 돌려주면 이행 · 거부될 때까지 다음 누름(포인터 · Enter · Space)을 버린다. 시간 창은 없다. 비동기 동작은 Promise 를 돌려준다(mutateAsync) — 돌려주지 않으면 잠그지 않는다. 앱은 Future 가 끝날 때까지(사용자 결정 2026-10-08)
Keyboard Enter · Space클릭과 같다(포커스 상태에서).
Keyboard Tab다음 포커스로. Shift+Tab 은 거꾸로.
asChild<Slot> 으로 요소를 바꾼다(예: <a>). loading 과 함께 쓰지 않는다(Slot 은 자식 하나만 받는다).
Disabled누르기 · 키보드 불가, 포커스에서 빠진다.
부른 횟수 0 · 버린 누름 0
부른 횟수 0 · 버린 누름 0
누른 동작이 끝날 때까지 한 번 — 빠르게 여러 번 눌러도(Enter · Space 도) 동작은 한 번만 불리고, 끝나기 전의 누름은 버린다

Form 안 — type="submit" 을 적어야 제출한다. type="button" 은 제출하지 않는다.

앱 — 버튼(PButton)도 포커스를 받아 Tab 순서에 든다. 하드웨어 키보드로 옮길 때만 웹과 같은 링(2px · 띄움 2px · stroke-focus-ring)을 그리고 — Flutter 의 FocusHighlightMode.traditional — 손가락으로 누를 때는 그리지 않는다(사용자 결정 2026-10-08, DESIGN.md State › 앱의 키보드 포커스).

Accessibility

기준검증
WCAG 1.4.3 글자 대비(4.5:1)흰 글자 × bg-brand-solid · bg-critical-solid, fg-neutral-inverted × bg-neutral-inverted(누름 포함), fg-neutral × bg-neutral-weak — npm run lint:all 의 role 짝이 잰다
WCAG 1.4.3 비활성fg-disabled — 비활성 컴포넌트 글자는 대비 예외(1.4.3 incidental)
WCAG 1.4.11 UI 대비(3:1)포커스 링 stroke-focus-ring × 표면. Brand Outline 의 로딩 원 stroke-brand-solid × 표면 — 라이트 Desk 8.38 · HR 8.17, 다크 Desk 6.10 · HR 6.07
WCAG 2.5.8 Target Size — Minimum(AA, 24×24)네 크기 모두 통과
WCAG 2.5.5 Target Size — Enhanced(AAA, 44×44)보이는 크기는 large 48 만 넘지만, 누르는 영역을 44 까지 넓혀 모두 통과(v106)
WCAG 2.4.7 Focus visible키보드 포커스에만 링(focus-visible) — 앱도 키보드로 옮길 때만 같은 링(2026-10-08)
ARIA<button> 기본. 아이콘만이면 aria-label 필수. 로딩이면 aria-busy="true". asChild 로 <a> 를 쓸 때 role="button" 을 따로 달지 않는다
모션 줄이기축소를 하지 않는다(v104 모드) — 누름은 색으로만

Do / Don't

✅ Do

  • 강조 버튼(brandSolid · neutralSolid · criticalSolid)은 한 화면에 하나.
  • 모달 footer 는 [취소 neutralWeak] [저장 neutralSolid], 삭제는 왼쪽 ghost + critical.
  • 되돌릴 수 없는 작업의 확정은 확인 창의 criticalSolid.
  • 증권 매매 한 쌍만 예외 — [매도] brandSolid · [매수] criticalSolid(국내 통념).
  • 아이콘만 있는 버튼에는 이름을 단다.
  • 모바일 하단 CTA 는 large + 채움 + 안전 영역(pb-safe).

❌ Don't

  • 강조 버튼을 한 화면에 여럿.
  • 셋 이상을 한 줄에 — 피한다, 더 있으면 더보기.
  • brandSolid 를 일반 저장 · 확인에 — 브랜드 색이 흩어진다.
  • Outline 을 Solid 와 섞기.
  • 앞 · 뒤 아이콘을 함께.
  • disabled 버튼에 호버 · 누름 반응.

Specification

button.yaml 의 규칙을 하나도 빼지 않고 조건마다 그린다 — 웹 · 앱이 버튼을 만들 때 이 값을 그대로 쓴다. 조건이 없는 Base 가 모든 조합에 걸리고, 뒤의 규칙이 앞의 같은 값을 덮는다. 상태는 enabled 에서 바뀌는 값만 적었다.

Base

부위상태속성값
rootenabled커서cursorpointer
터치 영역touchTarget44 × 44보이는 크기와 따로 — 누르는 영역을 가로 · 세로 44 까지 넓힌다(v106). AA ✓ · AAA ✓
전환 대상transitionPropertybackground-color, color
전환 시간transitionDurationmotion-duration-color-transition150ms
전환 곡선transitionEasingmotion-ease-easingcubic-bezier(0.35, 0, 0.35, 1)
누름press누른 동작이 끝날 때까지 한 번onClick 이 Promise 를 돌려주면 이행 · 거부될 때까지 다음 누름(포인터 · Enter · Space)을 버린다 — 시간 창은 없다. 비동기 동작은 Promise 를 돌려준다(mutateAsync). 돌려주지 않으면 잠그지 않는다. 앱은 Future 가 끝날 때까지. 사용자 결정 2026-10-08
labelenabled굵기fontWeight700
글꼴fontFamilyfont-sansPretendard, Inter, sans-serif
focusRingfocused링 두께width2px웹 · 앱 같다 — 앱은 키보드로 옮길 때만 그린다(Flutter FocusHighlightMode.traditional), 손가락으로 누를 때는 없다. 앱 버튼도 포커스를 받아 Tab 순서에 든다(사용자 결정 2026-10-08)
링 색colorstroke-focus-ringDesk#0147AD다크 #7AA9F6HR#075B3C다크 #2ABE84
링 간격offset2px
rootpressed배율scale세로 2px 축소기준 길이 max(높이, 폭 ÷ 4, 24) 에서 2px — Feedback 의 눌림 피드백(v104)
축소 시간scaleDurationmotion-duration-pressed-scale150ms
축소 곡선scaleEasingmotion-ease-pressed-scalecubic-bezier(0, 0, 0.15, 1)
rootloading커서cursorprogress
누름press막음누르기(포인터 · Enter · Space)를 삼켜 두 번 제출을 막는다 — 축소도 없다. aria-busy=true. SEED 는 막지 않는다
labelloading색colortransparent글자 · 아이콘을 투명하게 해 폭을 그대로 둔다
rootdisabled배경backgroundbg-disabled#F5F6FA다크 #353B4D
글자·아이콘foregroundfg-disabled#8A91A0다크 #838997
커서cursornot-allowed포인터 이벤트는 켜 둔다 — 끄면 이 커서가 안 보인다(SEED 와 같다)

variant=brandSolid

부위상태속성값
rootenabled배경backgroundbg-brand-solidDesk#0147AD다크 #1049A4HR#075B3C다크 #1B583E
글자·아이콘foregroundstatic-white#FFFFFF
progressCircleenabled로딩 원 트랙trackstatic-white / 30%rgba(255, 255, 255, 0.3)
로딩 원 채움rangestatic-white#FFFFFF
roothovered배경backgroundbg-brand-solid-pressedDesk#013D96다크 #1A5AC2HR#064F33다크 #0C6C49
rootpressed배경backgroundbg-brand-solid-pressedDesk#013D96다크 #1A5AC2HR#064F33다크 #0C6C49
rootloading배경backgroundbg-brand-solid-pressedDesk#013D96다크 #1A5AC2HR#064F33다크 #0C6C49

variant=neutralSolid

부위상태속성값
rootenabled배경backgroundbg-neutral-inverted#1A1F2E다크 #F5F6FA
글자·아이콘foregroundfg-neutral-inverted#FFFFFF다크 #242938
progressCircleenabled로딩 원 트랙trackfg-neutral-inverted / 30%rgba(255, 255, 255, 0.3)다크 rgba(36, 41, 56, 0.3)
로딩 원 채움rangefg-neutral-inverted#FFFFFF다크 #242938
roothovered배경backgroundbg-neutral-inverted-pressed#535866다크 #B7BDCC
rootpressed배경backgroundbg-neutral-inverted-pressed#535866다크 #B7BDCC
rootloading배경backgroundbg-neutral-inverted-pressed#535866다크 #B7BDCC

variant=neutralWeak

부위상태속성값
rootenabled배경backgroundbg-neutral-weak#F5F6FA다크 #353B4D
글자·아이콘foregroundfg-neutral#1A1F2E다크 #F5F6FA
progressCircleenabled로딩 원 트랙trackgray-500#8A91A0다크 #656B78
로딩 원 채움rangefg-neutral#1A1F2E다크 #F5F6FA
roothovered배경backgroundbg-neutral-weak-pressed#EDEFF3다크 #404757
rootpressed배경backgroundbg-neutral-weak-pressed#EDEFF3다크 #404757
rootloading배경backgroundbg-neutral-weak-pressed#EDEFF3다크 #404757

variant=criticalSolid

부위상태속성값
rootenabled배경backgroundbg-critical-solid#D72323다크 #CC0E17
글자·아이콘foregroundstatic-white#FFFFFF
progressCircleenabled로딩 원 트랙trackstatic-white / 30%rgba(255, 255, 255, 0.3)
로딩 원 채움rangestatic-white#FFFFFF
roothovered배경backgroundbg-critical-solid-pressed#C01016다크 #D82424
rootpressed배경backgroundbg-critical-solid-pressed#C01016다크 #D82424
rootloading배경backgroundbg-critical-solid-pressed#C01016다크 #D82424

variant=brandOutline

부위상태속성값
rootenabled배경backgroundtransparent
글자·아이콘foregroundfg-brandDesk#0147AD다크 #7AA9F6HR#075B3C다크 #2ABE84
테두리borderColorstroke-neutral-weak#E5E8EF다크 #404757SEED stroke.neutral-muted(투명도 있는 검정)의 불투명 짝
테두리 두께borderWidth1px
progressCircleenabled로딩 원 트랙trackbg-brand-weak-pressedDesk#D7E5FC다크 #1F3A69HR#D6E8DE다크 #214132
로딩 원 채움rangestroke-brand-solidDesk#0147AD다크 #7AA9F6HR#075B3C다크 #2ABE84Progress Circle brand 톤과 같다 — 다크는 밝은 짝(바탕 위 라이트 Desk 8.38 · HR 8.17, 다크 Desk 6.10 · HR 6.07 — 트랙과 Desk 6.59 · 4.73). 2026-10-08 까지 bg-brand-solid 라 다크 바탕 위 1.73 이었다(채움 색은 다크에서도 짙다)
roothovered배경backgroundbg-layer-default-pressed#F7F8FD다크 #353B4D
rootpressed배경backgroundbg-layer-default-pressed#F7F8FD다크 #353B4DSEED bg.transparent-pressed 의 불투명 짝
rootdisabled배경backgroundtransparent
테두리borderColorstroke-neutral-weak#E5E8EF다크 #404757

variant=neutralOutline

부위상태속성값
rootenabled배경backgroundtransparent
글자·아이콘foregroundfg-neutral#1A1F2E다크 #F5F6FA
테두리borderColorstroke-neutral-weak#E5E8EF다크 #404757
테두리 두께borderWidth1px
progressCircleenabled로딩 원 트랙trackgray-500#8A91A0다크 #656B78
로딩 원 채움rangefg-neutral#1A1F2E다크 #F5F6FA
roothovered배경backgroundbg-layer-default-pressed#F7F8FD다크 #353B4D
rootpressed배경backgroundbg-layer-default-pressed#F7F8FD다크 #353B4D
rootdisabled배경backgroundtransparent
테두리borderColorstroke-neutral-weak#E5E8EF다크 #404757

variant=ghost

부위상태속성값
rootenabled배경backgroundtransparent
글자·아이콘foregroundfg-neutral#1A1F2E다크 #F5F6FA
progressCircleenabled로딩 원 트랙trackgray-500#8A91A0다크 #656B78
로딩 원 채움rangefg-neutral#1A1F2E다크 #F5F6FA
roothovered배경backgroundbg-layer-default-pressed#F7F8FD다크 #353B4D
rootpressed배경backgroundbg-layer-default-pressed#F7F8FD다크 #353B4D
rootloading배경backgroundbg-layer-default-pressed#F7F8FD다크 #353B4D
rootdisabled배경backgroundtransparent

variant=ghost, ghostColor=neutralSubtle

부위상태속성값
rootenabled글자·아이콘foregroundfg-neutral-subtle#62697A다크 #A2A8B7

variant=ghost, ghostColor=brand

부위상태속성값
rootenabled글자·아이콘foregroundfg-brandDesk#0147AD다크 #7AA9F6HR#075B3C다크 #2ABE84

variant=ghost, ghostColor=critical

부위상태속성값
rootenabled글자·아이콘foregroundfg-critical#D72323다크 #FF8477

size=xsmall

부위상태속성값
rootenabled높이height32px최소 높이 — 글이 상자보다 크면(큰 글자) 상자가 글을 따라 늘어난다(2026-10-22 사용자 결정 — 버튼 큰 글자 A, SEED action-button root.minHeight)
모서리radiusradius-full9999px(알약)
progressCircleenabled로딩 원size14px
로딩 원 두께thickness2px

size=small

부위상태속성값
rootenabled높이height36px최소 높이 — 글이 상자보다 크면(큰 글자) 상자가 글을 따라 늘어난다(2026-10-22 사용자 결정 — 버튼 큰 글자 A, SEED action-button root.minHeight)
모서리radiusradius-r28px
progressCircleenabled로딩 원size14px
로딩 원 두께thickness2px

size=medium

부위상태속성값
rootenabled높이height40px최소 높이 — 글이 상자보다 크면(큰 글자) 상자가 글을 따라 늘어난다(2026-10-22 사용자 결정 — 버튼 큰 글자 A, SEED action-button root.minHeight)
모서리radiusradius-r28px
progressCircleenabled로딩 원size16px
로딩 원 두께thickness2px

size=large

부위상태속성값
rootenabled높이height48pxSEED 52 — groo 는 48(사용자 결정). 최소 높이 — 글이 상자보다 크면(큰 글자) 상자가 글을 따라 늘어난다(2026-10-22 사용자 결정 — 버튼 큰 글자 A, SEED action-button root.minHeight)
모서리radiusradius-r312px
progressCircleenabled로딩 원size18px
로딩 원 두께thickness2px

size=xsmall, layout=withText

부위상태속성값
rootenabled상하 여백paddingYspacing-x1_56px
좌우 여백paddingXspacing-x3_514px
간격gapspacing-x14px
labelenabled글자typographytext-t313px / 18px
prefixIconenabled앞 아이콘size14px
suffixIconenabled뒤 아이콘size14px

size=xsmall, layout=iconOnly

부위상태속성값
rootenabled너비width32px
여백paddingspacing-x1_56px
iconenabled아이콘size14px

size=small, layout=withText

부위상태속성값
rootenabled상하 여백paddingYspacing-x28px
좌우 여백paddingXspacing-x3_514px
간격gapspacing-x14px
labelenabled글자typographytext-t414px / 19px
prefixIconenabled앞 아이콘size14px
suffixIconenabled뒤 아이콘size14px

size=small, layout=iconOnly

부위상태속성값
rootenabled너비width36px
여백paddingspacing-x28px
iconenabled아이콘size16px

size=medium, layout=withText

부위상태속성값
rootenabled상하 여백paddingYspacing-x2_510px
좌우 여백paddingXspacing-x416px
간격gapspacing-x1_56px
labelenabled글자typographytext-t414px / 19px
prefixIconenabled앞 아이콘size16px
suffixIconenabled뒤 아이콘size16px

size=medium, layout=iconOnly

부위상태속성값
rootenabled너비width40px
여백paddingspacing-x2_510px
iconenabled아이콘size18px

size=large, layout=withText

부위상태속성값
rootenabled상하 여백paddingYspacing-x312px(48 − 줄 높이 24) ÷ 2 — SEED 52 는 14
좌우 여백paddingXspacing-x520px
간격gapspacing-x28px
labelenabled글자typographytext-t618px / 24px
prefixIconenabled앞 아이콘size22px
suffixIconenabled뒤 아이콘size22px

size=large, layout=iconOnly

부위상태속성값
rootenabled너비width48px
여백paddingspacing-x312px
iconenabled아이콘size22px

SEED 와 다른 점

  • large 는 48 이다(SEED 52) — 여백 · 글자 · 아이콘은 SEED large 그대로다.
  • 테두리 · 투명 누름은 SEED 가 투명도 있는 색(stroke.neutral-muted · bg.transparent-pressed)이라, 불투명한 가장 가까운 역할(stroke-neutral-weak · bg-layer-default-pressed)을 쓴다 — design.md 검사기가 8자리 hex 를 받지 않는다.
  • 웹의 hovered · focused 상태를 더한다(v106). focused 는 앱의 키보드 포커스에도 그린다(2026-10-08).
  • 누름은 누른 동작이 끝날 때까지 한 번이다(onClick 이 돌려준 Promise) — SEED 는 정하지 않았다.
  • 로딩 중 누르기를 자동으로 막는다(SEED 는 막지 않는다).
  • neutralSolid 의 누름은 새 역할 bg-neutral-inverted-pressed(gray-800, v112)다.

Migration notes

2026-10-30 — 아이콘 단추의 가장자리 맞춤

"가장자리 맞춤(flush)" 에 아이콘만 있는 단추의 규칙을 더했다 — 여백을 0 으로 하는 글 단추와 달리 상자째 (상자 − 아이콘) ÷ 2 만큼 번져 아이콘이 화면 여백 선에 닿는다(2026-10-29 I-29 — I-08 7 달 줄 · 앱 _bleedEnd 선례). 값 · 토큰 변경 없음 — 번지는 양은 크기 표(상자 · 아이콘)에서 나온다. 레시피 flush 는 아직 글 단추용(여백 0)이고, 아이콘 단추는 놓는 자리가 음수 여백으로 번진다. 옛 스펙은 button.history/v-pre-cleanup-1030.md.

2026-10-29 — 증권 매매 단추 예외

사용자 결정 C7 B — 지시서가 "지금 뜻 그대로" 로 넘긴 [매도] brandSolid · [매수] criticalSolid 가 변형 뜻(브랜드 = 중심 동작 하나 · 빨강 = 되돌릴 수 없는 확정)과 맞지 않아 그림으로 물었다. 국내 통념을 이 한 쌍의 예외로 적는다. 앱 매도는 옛 bg-informative-solid → brandSolid, 사이 10 → 8. 값 · 토큰 변경 없음.

사용자 결정 — 목록 줄에는 ⋮ 를 두지 않고, 들어간 화면의 동작이 하나(삭제)면 바닥 왼쪽 [삭제] ghost + critical 이다(button.md 모달 footer 꼴 — 시트도 같게, Menu 의 "줄의 동작"). "모달 footer" 에 시트 바닥 · 들어간 화면 두 줄을 더하고, critical 의 쓰임에 시트 바닥을 적었다. 시트 바닥의 폭(삭제 글자만큼 · 주 버튼이 남은 폭)은 모달 footer 의 왼쪽 삭제를 시트의 전체 폭 버튼에 옮긴 것이다. 값 변경 없음.

2026-10-22 — 셋 이상 나란히는 "피한다"

"셋 이상 나란히 두지 않는다" 를 SEED 말 그대로 "피한다" 로 낮췄다(남은 규칙 R7 — 화면은 같다). "강조 버튼은 한 화면에 하나"(SEED Hierarchy 표 "화면 내 개수 1개") · "모달 footer 를 neutralOutline 둘로 두지 않는다"(SEED 조합 규칙과 같은 쪽)는 그대로다(R8 · R9). 사용자 결정 — https://claude.ai/artifact/Mag1tEv51MdQHdy1o8hY3L. 값 변경 없음.

2026-10-08 — 앱 적용이 남긴 것

앱 적용(desk-front #428 ~ #430 · desk-app #407 ~ #410)이 남긴 문제를 사용자가 비교 페이지에서 정했다.

  • Brand Outline 의 로딩 원(8) — 호가 bg-brand-solid 라 다크의 투명한 버튼 위에서 1.73:1(트랙과 1.34)이었다. Progress Circle brand 톤과 같은 stroke-brand-solid(다크는 밝은 짝 — 6.10)로 바꿨다. 트랙 bg-brand-weak-pressed 는 그대로다.
  • 두 번 누름(F3) — onClick 이 Promise 를 돌려주면 끝날 때까지 다음 누름을 버린다. 시간 창(Desk 웹 Button 의 옛 600ms)은 두지 않는다 — 앱 옛 시트의 잠금과 같다. 로딩 중에 누름을 삼키는 것은 그대로다. 레시피의 onClick 은 돌려준 값을 받는 꼴((event) => unknown)이 됐다.
  • 앱의 키보드 포커스(E2) — 키보드로 옮길 때만 웹과 같은 링을 그리고, PButton 도 포커스를 받는다(DESIGN.md State · Inclusive Design).

2026-09-30 — SEED Action Button 구조로

사용자가 비교 페이지(https://claude.ai/artifact/JRfDTChty6VDZzzqpWSF6C)에서 정했다 — 변형은 SEED 7가지만(옛 dangerSoft 는 없앰, 정말 위험한 건 criticalSolid) · 일반 CTA 는 neutralSolid · 크기는 SEED 틀 + large 48 · 모양은 SEED(모서리 8 · 12 · 알약, 글자 700) · 확인 창을 여는 삭제는 ghost + critical · brandSolid 는 핵심 액션에만 · 로딩은 SEED 모양 + 누름 자동 막기.

제품(Desk 웹 197 · 앱 133 · HR 웹 176곳)은 앱 적용 단계에서 옮긴다. 그동안 옛 이름은 아래처럼 읽는다.

옛 변형새 변형
default(정보 파랑 채움)neutralSolid(일반 CTA) · brandSolid(핵심 액션)
destructivecriticalSolid
secondaryneutralWeak
outlineneutralOutline
dangerSoftghost + critical(확인 창을 여는 삭제)
ghostghost — 보조 아이콘 액션은 neutralSubtle
accent · linkghost + brand
옛 크기새 크기
sm(32)xsmall — 알약
default(36)small
md(40)medium
lg(48)large
icon(40 — Desk 웹은 36, button-variants.ts:60)medium · iconOnly. 상단 바는 Top Navigation 의 아이콘 버튼
iconLg(36 원형, 모바일 헤더)상단 바는 Top Navigation 의 아이콘 버튼(44 · 24 — 2026-10-04), 그 밖은 medium · iconOnly(보이는 크기 40 — v106)

그 밖에 바뀐 것 — 누름은 밝기 95% · 0.98 배 → 누름 색 + 2px 거리 축소(v104), 비활성은 50% 불투명 → 전용 색(v106), 글자 굵기 500 → 700, 모서리 4 → 8(large 12, xsmall 알약), 아이콘–글자 간격 8 고정 → 크기마다(4 · 4 · 6 · 8), 로딩 스피너는 라벨 앞 → 라벨 자리.

앱에 옮길 때 — cn 부터. 새 cva 는 groo 스케일 이름(text-t4 · px-x4 · rounded-r2)을 쓴다. 기본 tailwind-merge 는 text-t4 를 글자색으로 읽어 앞의 text-fg-neutral-inverted 를 지우고(짙은 버튼의 흰 글자가 사라진다), px-x4 를 여백으로 못 읽어 p-0 덮어쓰기가 먹지 않는다. 레시피 lib/utils.ts 처럼 spacing · radius · text 스케일을 extendTailwindMerge 에 등록한다 — Desk 웹의 cn 은 지금 옛 글자 이름만 안다.

다른 컴포넌트에 남은 옛 이름 — 버튼을 부르는 레시피 코드(alert-dialog · calendar · carousel · pagination · sidebar)와 그 스펙, 모달 footer 규칙(dialog · drawer · alert-dialog)은 이 변경에서 옮겼다. 아래는 옛 이름을 글로만 적고 있어 각 컴포넌트 차례에 옮긴다 — 예제 속 JSX(tooltip · sheet · popover · form · dropdown-menu · drawer · dialog · card · alert-dialog · table · spinner 예제), 스펙 본문(table · popover · spinner · context-menu · dropdown-menu · calendar 의 nav outline — toggle-group 은 2026-10-09 걷었고 icon-picker 는 Input Button 으로, collapsible 은 2026-10-10 Accordion 항목 모양의 트리거로 다시 썼다), DESIGN.md 의 다른 컴포넌트 절(Form 의 button-primary lg · Pagination 의 페이지 버튼), 미리보기의 빈 화면 · 폼 · 결재 행(Outline 과 Solid 를 한 줄에 둔다 — Outline 조합 규칙과 어긋난다).

Sizes 표는 원래 sm 32 · md 40 · lg 48 셋이었는데, desk 웹 구현에는 shadcn 이름을 그대로 둔 default(높이 36 · 좌우 양쪽 16 · 14px)가 하나 더 있었고 그게 cva 기본값이다. 모달 footer 20종 실측에서 표준 footer 40 과 손수 footer 36 이 섞여 있었고, 화면으로 보니 40 은 대화상자에 비해 굵어 사용자가 36 을 골랐다 — 2026-09-30 구조에서는 small(36)이 그 자리다.

default 채움색을 브랜드 남색에서 정보 파랑으로 바꾸고(남색은 버튼 채움으로 무겁다), 모달 footer 의 취소는 secondary, 삭제는 dangerSoft 로 두었다. 2026-09-30 에 일반 CTA 는 neutralSolid, 취소는 neutralWeak, 삭제는 ghost + critical 로 다시 정했다.

이 페이지