# Button 명확한 액션을 쉽게 수행할 수 있도록 돕는 기본 인터랙션 컴포넌트입니다. > 원본: `03_ASSET_GUIDE/catalog/button.js` · CSS: `03_ASSET_GUIDE/styles/elements/button.css` > kind `element` · category `action-input` · status `stable` · specSource `measured` > sub-asset: [Icon Button](#icon-button) (`--layout_icon-only`) --- ## 1. 구성 Button 은 Label 을 감싸는 **Container** 로 구성되며, 필요에 따라 Prefix Icon 과 Suffix Icon 을 가질 수 있습니다. ``` ┌──────────────────────────────┐ │ [icon] 글쓰기 [icon] │ └──────────────────────────────┘ ① ② ③ ④ ``` | # | 요소 | 필수 | 클래스 | 설명 | |---|---|---|---|---| | ① | Container | 필수 | `atom-button` | 터치 영역과 배경, 모서리를 담당합니다. 최소 높이는 size 로 결정됩니다. | | ② | Prefix Icon | 선택 | `atom-button__icon` | 액션의 의미를 시각적으로 보조합니다. | | ③ | Label | 필수 | `atom-button__label` | 액션을 서술하는 텍스트입니다. | | ④ | Suffix Icon | 선택 | `atom-button__icon` | 다음 단계로 이동함을 암시할 때 사용합니다. | | — | Spinner | `loading` 일 때 | `atom-button__spinner` | 로딩 표시. 아이콘 자리에 들어갑니다. | --- ## 2. 종류 / 크기 ### 크기 (Size) Button 은 `xsmall`, `small`, `medium`, `large` 네 가지 사이즈로 제공됩니다. `small` 과 `medium` 은 화면 중앙에서 범용적으로 사용되며, `large` 는 주로 CTA 역할로 사용됩니다. `xsmall` 은 좁은 공간에 들어가는 Pill 형태입니다. | Size | 높이 | 좌우 여백 | 모서리 | 폰트 | 아이콘 | 주 사용처 | |---|---|---|---|---|---|---| | `xsmall` | 32px | 12px | full | 13px | 14px | 카드 안 보조 액션 | | `small` | 40px | 16px | 8px | 14px | 16px | 리스트 행, 툴바 | | `medium` | 47px | 20px | 8px | 15px | 18px | 본문 기본값 | | `large` | 54px | 24px | 12px | 16px | 20px | 화면 하단 CTA | ### 종류 (Variant) 일곱 가지 Variant 를 제공합니다. Solid → Weak → Outline → Ghost 순서로 강조도가 낮아집니다. | 강조도 | Variant | 화면 내 개수 | 사용처 | |---|---|---|---| | 가장 높음 | `brandSolid` | 1개 | 화면의 목표가 되는 단 하나의 액션 | | 높음 | `neutralSolid` | 1~2개 | 브랜드 의미가 없는 주요 액션 | | 높음 | `criticalSolid` | 1개 | 삭제·탈퇴처럼 되돌리기 어려운 액션 | | 중간 | `neutralWeak` | 제한 없음 | 주요 액션과 나란히 놓이는 보조 액션 | | 중간 | `brandOutline` | 1개 | 브랜드 액션이지만 강조를 낮춰야 할 때 | | 낮음 | `neutralOutline` | 제한 없음 | 무게가 동등한 여러 액션 | | 가장 낮음 | `ghost` | 제한 없음 | 텍스트 링크에 가까운 액션 | ### Layout 라벨과 아이콘의 조합으로 구성됩니다. 아이콘은 라벨 앞, 뒤 또는 아이콘만으로 구성할 수 있습니다. `iconOnly` 는 Container 를 정사각형으로 만듭니다. 너비가 높이와 같아져 `xsmall` 32×32, `small` 40×40, `medium` 47×47, `large` 54×54 입니다. > [!WARNING] > **iconOnly 는 접근성이 떨어집니다** > 아이콘만으로 의미를 전달하기 때문에, 반드시 `aria-label` 로 액션 이름을 제공해야 합니다. > 의미가 관례로 굳은 아이콘(닫기, 더보기)에만 사용하세요. 자세한 규칙은 [Icon Button](#icon-button) 을 참고하세요. ### Width 기본은 라벨 길이에 맞춰지는 `fixed` 입니다. 화면 하단 CTA 처럼 영역 전체를 차지해야 할 때만 `full` 을 사용합니다. ### 클래스 계약 `styles/elements/button.css` 헤더와 원본 `registry.json` `css.blocks` 기준입니다. 클래스가 없는 값(—)은 기본 스타일 그대로입니다. | Prop | 값 | 클래스 | |---|---|---| | `size` | `xsmall` · `small` · `medium` · `large` | `--size_xsmall` · `--size_small` · — · `--size_large` | | `variant` | `brandSolid` · `neutralSolid` · `criticalSolid` · `neutralWeak` · `brandOutline` · `neutralOutline` · `ghost` | `--variant_brand-solid` · `--variant_neutral-solid` · `--variant_critical-solid` · `--variant_neutral-weak` · `--variant_brand-outline` · `--variant_neutral-outline` · `--variant_ghost` | | `layout` | `withText` · `iconOnly` | — · `--layout_icon-only` | | `width` | `fixed` · `full` | — · `--width_full` | 상태 선택자: `:hover` · `[data-pressed]` · `[disabled]` · `[data-loading]` --- ## 3. 상태 `enabled`, `pressed`, `loading`, `disabled` 네 가지 상태를 가집니다. 통신이 오래 걸리는 액션은 반드시 `loading` 을 노출해 중복 실행을 막습니다. | State | 표현 | 선택자 | |---|---|---| | `enabled` | 기본 | — | | `hover` | `filter: brightness(0.94)`. Ghost·Outline 은 대신 `bg-neutral-weak` 배경 | `:hover` | | `pressed` | `filter: brightness(0.88)` | `[data-pressed]` | | `loading` | 스피너 노출, 라벨 `opacity: 0.45`, 커서 `progress` | `[data-loading]` | | `disabled` | `opacity: 0.35`, 커서 `not-allowed` | `[disabled]` | > [!NOTE] > `disabled` 는 왜 누를 수 없는지 알려주지 않습니다. 조건이 충족되지 않은 이유를 함께 보여줄 수 없다면, > 버튼을 활성 상태로 두고 눌렀을 때 안내를 띄우는 편이 낫습니다. --- ## 4. Props | Prop | Type | Default | 필수 | 설명 | |---|---|---|---|---| | `size` | `"xsmall" \| "small" \| "medium" \| "large"` | `"medium"` | | 버튼 높이와 좌우 여백, 폰트 크기를 결정합니다. | | `variant` | `"brandSolid" \| "neutralSolid" \| "criticalSolid" \| "neutralWeak" \| "brandOutline" \| "neutralOutline" \| "ghost"` | `"neutralSolid"` | | 강조도와 색을 결정합니다. | | `layout` | `"withText" \| "iconOnly"` | `"withText"` | | `iconOnly` 는 정사각형이 되고 라벨을 숨깁니다. | | `width` | `"fixed" \| "full"` | `"fixed"` | | `full` 은 부모 너비를 모두 차지합니다. | | `loading` | `boolean` | `false` | | 스피너를 노출하고 입력을 무시합니다. | | `disabled` | `boolean` | `false` | | 비활성 상태로 만듭니다. | | `children` | `ReactNode` | | ● | 라벨 텍스트입니다. | ### Parts (compound) | Part | 요소 | 설명 | |---|---|---| | `Button` | ` ``` ```html ``` ```html ``` --- ## Icon Button 라벨 없이 아이콘만으로 액션을 실행하는 정사각형 버튼. > [!NOTE] > **독립된 컴포넌트가 아닙니다** > Icon Button 은 Button 의 `--layout_icon-only` modifier 입니다. variant · size · state 규칙이 전부 같고, > 달라지는 것은 **너비가 높이와 같아지고 좌우 여백이 0이 되는 것** 뿐입니다. 이 절은 그 차이와, 라벨이 없어서 생기는 접근성 규칙을 다룹니다. > [!CAUTION] > **카탈로그의 `input-button` 과 다릅니다** > [Input Button](./input-button.md) 은 **입력 필드처럼 생긴 버튼**으로, 눌러서 날짜 선택기 같은 것을 여는 컴포넌트입니다. 아이콘 버튼과 무관합니다. ### 구성 Container 와 Icon 둘뿐입니다. Label 요소는 렌더하지 않고, 대신 **접근 가능한 이름**을 `aria-label` 로 제공합니다. ### 크기 높이와 너비가 같아집니다. 좌우 여백은 0이 되고, 아이콘이 가운데 놓입니다. | Size | 크기 | 아이콘 | 아이콘 비율 | |---|---|---|---| | `xsmall` | 32×32px | 14px | 44% | | `small` | 40×40px | 16px | 40% | | `medium` | 47×47px | 18px | 38% | | `large` | 54×54px | 20px | 37% | 크기가 커질수록 아이콘 비율이 낮아집니다. 큰 버튼에서 아이콘이 같은 비율을 유지하면 면을 꽉 채운 것처럼 답답해 보이기 때문입니다. `xsmall` 은 Button 과 마찬가지로 모서리가 `--atom-radius-full` 이라 **원형**이 됩니다. `large` 는 `--atom-radius-r3`(12px) 입니다. ### 종류 Button 의 일곱 가지를 그대로 씁니다. 아이콘 버튼에서 주로 쓰는 것은 아래 다섯 가지입니다. | Variant | 아이콘 버튼에서의 쓰임 | |---|---| | `ghost` | 툴바·헤더처럼 버튼이 여러 개 늘어선 자리. 가장 흔함 | | `neutralWeak` | 배경 위에 단독으로 놓여 눌린다는 것을 알려야 할 때 | | `neutralOutline` | 이미지나 복잡한 배경 위 | | `criticalSolid` | 삭제처럼 위험한 액션을 아이콘으로 표시할 때. 드물게 | | `brandSolid` | 플로팅 액션 버튼. 화면에 하나 | `ghost` 는 배경이 없어 **눌린다는 신호가 약합니다.** ### 상태 Button 과 같습니다. `loading` 일 때는 아이콘 자리에 스피너가 들어갑니다. ### Props Button 과 같은 props 에 `layout="iconOnly"` 를 지정합니다. | Prop | Type | Default | 필수 | 설명 | |---|---|---|---|---| | `layout` | `"withText" \| "iconOnly"` | `"withText"` | | `"iconOnly"` 는 정사각형이 되고 라벨을 숨깁니다. | | `size` | `"xsmall" \| "small" \| "medium" \| "large"` | `"medium"` | | 버튼과 아이콘 크기를 결정합니다. | | `variant` | Button 과 동일 (7종) | `"neutralSolid"` | | 강조도와 색을 결정합니다. | | `icon` | `ReactNode` | | ● | 표시할 아이콘입니다. | | `aria-label` | `string` | | ● | 액션 이름입니다. **생략할 수 없습니다.** | | `loading` | `boolean` | `false` | | 아이콘 자리에 스피너를 노출합니다. | | `disabled` | `boolean` | `false` | | 비활성 상태로 만듭니다. | `children` 은 받지 않습니다. `width="full"` 은 정사각형이 깨지므로 쓰지 않습니다. ### Component tokens Button 과 같습니다. 추가되는 규칙은 아래뿐입니다. | 대상 | 속성 | 값 | |---|---|---| | `.atom-button--layout_icon-only` | `width` | `47px` (medium 높이와 동일) | | `.atom-button--layout_icon-only` | `padding-inline` | `0` | | `+ .atom-button--size_xsmall` | `width` | `32px` | | `+ .atom-button--size_small` | `width` | `40px` | | `+ .atom-button--size_large` | `width` | `54px` | > [!WARNING] > **저장소에 또 하나의 아이콘 버튼이 있습니다** > `styles/chrome.css` 의 `.atom-icon-button` 은 36×36px 에 18px 아이콘을 쓰는 별개 구현입니다. 문서 사이트의 헤더 테마 전환·메뉴 버튼에 쓰입니다. > 엘리먼트가 아니라 사이트 chrome 전용이라 `styles/elements/` 밖에 있지만, **`--layout_icon-only` 의 어떤 사이즈와도 크기가 맞지 않습니다**(32 · 40 · 47 · 54px). > 이름도 거의 같아 혼동을 일으킵니다. 이 디자인 시스템에는 포함하지 않습니다(원본 registry `nonAssetClasses`). ### 접근성 - **`aria-label` 은 필수입니다** — `aria-label` 이 없는 아이콘 버튼은 스크린리더에서 "버튼" 이라고만 읽혀 용도를 알 수 없습니다. - **아이콘에는 `aria-hidden="true"`** — 아이콘은 읽히지 않게 하고, 이름은 버튼의 `aria-label` 이 담당합니다. - **`aria-label` 과 툴팁 문구를 일치시킵니다** — 눈으로 보는 이름과 스크린리더가 읽는 이름이 다르면 음성 제어 사용자("검색 눌러")가 버튼을 찾지 못합니다. - **키보드** — `Tab` 으로 포커스, `Enter` · `Space` 로 실행됩니다. 툴팁은 hover 뿐 아니라 **포커스에도 나타나야** 키보드 사용자가 이름을 볼 수 있습니다. - **터치 영역** — `xsmall`(32px)과 `small`(40px)은 권장 최소 44×44px 에 못 미칩니다. 라벨이 없어 타깃을 넓힐 방법도 없으므로, 모바일에서 단독으로 놓이는 아이콘 버튼은 `medium`(47px) 이상을 씁니다. 시각적 크기를 유지하면서 타깃만 넓히려면 투명한 `::after` 로 히트 영역을 확장합니다. - **터치 기기의 툴팁** — hover 가 없어 툴팁이 뜨지 않습니다. 툴팁으로만 아이콘의 뜻을 설명하면 모바일에서는 뜻을 알 수 없습니다. 의미가 널리 알려진 아이콘만 쓰는 이유입니다. - **`ghost` 의 대비** — 배경이 없으므로 아이콘 색의 대비가 UI 요소의 대비입니다. `--atom-fg-neutral`(17.19:1)은 충분하지만, `--atom-fg-neutral-subtle`(2.57:1)로 아이콘을 그리면 WCAG 1.4.11 의 3:1 에 미달합니다. - **강제 색상 모드** — Windows 고대비 모드에서 `ghost` 아이콘 버튼은 배경도 테두리도 없어 버튼임을 알 수 없습니다. `forced-colors` 대응은 아직 없습니다. ### 사용 가이드 #### 의미가 관례로 굳은 아이콘에만 닫기(`×`), 더보기(`⋯`), 검색, 뒤로가기, 삭제(휴지통), 공유처럼 **의미가 널리 알려진 아이콘**만 씁니다. 그 밖의 개념은 아이콘만으로 의미를 알기 어렵습니다. - ✅ **Do** 닫기·더보기·검색처럼 관례가 있는 액션에 씁니다. - ❌ **Don't** 서비스 고유 개념을 아이콘만으로 표현하지 않습니다. ATOM 의 아이콘 스프라이트에서 아이콘 버튼에 쓸 만한 것은 `close` · `search` · `trash` · `plus` · `menu` 정도입니다(`implement/src/components/icons.tsx`). #### 단독으로 놓일 때는 배경이 있는 variant 를 - ✅ **Do** 고립된 아이콘 버튼은 `neutralWeak` 나 `neutralOutline` 으로 면을 줍니다. - ❌ **Don't** 단독 아이콘 버튼을 `ghost` 로 두지 않습니다. #### 툴팁으로 이름을 보여줍니다 마우스를 쓰는 사용자가 아이콘의 뜻을 확인할 방법은 [Tooltip](./tooltip.md) 뿐입니다. 단축키가 있다면 함께 넣습니다. - ✅ **Do** hover 시 액션 이름을 툴팁으로 띄웁니다. - ⚠️ **Caution** 터치 기기에는 hover 가 없습니다. 툴팁만으로 해결되지 않습니다. - ❌ **Don't** 툴팁을 유일한 설명 수단으로 삼지 않습니다. `aria-label` 은 항상 함께 둡니다. #### 나란히 놓을 때는 간격을 둡니다 아이콘 버튼은 라벨이 없어 시각적 경계가 약합니다. 최소 `--atom-x3`(12px) 를 둡니다. - ✅ **Do** 버튼 사이에 최소 12px 를 둡니다. - ❌ **Don't** 아이콘 버튼을 여백 없이 붙여 놓지 않습니다. #### 한 줄에 네 개를 넘기지 않습니다 아이콘이 다섯 개 넘게 늘어서면 하나씩 확인해야 합니다. 그보다 많으면 Dropdown 으로 묶고, 자주 쓰는 것 두세 개만 밖에 둡니다. #### 라벨을 붙일 수 있으면 붙입니다 공간이 있으면 라벨이 있는 Button 을 씁니다. 아이콘 버튼은 공간이 부족할 때만 씁니다. ### 예시 ```tsx ```