jQuery / Selector / :checked - 체크박스, 라디오 버튼에서 체크한 요소 선택하는 선택자

jQuery의 :checked는 현재 체크된 체크박스·라디오와 select의 선택된 option을 고르는 선택자입니다. 체크박스 값만 읽으려면 input[type="checkbox"]:checked처럼 종류와 범위를 제한합니다. 여러 선택의 값은 map으로 배열을 만들고, 라디오는 같은 name 그룹의 체크된 요소에서 값을 읽습니다.

현재 체크와 value를 구분하기

:checked 공식 문서는 option까지 포함합니다. 폼 전체의 :checked 개수는 체크박스 개수와 다를 수 있으므로, name과 type으로 원하는 그룹을 고릅니다. 예제는 channels 체크박스와 plan 라디오를 따로 처리하고, Priority의 선택 option이 전체 개수에 포함되는 것도 보여줍니다.

체크박스의 .val()은 체크 여부가 아니라 지정된 value를 읽습니다. SMS를 체크하지 않아도 .val()은 sms입니다. 체크 여부는 한 요소에 .prop("checked"), 집합 중 하나라도 맞는지는 .is(":checked"), 체크된 요소 집합은 .filter(":checked")로 구분합니다. 여러 요소의 .prop("checked") getter는 첫 요소만 읽습니다.

라디오는 같은 폼의 같은 name을 그룹으로 묶으면 하나를 고를 때 다른 선택이 풀립니다. 값이 필요한 대상을 form과 name으로 한정하고, 아무것도 고르지 않았을 때 체크된 집합이 비어 .val()이 undefined가 될 수 있음을 처리합니다.

체크박스·라디오·제출 값을 비교하는 전체 예제

UTF-8의 checked-demo.html로 저장하고 엽니다. 인터넷에서 jQuery 전체 파일 4.0.0을 읽으며 서버나 빌드는 필요 없습니다. Windows Edge에서 4.0.0·3.7.1으로 키보드, 여러 값, 비활성 항목, 동적 change와 초기화를 검증했습니다. 결과는 화면에만 표시하며 서버에 제출하지 않습니다.

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Checked controls and form data</title>
  <script src="https://code.jquery.com/jquery-4.0.0.min.js"></script>
  <style>
    body { font: 16px/1.6 sans-serif; max-width: 760px; margin: 20px; }
    fieldset { margin: 12px 0; }
    label { display: block; }
    button, select { font: inherit; max-width: 100%; margin: 4px; }
    pre { white-space: pre-wrap; overflow-wrap: anywhere; }
  </style>
</head>
<body>
  <form id="preferences">
    <fieldset id="channels"><legend>Channels</legend>
      <label><input id="email" type="checkbox" name="channels" value="email" checked>Email</label>
      <label><input id="sms" type="checkbox" name="channels" value="sms">SMS</label>
      <label><input type="checkbox" name="channels" value="archive" checked disabled>Archive (disabled)</label>
      <fieldset disabled><legend>Unavailable</legend>
        <label><input type="checkbox" name="channels" value="legacy" checked>Legacy</label>
      </fieldset>
    </fieldset>
    <fieldset><legend>Plan</legend>
      <label><input id="basic" type="radio" name="plan" value="basic" checked>Basic</label>
      <label><input id="pro" type="radio" name="plan" value="pro">Pro</label>
    </fieldset>
    <label for="priority">Priority</label>
    <select id="priority" name="priority"><option value="normal">Normal</option><option value="high">High</option></select>
    <button type="button" id="choose">Choose SMS and Pro</button>
    <button type="button" id="clear">Clear enabled channels</button>
    <button type="button" id="restore">Reset</button>
  </form>
  <pre id="result" aria-live="polite"></pre>
  <script>
    jQuery(function ($) {
      const $form = $("#preferences"), form = $form[0];
      let changes = 0;
      function boxes() { return $form.find('input[type="checkbox"][name="channels"]'); }
      function values($elements) { return $elements.map(function () { return this.value; }).get(); }
      function render() {
        const $checked = boxes().filter(":checked"), data = new FormData(form);
        $("#result").text("checkedBoxes=" + JSON.stringify(values($checked)) +
          "\nenabledBoxes=" + JSON.stringify(values($checked.filter(":enabled"))) +
          "\nradio=" + ($form.find('input[name="plan"]:checked').val() || "(none)") +
          "\ncheckedAttributes=" + JSON.stringify(values(boxes().filter("[checked]"))) +
          "\nsubmittedChannels=" + JSON.stringify(data.getAll("channels")) +
          "\nform=" + new URLSearchParams(data).toString() +
          "\nglobalChecked=" + $form.find(":checked").length + "\nchanges=" + changes);
      }
      $form.on("change", "input, select", function () { changes++; render(); });
      $("#choose").on("click", function () {
        boxes().filter(":enabled").prop("checked", false);
        $("#sms").prop("checked", true).trigger("change");
        $("#pro").prop("checked", true).trigger("change");
      });
      $("#clear").on("click", function () { boxes().filter(":enabled").prop("checked", false).trigger("change"); });
      $("#restore").on("click", function () { form.reset(); changes = 0; render(); });
      render();
    });
  </script>
</body>
</html>

초기 출력입니다. 체크된 channels는 세 개지만 제출 가능한 것은 Email 하나입니다. globalChecked는 라디오와 선택된 option까지 포함해 5입니다.

checkedBoxes=["email","archive","legacy"]
enabledBoxes=["email"]
radio=basic
checkedAttributes=["email","archive","legacy"]
submittedChannels=["email"]
form=channels=email&plan=basic&priority=normal
globalChecked=5
changes=0
  1. SMS를 클릭하거나 포커스 후 Space를 누릅니다. submittedChannels는 ["email","sms"]가 되고 두 값 모두 form에 반복된 channels 이름으로 들어갑니다.
  2. Plan에서 Pro를 고릅니다. Basic이 풀리고 radio=pro가 됩니다. 키보드 방향키로 바꿔도 같은 change 경로를 사용합니다.
  3. Reset 후 Choose SMS and Pro를 누릅니다. 활성 channels는 SMS만 선택되며 비활성 Archive·Legacy는 남습니다. radio는 pro, changes는 2입니다.
  4. checkedAttributes에는 여전히 초기 email·archive·legacy가 남습니다. 현재 체크와 기본 속성이 다르기 때문입니다.
  5. Clear enabled channels는 활성 체크박스만 해제합니다. enabledBoxes와 submittedChannels는 []가 되지만 비활성 체크는 유지됩니다.
  6. Reset은 초기 체크·라디오·Priority와 카운터를 복원합니다.

checkedBoxes는 현재 체크된 요소들의 값이고 enabledBoxes는 그중 :enabled만 남긴 값입니다. submittedChannels는 실제 FormData의 getAll 결과입니다. changes는 change 핸들러 실행 횟수로, Clear가 여러 활성 요소에 이벤트를 발생시키면 각각 증가합니다. 사용자 클릭 횟수를 뜻하지 않습니다.

checked 속성·프로퍼티와 이벤트 갱신

HTML [checked]는 속성 존재, DOM의 checked 프로퍼티와 :checked는 현재 체크 상태를 다룹니다. 기본값은 defaultChecked와 연결되므로 사용자가 체크를 풀어도 속성은 남을 수 있습니다. 현재 상태를 [checked]나 .attr("checked")의 진릿값으로 판단하지 않습니다.

.prop("checked", true 또는 false)로 현재 상태를 바꿉니다. 이 setter 자체는 change를 발생시키지 않으므로 예제는 화면 갱신을 위해 .trigger("change")를 호출합니다. 초기 화면도 render를 한 번 실행해 맞춥니다. 프로그램으로 값만 바꾼 뒤 결과 표시가 그대로라면 이벤트·렌더 경로를 확인합니다.

이 예제는 유지되는 form에서 input·select의 change를 위임합니다. 나중에 같은 폼 안에 추가한 항목도 이벤트가 올라오면 처리할 수 있고, render할 때마다 대상 체크박스를 다시 조회합니다. 처음 저장한 :checked 집합이 사용자 조작에 맞춰 자동으로 최신 요소 집합이 되는 것은 아닙니다.

Reset은 form.reset()으로 기본 상태를 복원한 뒤 직접 render합니다. 버튼 id를 restore로 둬 reset 메서드와 이름 충돌을 피했습니다. 기본 checked 속성 자체를 수정하거나 폼에 동적으로 다른 기본값을 넣었다면 복원 결과도 그 기본 상태를 따릅니다.

비활성 항목과 여러 값의 실제 제출

disabled인 체크박스와 비활성 fieldset 안의 체크박스도 체크 상태가 남을 수 있습니다. :checked만으로는 제출 가능 여부를 알 수 없습니다. 예제는 :enabled로 상속된 비활성 상태까지 구분하며, FormData는 Archive와 Legacy를 제외합니다. 단순히 [disabled] 속성이 없는지만 검사하면 fieldset에서 비활성화된 자손을 놓칠 수 있습니다.

체크하지 않은 체크박스·라디오는 일반 폼 제출에서 해당 항목이 빠집니다. false라는 문자열이 자동으로 전송되는 것은 아닙니다. 체크박스에 value를 생략하면 기본 제출 값은 on이므로 의미 있는 값을 명시하고, 서버가 기대하는 누락·선택 값의 규칙을 정합니다.

같은 name의 체크박스 여러 개를 모을 때는 FormData.getAll()처럼 여러 값을 보존하는 API를 사용합니다. Object.fromEntries처럼 이름을 키 하나로 바꾸면 반복 이름의 값이 사라질 수 있습니다. 서버 프레임워크가 배열을 받는 이름 규칙은 별도로 확인합니다. 클라이언트의 체크 상태만 믿지 말고 서버에서도 허용 값과 조합을 검사합니다.

:checked는 표준 DOM의 querySelectorAll에서도 사용할 수 있습니다. option만 필요하면 option:checked나 jQuery :selected로 범위를 좁히며, 체크박스·라디오 전용 코드와 섞지 않습니다. CSS나 결과 문구로 체크 상태를 꾸밀 때에도 실제 input의 checked와 label 연결을 유지하고, 화면 색상만 바뀌어 실제 선택과 달라지지 않게 합니다.