본문으로 건너뛰기

선택 컨트롤

Checkbox · RadioGroup · Switch · SegmentedControl 입니다. 모두 작은 키캡이고 두께는 (1,1), 누르면 1px 대각으로 가라앉습니다. radix-ui 위에 만들어 ./form 에서 가져옵니다.

import
import { Checkbox, … } from "@uyoolabs/ui/form";
쓰는 경우
제출해야 반영되는 켬/끔 · 동의 · 여럿 고르기 · 표 행 선택은 Checkbox, 누르는 즉시 저장되는 켬/끔 하나는 Switch, 설명이 필요한 하나 고르기(2~6개)는 RadioGroup, 툴바 · 카드 머리의 보기 · 기간 · 거르기(2~5칸)는 SegmentedControl 을 씁니다.
쓰지 않는 경우
저장 버튼이 있는 폼 안에 Switch 를 두지 않습니다(눌렀는데 아직 반영되지 않았습니다). 스위치 라벨에 동작 · 상태(“알림 켜기” · “켜짐”)를 쓰지 않습니다. 세그먼트로 아래 패널을 갈아 끼우지 않습니다(Tabs). 7개 이상은 Select 입니다. 선택을 색만으로 말하지 않고, 못 고르는 항목은 설명 줄에 이유를 적습니다.
상태
선택은 체크 · 점 · 손잡이 위치 · 굵은 글자로도 말합니다. hover 는 작은 컨트롤의 모양을 바꾸지 않습니다. 비활성은 투명도 대신 파인 면과 흐린 글자로, 오류는 경계 색과 묶음 아래 문장 한 번으로 보입니다.

무엇을 쓸까

상황컨트롤예
켬/끔 하나, 누르는 즉시 저장 · 적용Switch멘션 알림 · 초대 링크 사용
켬/끔 하나, 제출해야 반영 · 동의Checkbox약관 동의 · “다시 묻지 않기”
여럿 고르기 · 표 행 선택Checkbox 묶음요약 받을 채널 · 작업 표
하나 고르기 2~6개, 설명이 필요하거나 폼 안RadioGroup기본 역할 · 보관 기간
하나 고르기 2~5개, 짧은 이름, 툴바 · 카드 머리SegmentedControl보기 · 기간 · 거르기
하나 고르기 7개 이상, 또는 자리가 좁음Select기본 언어 · 시간대

Checkbox

두 상태 · 세 상태(일부). 라벨 글자까지 누르는 자리입니다.

약관

약관에 동의해야 결제를 진행할 수 있습니다.

주간 요약을 받을 채널

RadioGroup

묶음 이름은 FieldGroup 의 legend 를 render-prop 으로 받습니다. 화살표가 옮기며 바로 고릅니다.

Switch

누르는 즉시 저장되는 켬/끔 하나에만 씁니다. 손잡이를 바로 옮기고 저장 중 · 저장됨 · 실패를 줄의 SaveStatus 로 보입니다.

누군가 나를 @멘션하면 바로 알립니다.
매주 월요일 아침에 안 읽은 스레드를 보냅니다. 데모에서는 켜면 실패합니다.
켬

SegmentedControl

radiogroup 입니다(결정 #12). 고른 면이 220ms 로 미끄러지고, 줄인 모션에서는 바로 옮깁니다. 6칸 이상이면 개발 모드에서 경고합니다.

네이티브 클래스

radix 를 설치하지 않은 소비자(서버 폼)는 checkboxClass · radioClass 를 네이티브 input 에 붙입니다. 모양과 상태가 컴포넌트와 같습니다(결정 #10).

키보드

Checkbox

키동작
Space켬 ↔ 끔. 일부에서 누르면 켬
Enter토글하지 않습니다. 폼 안이면 폼에 맡깁니다

RadioGroup · SegmentedControl

키동작
Tab묶음에 한 번 들어옵니다. 고른 항목(없으면 첫 항목)에 멈추고, 다음 Tab 은 묶음 밖
↓ · →다음 항목으로 옮기며 바로 고릅니다. 끝에서 처음으로 돌고, 비활성 항목은 건너뜁니다
↑ · ←이전 항목으로 옮기며 고릅니다
Home · End처음 · 끝 항목으로 갑니다
Space포커스한 항목을 고릅니다

Switch

키동작
Space · Enter켬 ↔ 끔. 저장 중(pending)에는 무시합니다

props

소스의 타입과 JSDoc 에서 생성한 표입니다. 컴포넌트를 고치면 pnpm gen 으로 다시 만듭니다.

Checkbox

prop타입기본값설명
labelReactNode—보이는 라벨. 주면 <label data-slot="choice"> 로 감싸 라벨까지 누르는 자리가 된다. 주지 않으면 aria-label 또는 aria-labelledby 가 반드시 있어야 한다(표 행: "PAY-142 선택")
descriptionReactNode—라벨 아래 설명 한 줄(13px · muted). aria-describedby 로 잇는다. 비활성 이유도 여기에 쓴다
invalidboolean—오류 경계. aria-invalid 를 단다. 오류 문장은 Field · FieldGroup 의 error 가 맡는다

이 밖에 Radix Checkbox.Root props(children · asChild 제외)를 그대로 받습니다.

RadioGroup

prop타입기본값설명
invalidboolean—묶음 오류. 루트(role="radiogroup")에 aria-invalid, 고르지 않은 항목의 경계를 오류 색으로

이 밖에 Radix RadioGroup.Root props(asChild 제외)를 그대로 받습니다. 기본값을 바꾼 것은 orientation="vertical" 입니다.

Radio

prop타입기본값설명
label필수ReactNode—보이는 라벨. <label data-slot="choice"> 로 감싸 라벨 글자까지 누르는 자리가 된다
descriptionReactNode—라벨 아래 설명. aria-describedby 로 잇는다. 비활성 항목은 이유를 여기에(“소유자만 고를 수 있습니다.”)

이 밖에 Radix RadioGroup.Item props(children · asChild 제외)를 그대로 받습니다.

Switch

prop타입기본값설명
labelReactNode—켜지는 것의 이름(“멘션 알림”). 동작 · 상태(“알림 켜기” · “켜짐”)를 쓰지 않는다. 없으면 aria-label 이 필요하다
descriptionReactNode—라벨 아래 설명. aria-describedby 로 잇는다
layout"inline" | "row""inline"inline: 스위치 → 라벨. row: 라벨 · 설명 왼쪽, 스위치 오른쪽 끝(설정 줄, 줄 사이 헤어라인)
size"sm" | "md""md"md 36×20 · sm 28×16. sm 은 손잡이 안 체크가 없어 옆에 상태 글자를 둔다
pendingbooleanfalse저장 중. 손잡이에 스피너, aria-busy, 클릭 · 키 입력을 무시한다(포커스는 둔다)
statusReactNode—row 레이아웃에서 스위치 왼쪽 자리(SaveStatus · 다시 시도). inline 에서는 그리지 않는다
errorReactNode—저장에 실패한 이유와 할 일(되돌린 뒤 줄 아래에 보인다). aria-describedby 로 잇는다
lang"ko" | "en" | "ja"—내장 문구의 언어
labelsPartial<SwitchLabels>—내장 문구 중 바꿀 것만

이 밖에 Radix Switch.Root props(children · asChild 제외)를 그대로 받습니다.

SegmentedControl

prop타입기본값설명
label필수string—축 이름. 화면에 그리지 않고 aria-label 로만 쓴다(칸 이름이 이미 축을 말한다)
options필수ReadonlyArray<SegmentedOption<T>>—2~5개. 6개 이상이면 개발 모드에서 경고한다(Select 를 쓴다)
valueT—고른 칸(제어)
defaultValueT—처음 고른 칸(비제어). 없으면 첫 칸
onValueChange(value: T) => void—다른 칸을 골랐을 때만 부른다. 고른 칸을 다시 눌러도 부르지 않는다(빈 값이 없다)
fillboolean—균등 폭. 좁은 패널(시트 · 카드 머리)에서만
disabledboolean—묶음 전체 비활성
namestring—form 안이면 숨은 radio 로 FormData 에 들어간다
classNamestring—

import

./form 은 radix-ui 가 peer 로 필요합니다. 내장 문구는 Switch 의 저장 중 설명 하나(lang · labels)뿐이고, 나머지 글자는 모두 소비자가 줍니다.