컴포넌트/필드 컴포넌트

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:10692va_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이라 이 동작이 직접 시각화엔 반영 안 되지만, 폼 서브밋 시 하나만 선택된 것으로 처리되긴 합니다. 다만 시각적 배타는 별도로 관리해야 하니, 이 방식에 기대지 마세요.