Va.Radio — 라디오 버튼 (단일 배타 선택 항목)
체크박스와 시각적으로 비슷하지만 배타 선택을 의도한 컴포넌트. 하나만 켜지고 나머지는 꺼져야 하는 상황용입니다. 단독으로도 쓸 수 있지만 여러 개를 그룹지어 사용하는 게 정석 — 그때는 Va.RadioGroup을 사용합니다.
- 클래스: Va.Radio — va_component.js:10634
- short name: radio
- 상속: Va.PureField (Checkbox와 형제)
- isContainer: true
- 베이스 CSS: va-radio

1. 기본 사용
{
tagName: 'radio',
radioLabel: '옵션 A',
checked: true,
onChange: 'onChange'
}
- 좌측: 라디오 버튼 아이콘 (원 안 채워짐 or 빈 원)
- 우측: 라벨 (옵션 A)
- 라벨 클릭도 선택으로 동작
2. Va.Radio의 특징 및 Va.Checkbox와의 차이
Radio와 Checkbox는 소스가 매우 비슷합니다. 핵심 차이만 표로:
항목Va.RadioVa.Checkbox
| 선택 개념 | 배타 (하나만) | 다중 (여러 개) |
| 클릭 시 | checked=true만 (해제 없음) | checked 토글 |
| 3상 상태 (mixed) | ✕ | ✓ |
| HTML type | radio | checkbox |
| 아이콘 | ico_radio_button_* | ico_checkbox_* |
| role ARIA | radio | checkbox |
| 라벨 옵션 이름 | radioLabel | checkboxLabel |
| 라벨 클릭 옵션 | radioLabelClick | checkboxLabelClick |
| valueType | ✓ (반환값만 반영, 실제 배타 로직은 없음) | ✓ |
⚠️ Va.Radio 자체는 "배타" 로직을 담고 있지 않습니다. 클릭 시 checked = true가 되기만 하고, 형제 Radio를 자동으로 해제하지 않아요. 배타 선택은 Va.RadioGroup이 담당합니다. Radio를 단독으로 여러 개 나열하면 모두 체크될 수 있음.
3. 주요 속성
체크 상태
속성기본값설명
| checked | false | 상태값. true/'true'만 체크로 인식 (Checkbox처럼 Y/N/1은 코드상 인식 시도) |
| valueType | undefined | 'YN' / '10' / 미지정 (표준 boolean) — getChecked() 반환값에만 영향 |
| selected | false | 별개 개념 (checked와 다름) |
라벨
속성기본값설명
| radioLabel | — | 우측에 표시되는 라벨 텍스트 |
| radioLabelClick | true | 라벨 클릭으로도 선택 가능 |
데이터 (RadioGroup에서 활용)
속성설명
| key | 값으로 저장할 필드명 |
| display | 표시 필드명 |
PureField 상속
readonly, disabled, size, appearance, stopPropagation 등 표준.
4. 이벤트
이벤트시그니처발생 시점
| change | (component, element, checked, evt) | 체크 상태 변경 시. checked가 새 상태값 |
| click | (component, element, evt) | 클릭 시 |
| keydown | (component, element, keyCode, evt) | 스페이스 눌러 선택 시 |
| focus / blur | (component, element, evt) | 포커스 진입/이탈 |
⚠️ keydown 리스너가 두 번 등록됨 — va_component.js:10692와 va_component.js:10711에서 각각. 스페이스 키 눌렀을 때 dispatchEvent('keydown', ...)가 두 번 실행될 수 있습니다. 콜백 방어 필요.
change 콜백 예시
onSelectOption(comp, el, checked, evt) {
console.log('선택됨:', checked); // true (항상)
}
Radio는 클릭 시 항상 checked = true로 바뀌므로 콜백 인자는 사실상 항상 true.
5. 메서드
상태 조회·변경
메서드설명
| getChecked() | 현재 값 반환. valueType 규약에 맞춰 반환 |
| setChecked(value) | 상태 세팅. true/false 등 다양한 형태 수용 |
| check() | true로 세팅 (편의) |
| uncheck() | false로 세팅 (편의) |
포커스
메서드설명
| focus() | fieldWrapperElement에 포커스 (실제 <input>이 아님) |
| blur() | 블러 |
상태 (PureField 상속)
메서드설명
| setDisabled(bool) / setReadOnly(bool) | 상태 |
주의: 배타 선택 로직은 개발자가 직접 관리해야 하거나 RadioGroup을 사용해야 합니다.
6. 내부 구조
<div elname="element" class="va-radio [checked] [focused] [disabled]"
tag-name="radio">
<div elname="inner" class="radio-inner">
<div elname="fieldWrapper" class="field-wrapper" tabindex="0"
role="radio" aria-checked="true|false" aria-label="옵션 A">
<input elname="field" type="radio" style="display:none">
<span elname="icon" class="icon ico_radio_button_checked">◉</span>
</div>
<label elname="radioLabel" class="label">옵션 A</label>
</div>
</div>
핵심 트릭 (Checkbox와 동일 패턴):
- <input type="radio">는 display:none — 시각은 아이콘이 담당
- fieldWrapper가 실제 tabindex 대상
- 아이콘 종류:
- ico_radio_button_unchecked — 빈 원
- ico_radio_button_checked — 채워진 원
- ARIA 자동 — role="radio", aria-checked, aria-label
7. 접근성 (a11y)
VanillaFront Radio는 접근성이 잘 되어 있습니다.
요소값
| role | "radio" (자동) |
| aria-checked | "true" / "false" (자동) |
| aria-label | radioLabel 값 (자동) |
| tabindex | 0 (Tab으로 접근 가능) |
| 스페이스 키 | 선택 (자동 처리) |
스크린리더 사용자가 정확히 인식합니다.
⚠️ 그룹 안에서 화살표 키로 항목 이동하는 표준 라디오 UX는 RadioGroup이 담당해야 합니다. Radio 단독으로는 그 로직이 없어요.
8. 왜 Radio 단독 사용은 드물까
Radio가 진정한 가치를 발휘하는 건 그룹 안에서입니다. 단독 Radio는:
- 클릭해도 배타 로직이 없음 → 모두 체크될 수 있음
- 대개 "하나만 선택"이라는 시각적 힌트를 주지만, 실제로는 값 관리가 어려움
- 그럴 바엔 Checkbox나 Switch를 쓰는 게 더 명확함
실제 사용은 대부분:
- Va.RadioGroup — 데이터 배열로 여러 Radio를 자동 관리
- Va.RadioGroupField — 그룹 + 라벨·검증
Radio 단독은 커스텀 UI를 만들 때 벽돌로 조립하는 경우 정도.
9. Radio 계열 형제 컴포넌트
컴포넌트역할
| Va.Radio | 단일 라디오 버튼 (이 문서) — 실무 단독 사용 드묾 |
| Va.RadioField | Radio + Field 래퍼 (라벨/검증) |
| Va.RadioGroup | 여러 Radio를 배열로 관리 + 배타 로직 자동 |
| Va.RadioGroupField | RadioGroup + Field 래퍼 |
10. 언제 쓰나
Radio 단독이 맞을 때 (드묾)
- 커스텀 UI를 element 단위로 조립할 때
- 단일 옵션이면서 시각적 관행상 라디오가 어울릴 때 (매우 드물지만)
RadioGroup이 나을 때
- 여러 옵션 중 하나만 선택 — 성별, 결제 방식, 등급 등
- 데이터 배열로 처리하고 싶을 때
- 배타 로직이 필요할 때 (대부분의 경우)
Checkbox가 맞을 때
- 여러 선택 가능
- 단일 on/off (동의 등)
다른 선택지
- ON/OFF 스위치 UI → Va.Switch
- 툴바 배타 토글 → Va.ToggleButton + Va.ButtonGroup
- 배타 세그먼트 → Va.SegmentedControl
11. 흔한 조합 예시
// 표준 (단독 사용, 드묾)
{
tagName: 'radio',
radioLabel: '옵션 A',
checked: true,
onChange: 'onSelect'
}
// 두 개를 나란히 (수동 배타 관리 필요)
{
tagName: 'div',
layout: 'ds-flex fd-row gap-m',
tags: [
{ tagName: 'radio', radioLabel: '남', ref: 'male', onChange: 'onGender' },
{ tagName: 'radio', radioLabel: '여', ref: 'female', onChange: 'onGender' }
]
}
// 위 코드의 배타 로직
onGender(comp, el, checked, evt) {
if (comp === this.getRef('male')) this.getRef('female').setChecked(false);
if (comp === this.getRef('female')) this.getRef('male').setChecked(false);
}
// ...RadioGroup이 훨씬 간단
{
tagName: 'radioGroup',
key: 'code',
display: 'name',
data: [
{ code: 'M', name: '남' },
{ code: 'F', name: '여' }
]
}
12. 알아두면 좋을 주의사항
- 단독 Radio는 배타 로직 없음 — 클릭 시 checked = true만. 형제 Radio 자동 해제 안 됨.
- checked 인식 형태 제한 — 코드상 true/'true'만 정확히 체크로 처리. Y/N/1은 getChecked() 반환에만 반영됨.
- change 이벤트 시 checked는 항상 true — 클릭 시 무조건 true로 바뀌므로.
- keydown 리스너 두 번 등록 — 스페이스 시 이벤트 중복 위험. 방어 코드.
- <input> 자체는 숨겨짐 — 시각은 아이콘. 폼 서브밋 참여.
- fieldWrapper가 실제 focus 대상 — 표준 <input> 포커스 아님.
- 스페이스 키로 선택 — 표준 라디오 UX. keyCode 32 처리.
- uncheck()는 사용자 액션으로 발생 안 함 — 개발자가 명시 호출해야 해제.
- radioLabel 없으면 aria-label도 없음 — 접근성 고려해 라벨 지정 권장.
- radioLabelClick: false로 라벨 클릭 무시 가능 — 실수 방지 등.
- 화살표 키 그룹 네비 없음 — RadioGroup에서 담당해야 하는 로직.
- focus()는 fieldWrapper에 포커스 — CSS 클래스로 시각 표시.
- 읽기 전용·비활성화 상태에서 이벤트 무시 — 클릭·키다운 모두 조기 return.
13. radio vs checkbox vs switch vs toggleButton 선택
상황추천
| 여러 옵션 중 하나만 선택 | radioGroup (Radio 단독 아님) |
| 여러 옵션 중 여러 개 선택 | checkboxGroup |
| ON/OFF 단일 (동의 등) | checkbox |
| ON/OFF 스위치 형태 | switch |
| 툴바 배타 토글 (bold/italic 계열) | toggleButton + buttonGroup |
| 세그먼트 컨트롤 | segmentedControl |
| 드롭다운으로 선택 | combobox |
"단일 Radio를 여러 개 나열하지 마세요. 그건 RadioGroup의 일입니다."
14. 실전 팁
RadioGroup을 쓰기 어려운 상황
폼 구조가 이미 복잡해서 데이터 배열로 정리하기 어렵고, 그냥 Radio 몇 개를 다른 컴포넌트와 섞어 배치해야 할 때. 이 경우 배타 로직을 부모 View가 관리하는 패턴:
class Manual extends Va.View {
onSelectA(c, e, checked) { this._select('A'); }
onSelectB(c, e, checked) { this._select('B'); }
onSelectC(c, e, checked) { this._select('C'); }
_select(key) {
['A', 'B', 'C'].forEach(k => {
this.getRef('r' + k).setChecked(k === key);
});
this.selected = key;
}
// ...
}
이 정도까지 오면 대부분 RadioGroup으로 리팩터링하는 게 낫습니다.
name 속성을 이용한 HTML 표준 배타
HTML 표준상 같은 name을 가진 <input type="radio">는 자동 배타됩니다. VanillaFront Radio는 <input>이 display:none이라 이 동작이 직접 시각화엔 반영 안 되지만, 폼 서브밋 시 하나만 선택된 것으로 처리되긴 합니다. 다만 시각적 배타는 별도로 관리해야 하니, 이 방식에 기대지 마세요.
'컴포넌트 > 필드 컴포넌트' 카테고리의 다른 글
| RadioGroup (라디오 그룹) (0) | 2026.09.12 |
|---|---|
| RadioField (라디오 필드) (1) | 2026.09.12 |
| CheckboxGroupField (체크박스그룹필드) (0) | 2026.09.12 |
| CheckboxGroup (체크박스그룹) (0) | 2026.09.12 |
| CheckboxField (체크박스필드) (0) | 2026.09.12 |