컴포넌트/필드 컴포넌트

Radio (라디오 버튼)

VanillaFront 2026. 9. 12. 19:42

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. 알아두면 좋을 주의사항

  1. 단독 Radio는 배타 로직 없음 — 클릭 시 checked = true만. 형제 Radio 자동 해제 안 됨.
  2. checked 인식 형태 제한 — 코드상 true/'true'만 정확히 체크로 처리. Y/N/1은 getChecked() 반환에만 반영됨.
  3. change 이벤트 시 checked는 항상 true — 클릭 시 무조건 true로 바뀌므로.
  4. keydown 리스너 두 번 등록 — 스페이스 시 이벤트 중복 위험. 방어 코드.
  5. <input> 자체는 숨겨짐 — 시각은 아이콘. 폼 서브밋 참여.
  6. fieldWrapper가 실제 focus 대상 — 표준 <input> 포커스 아님.
  7. 스페이스 키로 선택 — 표준 라디오 UX. keyCode 32 처리.
  8. uncheck()는 사용자 액션으로 발생 안 함 — 개발자가 명시 호출해야 해제.
  9. radioLabel 없으면 aria-label도 없음 — 접근성 고려해 라벨 지정 권장.
  10. radioLabelClick: false로 라벨 클릭 무시 가능 — 실수 방지 등.
  11. 화살표 키 그룹 네비 없음 — RadioGroup에서 담당해야 하는 로직.
  12. focus()는 fieldWrapper에 포커스 — CSS 클래스로 시각 표시.
  13. 읽기 전용·비활성화 상태에서 이벤트 무시 — 클릭·키다운 모두 조기 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이라 이 동작이 직접 시각화엔 반영 안 되지만, 폼 서브밋 시 하나만 선택된 것으로 처리되긴 합니다. 다만 시각적 배타는 별도로 관리해야 하니, 이 방식에 기대지 마세요.