PrimaryButton
서비스 주색 그라데이션으로 채운 알약 모양 버튼이다. 한 화면에서 가장 중요한 행동 하나에 쓴다.
- 분류
- 버튼
- 가져오기
import { PrimaryButton } from '@/ui/Button'- 코드 위치
apps/mobile/src/ui/Button.tsx:23
미리보기#
실제 컴포넌트를 화면에 그린 것이다. 화면 아래의 서비스 색 선택기로 색을 바꿔 볼 수 있다. 버튼과 입력 칸은 직접 눌러 볼 수 있다.
언제 쓰는가#
쓴다
- 사용자가 다음에 해야 할 가장 중요한 행동 하나에 쓴다. 예: "저장", "다음으로", "동의하고 시작하기".
- 한 화면에 하나만 둔다. 화면 아래에 고정할 때는
Screen의footer에 넣는다. - 저장이나 전송처럼 결과를 기다리는 동안에는
busy를 켠다. 글자가 스피너로 바뀌고 다시 눌리지 않는다. - 다음 화면으로 넘어가는 행동에는 화살표를 둔다(기본값). 저장, 확인, 재시도처럼 화면을 넘기지 않는 행동에는
arrow={false}를 쓴다.
쓰지 않는다
- 한 화면에 두 개 이상 놓지 않는다. 두 번째 행동에는
SecondaryButton이나TextButton을 쓴다. - 삭제처럼 되돌릴 수 없는 행동에 쓰지 않는다.
dialog.confirm을tone: "danger"로 호출해 한 번 더 확인한다. - 목록 행 안에 쓰지 않는다. 행 안의 작은 행동에는
SmallButton을 쓴다. - 사용할 수 없는 이유를 숨긴 채 비활성으로 두지 않는다. 이유를 근처 글자로 알린다.
종류#
| 이름 | 설명 |
|---|---|
| 화살표 있음 (기본) | arrow가 true다. 글자 뒤에 화살표 아이콘이 붙는다. 다음 화면으로 넘어가는 행동에 쓴다. |
| 화살표 없음 | arrow={false}. 저장, 확인, 재시도처럼 화면을 넘기지 않는 행동에 쓴다. 앱에서 가장 많이 쓰는 모양이다. |
| 앞쪽 아이콘 | icon에 17~18px 아이콘을 넣는다. 글자 앞에 놓인다. 아이콘 색은 직접 white로 지정한다. |
크기#
| 이름 | 값 | 설명 |
|---|---|---|
| 기본 | 높이 52 | height를 생략하면 52다. |
| 시트·하단 영역 | 높이 54, 56 | 앱의 시트와 하단 고정 영역에서 height={54} 또는 height={56}을 쓴다. 이 외의 값은 쓰지 않는다. |
상태#
| 상태 | 모습과 동작 |
|---|---|
| 기본 | 그라데이션 배경, 그림자 shadow(1). |
| 누름 | 0.985배로 살짝 줄어든다. 그림자와 색은 그대로다. 누르는 순간 가벼운 진동(haptic.tap)이 난다. |
| 비활성 | 투명도 0.45. 눌러도 아무 일도 일어나지 않고 진동도 없다. |
| 처리 중 | 글자 대신 스피너를 보여 준다. 크기는 그대로이고 눌러도 반응하지 않는다. |
사용 방법#
기본
기본
import { PrimaryButton } from '@/ui/Button';
<PrimaryButton label="다음으로" onPress={next} />저장 버튼: 처리 중과 비활성
저장 버튼: 처리 중과 비활성
<PrimaryButton
label="저장"
arrow={false}
busy={saving}
disabled={!changed}
onPress={() => void save()}
/>화면 아래에 고정
화면 아래에 고정
import { Screen } from '@/ui/layout';
<Screen footer={<PrimaryButton label="완료" arrow={false} height={56} onPress={done} />}>
{/* 화면 내용 */}
</Screen>속성(props)#
| 이름 | 타입 | 기본값 | 필수 | 설명 |
|---|---|---|---|---|
label | string | — | 필수 | 버튼에 쓰는 글자. 한 줄로 보여 주고, 길면 글자를 80%까지 줄인다. |
onPress | () => void | — | 선택 | 누르면 실행할 함수. 없거나 disabled, busy이면 눌러도 실행하지 않는다. |
disabled | boolean | — | 선택 | true이면 투명도 0.45로 바꾸고 누를 수 없게 한다. |
busy | boolean | — | 선택 | true이면 글자 대신 스피너를 보여 주고 누를 수 없게 한다. 접근성 상태에도 busy가 전달된다. |
arrow | boolean | true | 선택 | false이면 글자 뒤의 화살표를 숨긴다. |
icon | ReactNode | — | 선택 | 글자 앞에 놓을 아이콘. 흰색(color.white)으로 그린다. |
style | StyleProp<ViewStyle> | — | 선택 | 바깥 여백이나 배치만 바꾸는 React Native 스타일이다. 색, 높이, 모서리는 바꾸지 않는다. |
height | number | 52 | 선택 | 버튼 높이(px). 앱은 52, 54, 56만 쓴다. |
accessibilityHint | string | — | 선택 | 화면 읽기 프로그램이 버튼 이름 뒤에 읽어 주는 설명. |
치수와 토큰#
- 높이
- 52 (기본)
- 모서리
- pill(999)
- 좌우 안쪽 여백
- 20
- 글자
- button (15/20, Bold), 색 white(웹에서는 onPrimaryStrong)
- 배경
- gradient: gradientFrom → gradientTo, 왼쪽에서 오른쪽(90°)
- 그림자
- shadow(1)
- 아이콘
- ArrowRight 18px, 선 두께 2.4
- 글자와 아이콘 사이
- 6
접근성#
- 역할은
button이다. 접근성 상태에disabled와busy가 들어간다. - 버튼 높이가 52 이상이라 눌러야 하는 영역의 최소 크기(48×48)를 넘는다.
- 앱은 터치 화면이라 마우스 올림(hover)과 키보드 포커스 표시가 없다. 웹에서 같은 모양을 만들 때는 포커스 링(2.5px 실선, 바깥쪽 3px 띄움)을 직접 그린다. 이 문서의 미리보기에는 포커스 링이 그려져 있다.
- Android에서는 흰색 물결(ripple)이 눌림 표시를 대신한다. 이때 누름 크기 변화(0.985배)는 유지된다.
글자 색 대비
글자 크기별 기준: 작은 글자는 4.5:1 이상, 큰 글자(굵은 18.66px 이상)는 3:1 이상이다. 값은 선택한 서비스와 모드의 토큰으로 계산했다.
| 쓰임 | 글자색 | 바탕 | 대비 | 기준 |
|---|---|---|---|---|
| 버튼 글자 (15px 굵게) | onPrimaryStrong | gradient (양 끝 중 낮은 값) | 3.08:1 미달3.08:1 미달4.60:1 통과5.48:1 통과 | 4.5:1 (작은 글자) |
잘한 예와 잘못한 예#
함께 보는 컴포넌트#
- SecondaryButton: 테두리만 있거나 옅은 주색으로 채운 알약 모양 버튼이다. PrimaryButton 옆의 두 번째 행동에 쓴다.
- TextButton: 글자만 있는 가장 가벼운 버튼이다. "나중에", "건너뛰기" 같은 선택 행동에 쓴다.
- SmallButton: 목록 행 안에 놓는 작은 사각형 버튼이다. "풀기", "승인", "거절" 같은 한 단어 행동에 쓴다.
- Screen: 모든 화면의 바탕이다. 안전 영역, 키보드, 스크롤, 좌우 여백, 아래 고정 영역을 한 번에 처리한다.
- dialog: 화면 가운데 뜨는 확인 대화상자와 아래에서 올라오는 메뉴를 함수 호출 하나로 여는 도구다. 결과(눌린 버튼)를 약속(Promise)으로 돌려준다.