Bootstrap Checkbox: 17 Examples of Forms, Cards and Switches

Recolor a Bootstrap checkbox with a plain background-color override and the old white check mark stays exactly where it was. The mark is an SVG background image, not a native browser graphic.

Below are 17 Bootstrap checkbox examples you can copy, followed by a guide to markup, states, validation, accessibility and styling in versions 3, 4 and 5.

By Bogdan Sandu ยท Updated October 29, 2026

Every example runs on Bootstrap 5.3.8 from the jsDelivr CDN. Add these tags to your page, then paste an example's HTML, CSS and JS.

<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-sRIl4kxILFvY47J16cr9ZwB07vP4J8+LH7qKQnuqkuIAvNWLzeN8tE5YBujZqJLB" crossorigin="anonymous">
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.13.2/font/bootstrap-icons.min.css" rel="stylesheet" integrity="sha384-nWN3KqaaVcbEsCjLSV+2jMjh62Iny/+mN3srM8TZeM8N6aj7eDl5wwYggjAJ4/sJ" crossorigin="anonymous">
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/js/bootstrap.bundle.min.js" integrity="sha384-FKyoEForCGlyvwx9Hj09JcYn3nv7wiPVlz7YYwJrWVcXK/BmnVDxM+D2scQbITxI" crossorigin="anonymous"></script>

Checkboxes in real forms

The form-check pattern in the places it shows up most: consent boxes with validation, login rows, grouped options and locked settings.

Remember me login row

  • form-check
  • d-flex
  • form-floating
  • link-*

Click a label or a box to change it.

The classic login footer: a remember-me checkbox on the left and a reset link on the right, laid out with d-flex and justify-content-between. The fields use form-floating, and the custom class only themes the checkbox and button through Bootstrap variables.

View code
<form class="card border-0 shadow-sm bck-login">
  <div class="card-body p-4">
    <h3 class="h5 mb-4">Sign in to Ledgerly</h3>
    <div class="form-floating mb-3">
      <input type="email" class="form-control" id="bck-login-email" placeholder="name@example.com" autocomplete="username">
      <label for="bck-login-email">Email address</label>
    </div>
    <div class="form-floating mb-3">
      <input type="password" class="form-control" id="bck-login-pass" placeholder="Password" autocomplete="current-password">
      <label for="bck-login-pass">Password</label>
    </div>
    <div class="d-flex flex-wrap justify-content-between align-items-center gap-2 mb-4">
      <div class="form-check mb-0">
        <input class="form-check-input" type="checkbox" value="remember" id="bck-remember">
        <label class="form-check-label" for="bck-remember">Remember me for 30 days</label>
      </div>
      <a href="#" class="small link-offset-2 bck-login-link">Forgot password?</a>
    </div>
    <button type="submit" class="btn w-100 py-2 bck-login-btn">Sign in</button>
  </div>
</form>

Inline group with fieldset and legend

  • form-check-inline
  • fieldset
  • legend
  • form-text

Click a label or a box to change it.

Delivery days as one inline row of checkboxes. The fieldset and legend give the group a shared name for screen readers, form-check-inline puts the options on one line, and a form-text hint is linked with aria-describedby. Narrow screens simply wrap the row.

View code
<div class="card border-0 shadow-sm bck-days">
  <div class="card-body p-4">
    <h3 class="h6 text-body-secondary fw-normal mb-3">Weekly veg box, Harvest Lane Farm</h3>
    <fieldset aria-describedby="bck-days-help">
      <legend class="fs-6 fw-semibold mb-2">Delivery days</legend>
      <div class="form-check form-check-inline">
        <input class="form-check-input" type="checkbox" id="bck-day-mon" value="mon" checked>
        <label class="form-check-label" for="bck-day-mon">Mon</label>
      </div>
      <div class="form-check form-check-inline">
        <input class="form-check-input" type="checkbox" id="bck-day-tue" value="tue">
        <label class="form-check-label" for="bck-day-tue">Tue</label>
      </div>
      <div class="form-check form-check-inline">
        <input class="form-check-input" type="checkbox" id="bck-day-wed" value="wed">
        <label class="form-check-label" for="bck-day-wed">Wed</label>
      </div>
      <div class="form-check form-check-inline">
        <input class="form-check-input" type="checkbox" id="bck-day-thu" value="thu" checked>
        <label class="form-check-label" for="bck-day-thu">Thu</label>
      </div>
      <div class="form-check form-check-inline">
        <input class="form-check-input" type="checkbox" id="bck-day-fri" value="fri">
        <label class="form-check-label" for="bck-day-fri">Fri</label>
      </div>
      <div class="form-text" id="bck-days-help">Pick up to three days. Orders close at 8 pm the night before.</div>
    </fieldset>
  </div>
</div>

Disabled and read-only looking states

  • disabled
  • aria-readonly
  • form-text
  • bi-lock

Try clicking each locked row.

Three ways to show a setting the user cannot change. disabled dims the box and label and drops the value from the submission. The read-only row keeps the box focusable and submitted, sets aria-readonly and cancels clicks in JS, because checkboxes ignore the readonly attribute.

View code
<div class="card border-0 shadow-sm bck-locked">
  <div class="card-body p-4">
    <h3 class="h5 mb-1">Email notifications</h3>
    <p class="small text-body-secondary mb-3">Workspace: Arcadia Design Co.</p>
    <div class="form-check mb-3">
      <input class="form-check-input" type="checkbox" id="bck-n-mentions" checked>
      <label class="form-check-label" for="bck-n-mentions">Mentions and replies</label>
    </div>
    <div class="form-check mb-3">
      <input class="form-check-input" type="checkbox" id="bck-n-digest" disabled>
      <label class="form-check-label" for="bck-n-digest">Weekly digest</label>
      <div class="form-text mt-0">Available on the Team plan.</div>
    </div>
    <div class="form-check mb-3">
      <input class="form-check-input" type="checkbox" id="bck-n-security" checked disabled>
      <label class="form-check-label" for="bck-n-security">Security alerts</label>
      <div class="form-text mt-0">Always on for account safety.</div>
    </div>
    <div class="form-check mb-0">
      <input class="form-check-input bck-readonly" type="checkbox" id="bck-n-billing" name="billing" value="on" checked aria-readonly="true" aria-describedby="bck-n-billing-help">
      <label class="form-check-label" for="bck-n-billing">Billing receipts <i class="bi bi-lock-fill text-body-secondary" aria-hidden="true"></i></label>
      <div class="form-text mt-0" id="bck-n-billing-help">Set by your workspace owner. Still sent with the form.</div>
    </div>
  </div>
</div>

Newsletter picker in a list group

  • list-group
  • stretched-link
  • form-check-input
  • list-group-item-action

Click anywhere on a row.

Bootstrap list group docs place a .form-check-input inside each .list-group-item. Here the label also gets the stretched-link helper, so a click anywhere on the row toggles the box. A :has() rule tints the chosen rows, and the label keeps a short description in a second line.

View code
<div class="bck-news">
  <h3 class="h5 mb-3">Choose your newsletters</h3>
  <ul class="list-group shadow-sm">
    <li class="list-group-item list-group-item-action d-flex gap-3 py-3">
      <input class="form-check-input flex-shrink-0" type="checkbox" value="weekly" id="bck-nl-weekly" checked>
      <label class="form-check-label stretched-link" for="bck-nl-weekly"><span class="d-block fw-semibold">The Weekly Fold</span><span class="small text-body-secondary">Top design stories every Friday</span></label>
    </li>
    <li class="list-group-item list-group-item-action d-flex gap-3 py-3">
      <input class="form-check-input flex-shrink-0" type="checkbox" value="jobs" id="bck-nl-jobs">
      <label class="form-check-label stretched-link" for="bck-nl-jobs"><span class="d-block fw-semibold">Studio Jobs</span><span class="small text-body-secondary">Remote design roles, twice a month</span></label>
    </li>
    <li class="list-group-item list-group-item-action d-flex gap-3 py-3">
      <input class="form-check-input flex-shrink-0" type="checkbox" value="deals" id="bck-nl-deals">
      <label class="form-check-label stretched-link" for="bck-nl-deals"><span class="d-block fw-semibold">Tool Deals</span><span class="small text-body-secondary">Font and plugin discounts, monthly</span></label>
    </li>
  </ul>
</div>

Lists, select-all and filters

Checkbox groups that drive the rest of the UI, from an indeterminate select-all box to filter counts and a permissions table.

Select all with indeterminate state

  • indeterminate
  • list-group
  • aria-controls
  • disabled

Tick one message, then use the select-all box.

An inbox toolbar whose master box shows the dash while only some messages are ticked. There is no HTML attribute for that state, so the script sets the indeterminate property and keeps it in sync with the rows, the counter and the disabled Archive button.

View code
<div class="card border-0 shadow-sm bck-inbox">
  <div class="card-header bg-body d-flex align-items-center gap-3 py-3">
    <div class="form-check mb-0">
      <input class="form-check-input" type="checkbox" id="bck-all" aria-controls="bck-mail">
      <label class="form-check-label fw-semibold" for="bck-all">Select all</label>
    </div>
    <span class="small text-body-secondary ms-auto" id="bck-all-count" aria-live="polite">0 of 4 selected</span>
    <button type="button" class="btn btn-sm btn-outline-secondary" id="bck-archive" disabled>Archive</button>
  </div>
  <ul class="list-group list-group-flush" id="bck-mail">
    <li class="list-group-item d-flex gap-3 py-3">
      <input class="form-check-input flex-shrink-0 bck-mail-box" type="checkbox" id="bck-m1" checked>
      <label class="form-check-label w-100" for="bck-m1"><span class="d-block fw-semibold">Lena Ortiz</span><span class="small text-body-secondary">Revised homepage copy is ready</span></label>
    </li>
    <li class="list-group-item d-flex gap-3 py-3">
      <input class="form-check-input flex-shrink-0 bck-mail-box" type="checkbox" id="bck-m2">
      <label class="form-check-label w-100" for="bck-m2"><span class="d-block fw-semibold">Fieldnote Billing</span><span class="small text-body-secondary">Your October invoice</span></label>
    </li>
    <li class="list-group-item d-flex gap-3 py-3">
      <input class="form-check-input flex-shrink-0 bck-mail-box" type="checkbox" id="bck-m3">
      <label class="form-check-label w-100" for="bck-m3"><span class="d-block fw-semibold">Sam Whitaker</span><span class="small text-body-secondary">Standup moved to 10:30</span></label>
    </li>
    <li class="list-group-item d-flex gap-3 py-3">
      <input class="form-check-input flex-shrink-0 bck-mail-box" type="checkbox" id="bck-m4">
      <label class="form-check-label w-100" for="bck-m4"><span class="d-block fw-semibold">Priya Nair</span><span class="small text-body-secondary">Logo files, final round</span></label>
    </li>
  </ul>
</div>

To-do list with strike-through

  • :checked + label
  • list-group
  • progress
  • text-decoration-line-through

Tick a task to cross it off.

Ticking a task strikes it out with a :checked + .form-check-label rule, the same sibling selector Bootstrap uses for disabled labels. A Bootstrap progress bar shows how much is done; the script only updates its width and aria-valuenow.

View code
<div class="card border-0 shadow-sm bck-todo">
  <div class="card-body p-4 pb-3">
    <div class="d-flex justify-content-between align-items-baseline mb-2">
      <h3 class="h5 mb-0">Launch checklist</h3>
      <span class="small text-body-secondary" id="bck-todo-label">2 of 5 done</span>
    </div>
    <div class="progress mb-3" role="progressbar" aria-label="Checklist progress" aria-valuenow="40" aria-valuemin="0" aria-valuemax="100" id="bck-todo-bar" style="height: .5rem">
      <div class="progress-bar bck-todo-fill" style="width: 40%"></div>
    </div>
  </div>
  <ul class="list-group list-group-flush">
    <li class="list-group-item px-4"><div class="form-check mb-0"><input class="form-check-input bck-task" type="checkbox" id="bck-t1" checked><label class="form-check-label" for="bck-t1">Write release notes</label></div></li>
    <li class="list-group-item px-4"><div class="form-check mb-0"><input class="form-check-input bck-task" type="checkbox" id="bck-t2" checked><label class="form-check-label" for="bck-t2">Update pricing page</label></div></li>
    <li class="list-group-item px-4"><div class="form-check mb-0"><input class="form-check-input bck-task" type="checkbox" id="bck-t3"><label class="form-check-label" for="bck-t3">Record the demo video</label></div></li>
    <li class="list-group-item px-4"><div class="form-check mb-0"><input class="form-check-input bck-task" type="checkbox" id="bck-t4"><label class="form-check-label" for="bck-t4">Schedule the newsletter</label></div></li>
    <li class="list-group-item px-4"><div class="form-check mb-0"><input class="form-check-input bck-task" type="checkbox" id="bck-t5"><label class="form-check-label" for="bck-t5">Post in the community forum</label></div></li>
  </ul>
</div>

Filter sidebar with counts

  • fieldset
  • badge
  • form-check
  • btn-link

Tick a few filters, then press Clear all.

A shop filter panel with two fieldsets, a count badge beside each option and a Clear link. The label is stretched with d-flex so the count sits flush right, and the script sums the counts into the result button text.

View code
<form class="card border-0 shadow-sm bck-filter" id="bck-filter">
  <div class="card-body p-4">
    <div class="d-flex justify-content-between align-items-center mb-3">
      <h3 class="h5 mb-0">Filters</h3>
      <button type="reset" class="btn btn-link btn-sm p-0 text-decoration-none">Clear all</button>
    </div>
    <fieldset class="mb-3">
      <legend class="fs-6 fw-semibold text-uppercase small text-body-secondary mb-2">Category</legend>
      <div class="form-check"><input class="form-check-input" type="checkbox" id="bck-f-chairs" data-count="42"><label class="form-check-label d-flex" for="bck-f-chairs">Chairs<span class="badge rounded-pill bg-body-secondary text-body-secondary ms-auto">42</span></label></div>
      <div class="form-check"><input class="form-check-input" type="checkbox" id="bck-f-tables" data-count="18"><label class="form-check-label d-flex" for="bck-f-tables">Tables<span class="badge rounded-pill bg-body-secondary text-body-secondary ms-auto">18</span></label></div>
      <div class="form-check"><input class="form-check-input" type="checkbox" id="bck-f-lamps" data-count="27"><label class="form-check-label d-flex" for="bck-f-lamps">Lighting<span class="badge rounded-pill bg-body-secondary text-body-secondary ms-auto">27</span></label></div>
    </fieldset>
    <fieldset class="mb-4">
      <legend class="fs-6 fw-semibold text-uppercase small text-body-secondary mb-2">Material</legend>
      <div class="form-check"><input class="form-check-input" type="checkbox" id="bck-f-oak" data-count="31"><label class="form-check-label d-flex" for="bck-f-oak">Solid oak<span class="badge rounded-pill bg-body-secondary text-body-secondary ms-auto">31</span></label></div>
      <div class="form-check"><input class="form-check-input" type="checkbox" id="bck-f-rattan" data-count="12"><label class="form-check-label d-flex" for="bck-f-rattan">Rattan<span class="badge rounded-pill bg-body-secondary text-body-secondary ms-auto">12</span></label></div>
      <div class="form-check"><input class="form-check-input" type="checkbox" id="bck-f-steel" disabled data-count="0"><label class="form-check-label d-flex" for="bck-f-steel">Powder-coated steel<span class="badge rounded-pill bg-body-secondary text-body-secondary ms-auto">0</span></label></div>
    </fieldset>
    <button type="button" class="btn btn-dark w-100" id="bck-filter-go">Show all 87 products</button>
  </div>
</form>

Permissions matrix table

  • table
  • table-responsive
  • aria-labelledby
  • form-check-input

Toggle a permission in the table.

Role permissions as a table of label-free checkboxes. Each input gets its accessible name from aria-labelledby pointing at its row and column headers, so a screen reader hears, for example, Edit posts Editor. The table sits in table-responsive for narrow screens.

View code
<div class="card border-0 shadow-sm bck-perms">
  <div class="card-body p-0">
    <div class="table-responsive">
      <table class="table align-middle mb-0">
        <caption class="px-3 small">Roles for the Atlas Journal workspace</caption>
        <thead>
          <tr>
            <th scope="col" class="ps-3">Permission</th>
            <th scope="col" id="bck-r-viewer" class="text-center">Viewer</th>
            <th scope="col" id="bck-r-editor" class="text-center">Editor</th>
            <th scope="col" id="bck-r-admin" class="text-center pe-3">Admin</th>
          </tr>
        </thead>
        <tbody>
          <tr>
            <th scope="row" id="bck-p-read" class="ps-3 fw-normal">Read posts</th>
            <td class="text-center"><input class="form-check-input" type="checkbox" checked aria-labelledby="bck-p-read bck-r-viewer"></td>
            <td class="text-center"><input class="form-check-input" type="checkbox" checked aria-labelledby="bck-p-read bck-r-editor"></td>
            <td class="text-center pe-3"><input class="form-check-input" type="checkbox" checked disabled aria-labelledby="bck-p-read bck-r-admin"></td>
          </tr>
          <tr>
            <th scope="row" id="bck-p-edit" class="ps-3 fw-normal">Edit posts</th>
            <td class="text-center"><input class="form-check-input" type="checkbox" aria-labelledby="bck-p-edit bck-r-viewer"></td>
            <td class="text-center"><input class="form-check-input" type="checkbox" id="bck-edit-editor" aria-labelledby="bck-p-edit bck-r-editor"></td>
            <td class="text-center pe-3"><input class="form-check-input" type="checkbox" checked disabled aria-labelledby="bck-p-edit bck-r-admin"></td>
          </tr>
          <tr>
            <th scope="row" id="bck-p-pub" class="ps-3 fw-normal">Publish</th>
            <td class="text-center"><input class="form-check-input" type="checkbox" aria-labelledby="bck-p-pub bck-r-viewer"></td>
            <td class="text-center"><input class="form-check-input" type="checkbox" aria-labelledby="bck-p-pub bck-r-editor"></td>
            <td class="text-center pe-3"><input class="form-check-input" type="checkbox" checked disabled aria-labelledby="bck-p-pub bck-r-admin"></td>
          </tr>
          <tr>
            <th scope="row" id="bck-p-bill" class="ps-3 fw-normal">Manage billing</th>
            <td class="text-center"><input class="form-check-input" type="checkbox" aria-labelledby="bck-p-bill bck-r-viewer"></td>
            <td class="text-center"><input class="form-check-input" type="checkbox" aria-labelledby="bck-p-bill bck-r-editor"></td>
            <td class="text-center pe-3"><input class="form-check-input" type="checkbox" checked disabled aria-labelledby="bck-p-bill bck-r-admin"></td>
          </tr>
        </tbody>
      </table>
    </div>
  </div>
</div>

Cards, chips and switches

The same input wearing different clothes: clickable cards, btn-check chips and photo tiles, plus a side-by-side look at form-switch.

Clickable checkbox cards

  • form-check-input
  • :has()
  • @supports
  • card

Click anywhere on a card.

Add-on cards where the whole card is the label, so a click anywhere toggles the box. The selected border comes from :has(:checked) on the card; browsers without :has() fall back to the visible checkbox, guarded by @supports selector(:has(*)).

View code
<fieldset class="bck-addons">
  <legend class="h5 mb-3">Add extras to your hosting plan</legend>
  <label class="card bck-addon mb-2" for="bck-add-backup">
    <span class="card-body d-flex gap-3 align-items-start">
      <input class="form-check-input flex-shrink-0 mt-1" type="checkbox" id="bck-add-backup" checked>
      <span class="flex-grow-1"><span class="d-block fw-semibold">Daily backups</span><span class="small text-body-secondary">Kept for 30 days, one-click restore</span></span>
      <span class="fw-semibold">$4<span class="small fw-normal text-body-secondary">/mo</span></span>
    </span>
  </label>
  <label class="card bck-addon mb-2" for="bck-add-cdn">
    <span class="card-body d-flex gap-3 align-items-start">
      <input class="form-check-input flex-shrink-0 mt-1" type="checkbox" id="bck-add-cdn">
      <span class="flex-grow-1"><span class="d-block fw-semibold">Global CDN</span><span class="small text-body-secondary">Edge caching in 40 regions</span></span>
      <span class="fw-semibold">$9<span class="small fw-normal text-body-secondary">/mo</span></span>
    </span>
  </label>
  <label class="card bck-addon" for="bck-add-mail">
    <span class="card-body d-flex gap-3 align-items-start">
      <input class="form-check-input flex-shrink-0 mt-1" type="checkbox" id="bck-add-mail">
      <span class="flex-grow-1"><span class="d-block fw-semibold">Business email</span><span class="small text-body-secondary">5 mailboxes on your domain</span></span>
      <span class="fw-semibold">$6<span class="small fw-normal text-body-secondary">/mo</span></span>
    </span>
  </label>
</fieldset>

Toggle-button tag chips

  • btn-check
  • btn-outline-*
  • rounded-pill
  • --bs-btn-* variables

Pick a few topics.

Interest chips built with btn-check: a real checkbox followed by a button-styled label. Checked chips pick up the active button colors and show a check icon through the :checked + .btn sibling selector. The theme comes from --bs-btn-* variables on one prefixed class.

View code
<div class="card border-0 shadow-sm bck-chips">
  <div class="card-body p-4">
    <h3 class="h5 mb-1">What do you want to read about?</h3>
    <p class="small text-body-secondary mb-3">Pick at least three to tune your feed.</p>
    <div class="d-flex flex-wrap gap-2" role="group" aria-label="Topics">
      <input type="checkbox" class="btn-check" id="bck-tag-ux" autocomplete="off" checked>
      <label class="btn rounded-pill bck-chip" for="bck-tag-ux"><i class="bi bi-check2" aria-hidden="true"></i>UX research</label>
      <input type="checkbox" class="btn-check" id="bck-tag-type" autocomplete="off">
      <label class="btn rounded-pill bck-chip" for="bck-tag-type"><i class="bi bi-check2" aria-hidden="true"></i>Typography</label>
      <input type="checkbox" class="btn-check" id="bck-tag-css" autocomplete="off" checked>
      <label class="btn rounded-pill bck-chip" for="bck-tag-css"><i class="bi bi-check2" aria-hidden="true"></i>CSS</label>
      <input type="checkbox" class="btn-check" id="bck-tag-a11y" autocomplete="off">
      <label class="btn rounded-pill bck-chip" for="bck-tag-a11y"><i class="bi bi-check2" aria-hidden="true"></i>Accessibility</label>
      <input type="checkbox" class="btn-check" id="bck-tag-motion" autocomplete="off">
      <label class="btn rounded-pill bck-chip" for="bck-tag-motion"><i class="bi bi-check2" aria-hidden="true"></i>Motion design</label>
      <input type="checkbox" class="btn-check" id="bck-tag-wp" autocomplete="off">
      <label class="btn rounded-pill bck-chip" for="bck-tag-wp"><i class="bi bi-check2" aria-hidden="true"></i>WordPress</label>
      <input type="checkbox" class="btn-check" id="bck-tag-brand" autocomplete="off" disabled>
      <label class="btn rounded-pill bck-chip" for="bck-tag-brand"><i class="bi bi-check2" aria-hidden="true"></i>Branding (soon)</label>
    </div>
  </div>
</div>

Photo tiles with btn-check

  • btn-check
  • ratio
  • object-fit-cover
  • row-cols-*

Pick the places you want to visit.

A trip planner where each photo is the label of a hidden btn-check input, so the tiles stay keyboard operable with Space. The grid is row-cols-2, the photos use ratio and object-fit-cover, and the checked tile gets a ring and a check badge from :checked + label.

View code
<fieldset class="bck-trips">
  <legend class="h5 mb-3">Where are you heading this year?</legend>
  <div class="row row-cols-2 g-2">
    <div class="col">
      <input type="checkbox" class="btn-check" id="bck-trip-fjord" autocomplete="off" checked>
      <label class="bck-tile d-block rounded-3 overflow-hidden" for="bck-trip-fjord">
        <span class="ratio ratio-4x3 d-block"><img src="fjord.webp" alt="" class="object-fit-cover"></span>
        <span class="bck-tile-name">Norwegian fjords</span>
      </label>
    </div>
    <div class="col">
      <input type="checkbox" class="btn-check" id="bck-trip-valley" autocomplete="off">
      <label class="bck-tile d-block rounded-3 overflow-hidden" for="bck-trip-valley">
        <span class="ratio ratio-4x3 d-block"><img src="granite-valley.webp" alt="" class="object-fit-cover"></span>
        <span class="bck-tile-name">Granite valleys</span>
      </label>
    </div>
    <div class="col">
      <input type="checkbox" class="btn-check" id="bck-trip-canal" autocomplete="off">
      <label class="bck-tile d-block rounded-3 overflow-hidden" for="bck-trip-canal">
        <span class="ratio ratio-4x3 d-block"><img src="canal-houses.webp" alt="" class="object-fit-cover"></span>
        <span class="bck-tile-name">Canal towns</span>
      </label>
    </div>
    <div class="col">
      <input type="checkbox" class="btn-check" id="bck-trip-snow" autocomplete="off">
      <label class="bck-tile d-block rounded-3 overflow-hidden" for="bck-trip-snow">
        <span class="ratio ratio-4x3 d-block"><img src="snow-camp.webp" alt="" class="object-fit-cover"></span>
        <span class="bck-tile-name">Winter camping</span>
      </label>
    </div>
  </div>
</fieldset>

Switch versus checkbox settings

  • form-switch
  • role="switch"
  • form-check-reverse
  • form-check

Flip a switch, then tick a box and press Save.

Two panels side by side. The switches use form-switch with role="switch" and form-check-reverse, and they apply at once with a short status message. The checkboxes collect choices that only count when the user presses Save, which is the split the Bootstrap docs suggest.

View code
<div class="row g-3 bck-compare">
  <div class="col-md-6">
    <div class="card h-100 border-0 shadow-sm">
      <div class="card-body p-4">
        <h3 class="h6 mb-1">Instant settings</h3>
        <p class="small text-body-secondary mb-3">Switches apply as soon as you flip them.</p>
        <div class="form-check form-switch form-check-reverse text-start mb-3">
          <input class="form-check-input bck-instant" type="checkbox" role="switch" id="bck-sw-dark">
          <label class="form-check-label" for="bck-sw-dark">Compact sidebar</label>
        </div>
        <div class="form-check form-switch form-check-reverse text-start mb-3">
          <input class="form-check-input bck-instant" type="checkbox" role="switch" id="bck-sw-sound" checked>
          <label class="form-check-label" for="bck-sw-sound">Notification sounds</label>
        </div>
        <p class="small text-success mb-0" id="bck-sw-status" role="status"></p>
      </div>
    </div>
  </div>
  <div class="col-md-6">
    <form class="card h-100 border-0 shadow-sm" id="bck-cb-form">
      <div class="card-body p-4">
        <h3 class="h6 mb-1">Export options</h3>
        <p class="small text-body-secondary mb-3">Checkboxes wait for the Save button.</p>
        <div class="form-check mb-2">
          <input class="form-check-input" type="checkbox" id="bck-ex-comments" checked>
          <label class="form-check-label" for="bck-ex-comments">Include comments</label>
        </div>
        <div class="form-check mb-3">
          <input class="form-check-input" type="checkbox" id="bck-ex-history">
          <label class="form-check-label" for="bck-ex-history">Include version history</label>
        </div>
        <button type="submit" class="btn btn-sm btn-primary px-3">Save</button>
        <span class="small text-success ms-2" id="bck-cb-status" role="status"></span>
      </div>
    </form>
  </div>
</div>

Size, color and dark mode

Checkbox looks come from a few properties and CSS variables on .form-check-input. These demos resize, recolor and darken them without Sass.

Large touch-friendly checkboxes

  • form-check-input
  • em sizing
  • --bs-border-radius
  • list-group

Tap the big boxes.

Bootstrap sizes the box in em (1em wide, label offset 1.5em), so one font-size and padding change scales the whole control. Here a prefixed class makes 1.6em boxes with a heavier border for a kiosk-style survey, and each row stays at least 56px tall.

View code
<div class="card border-0 shadow-sm bck-big">
  <div class="card-body p-4">
    <h3 class="h5 mb-1">How did you hear about Brightside Clinic?</h3>
    <p class="small text-body-secondary mb-3">Choose all that apply.</p>
    <div class="form-check bck-big-check">
      <input class="form-check-input" type="checkbox" id="bck-big-friend" checked>
      <label class="form-check-label" for="bck-big-friend">A friend or family member</label>
    </div>
    <div class="form-check bck-big-check">
      <input class="form-check-input" type="checkbox" id="bck-big-doctor">
      <label class="form-check-label" for="bck-big-doctor">My doctor referred me</label>
    </div>
    <div class="form-check bck-big-check">
      <input class="form-check-input" type="checkbox" id="bck-big-search">
      <label class="form-check-label" for="bck-big-search">Online search</label>
    </div>
  </div>
</div>

Custom check color and mark

  • --bs-form-check-bg-image
  • :checked
  • :indeterminate
  • focus ring

Tick the boxes to see the custom mark.

A lime box with a dark check. Changing background-color alone would keep the white mark, so the checked and indeterminate states also reassign --bs-form-check-bg-image to a new SVG with a dark stroke. The focus ring and border are recolored to match.

View code
<div class="card border-0 shadow-sm bck-lime">
  <div class="card-body p-4">
    <h3 class="h5 mb-3">Sprint 14 review</h3>
    <div class="form-check mb-2">
      <input class="form-check-input" type="checkbox" id="bck-lime-1" checked>
      <label class="form-check-label" for="bck-lime-1">Checkout flow redesign</label>
    </div>
    <div class="form-check mb-2">
      <input class="form-check-input" type="checkbox" id="bck-lime-2" checked>
      <label class="form-check-label" for="bck-lime-2">Faster search results</label>
    </div>
    <div class="form-check mb-2">
      <input class="form-check-input" type="checkbox" id="bck-lime-3">
      <label class="form-check-label" for="bck-lime-3">Saved filters</label>
    </div>
    <div class="form-check mb-0">
      <input class="form-check-input" type="checkbox" id="bck-lime-4">
      <label class="form-check-label" for="bck-lime-4">Bulk export (partly done)</label>
    </div>
  </div>
</div>

Contrast-safe border

  • --bs-border-color
  • form-check
  • WCAG 1.4.11

Compare the two columns, then tick a box.

The default unchecked border, #dee2e6 on white, is about 1.3:1. Reassigning --bs-border-color on the input to #767676 lifts it past the 3:1 non-text contrast minimum without touching the checked colors, since those set border-color directly.

View code
<div class="card border-0 shadow-sm bck-contrast">
  <div class="card-body p-4">
    <div class="row g-4">
      <div class="col-6">
        <h3 class="h6 mb-3">Default <span class="badge text-bg-danger fw-normal">1.3:1</span></h3>
        <div class="form-check"><input class="form-check-input" type="checkbox" id="bck-c-a1"><label class="form-check-label" for="bck-c-a1">Weekly</label></div>
        <div class="form-check"><input class="form-check-input" type="checkbox" id="bck-c-a2"><label class="form-check-label" for="bck-c-a2">Monthly</label></div>
      </div>
      <div class="col-6">
        <h3 class="h6 mb-3">Fixed <span class="badge text-bg-success fw-normal">4.5:1</span></h3>
        <div class="form-check"><input class="form-check-input bck-strong" type="checkbox" id="bck-c-b1"><label class="form-check-label" for="bck-c-b1">Weekly</label></div>
        <div class="form-check"><input class="form-check-input bck-strong" type="checkbox" id="bck-c-b2"><label class="form-check-label" for="bck-c-b2">Monthly</label></div>
      </div>
    </div>
  </div>
</div>

Dark theme deploy checklist

  • data-bs-theme="dark"
  • form-check
  • form-switch
  • bg-body-tertiary

Tick the remaining steps.

The frame runs with data-bs-theme="dark", so the boxes, labels and switch pick up dark colors from Bootstrap variables with no extra CSS. The custom class only swaps the accent to amber and gives the check mark a dark stroke so it stays readable.

View code
<div class="card bg-body-tertiary border-secondary-subtle bck-deploy">
  <div class="card-body p-4">
    <div class="d-flex justify-content-between align-items-center mb-3 gap-2">
      <h3 class="h5 mb-0">Deploy v2.8.0</h3>
      <span class="badge rounded-pill text-bg-warning">Staging</span>
    </div>
    <div class="form-check mb-2"><input class="form-check-input" type="checkbox" id="bck-d-tests" checked><label class="form-check-label" for="bck-d-tests">All tests passing</label></div>
    <div class="form-check mb-2"><input class="form-check-input" type="checkbox" id="bck-d-migr" checked><label class="form-check-label" for="bck-d-migr">Database migration reviewed</label></div>
    <div class="form-check mb-2"><input class="form-check-input" type="checkbox" id="bck-d-flags"><label class="form-check-label" for="bck-d-flags">Feature flags set for rollout</label></div>
    <div class="form-check mb-3"><input class="form-check-input" type="checkbox" id="bck-d-notes"><label class="form-check-label" for="bck-d-notes">Changelog published</label></div>
    <hr class="border-secondary-subtle">
    <div class="form-check form-switch mb-0">
      <input class="form-check-input" type="checkbox" role="switch" id="bck-d-notify" checked>
      <label class="form-check-label" for="bck-d-notify">Notify the #releases channel</label>
    </div>
  </div>
</div>

What is a Bootstrap checkbox?

Underneath, it’s the regular checkbox, restyled with the .form-check class family so it looks the same in every browser. No custom JavaScript is needed for that.

The element stays a real HTML input, so it still submits, takes focus and validates like any other checkbox. Bootstrap only changes how it looks and how much space it takes.

The wrapper div (.form-check) handles spacing and layout. Bootstrap draws the box on the input through .form-check-input, with its own checked and indeterminate icons, and the label (.form-check-label) is styled from whatever state the input is in.

Input and label sit next to each other instead of nesting. According to the Bootstrap 5.3 documentation, that costs you an id on the input and a matching for on the label, but the stylesheet can then react to :checked and :disabled through the sibling selector.

Everything runs on CSS. The one exception is the indeterminate state, which needs a line of script.

Two older add-ons have similar names and get mixed up with this. The jQuery plugin bootstrap-checkbox requires Bootstrap 3.0.0 and jQuery 1.9.1, and awesome-bootstrap-checkbox restyles the Bootstrap 3 .checkbox markup with Font Awesome.

Class names follow the version of the Bootstrap framework you load, and old markup is still everywhere. W3Techs put Bootstrap 3 on 28.2% of Bootstrap sites and Bootstrap 4 on 21.4%, against 10.7% for Bootstrap 5 (28 October 2026).

Adding one to a page

  1. Load the Bootstrap 5 stylesheet in the page head. The JavaScript bundle is not needed for a checkbox.
  2. Wrap the control in a div with the class form-check.
  3. Add the input with the class form-check-input, type checkbox, a value and a unique id.
  4. Add a label with the class form-check-label and a for attribute that matches that id.

Repeat steps 2 to 4 for each option. Sibling wrappers stack vertically on their own.

How to set checked, disabled and indeterminate states

Checked and disabled are plain HTML attributes on the input. Indeterminate has no attribute at all and must be set from a script.

Checked by default

Add the checked attribute and the box renders ticked on load.

That sets the starting state only. The user can untick it, and a form reset brings it back.

Disabled

Add the disabled attribute to the input and Bootstrap dims the label to match, because the label is styled from the input’s state. In the Bootstrap 5.3.8 Sass defaults, both drop to .5 opacity.

A disabled box can’t be focused or clicked, and its value is not submitted. Put the attribute on a fieldset and every checkbox inside it is disabled at once.

Disabled works on checked boxes too. A disabled, checked box is a clean way to show a locked default.

Indeterminate

MDN Web Docs states that the indeterminate state only changes how a checkbox looks, not whether it is submitted. Bootstrap draws it as a dash and notes that it can only be set from JavaScript, since no HTML attribute exists for it.

A few lines of JavaScript have to set the indeterminate property on the input after the page loads. Any click clears the state, and the checked value underneath is what actually submits.

The usual case is a “select all” box that shows the dash while only some of its child boxes are ticked.

Inline, reversed and label-free checkbox layouts

Sibling .form-check wrappers stack vertically with spacing built in, so the default needs no extra class and gives you one option per line.

Put .form-check-inline on each wrapper to line the boxes up on one row, with a 1rem end margin in the 5.3.8 defaults. .form-check-reverse moves the box to the opposite side, and it works on switches too (5.3 docs). For a box with no label at all, drop the .form-check wrapper and keep only the input.

Inline works for two to four short options. Past that, labels wrap unevenly and a stacked list reads better, which is how most Bootstrap form layouts handle longer option sets.

Reverse earns its place in settings panels where labels sit on the left and the controls line up on the right.

The label-free variant comes with one rule. Still give the input an accessible name, for example with aria-label. The 5.3 docs say so directly, and without a name a screen reader has nothing to announce.

Turning a Bootstrap checkbox into a switch or toggle button

A switch needs one extra class on the wrapper, .form-switch. A toggle button swaps the wrapper for .btn-check and a button-styled label.

Switch

The wrapper gets .form-switch alongside .form-check, the input gets role=”switch”, and the label stays .form-check-label.

The Bootstrap docs word the role as a suggestion, and add that older assistive technology will simply announce a regular checkbox. MDN Web Docs notes that the switch role reports on and off instead of checked and unchecked, and that it has no indeterminate state, so keep that state for real checkboxes.

As of the Bootstrap 5.3.8 docs, adding the switch attribute gives mobile Safari on iOS 17.4 and later haptic feedback when the control toggles. Bootstrap attaches no styles to the attribute, because its switches already use custom styles.

Sizing and label placement are covered in more depth in this guide to Bootstrap toggle switches.

Toggle button

A toggle button is still a checkbox, just wearing button styles.

Put the input first with the class btn-check and autocomplete set to off, then a label with .btn classes and a matching for attribute. Any of the Bootstrap button classes works on the label, outline variants included, and several toggles can sit inside a button group.

They look the same as the button plugin’s toggles but are announced differently by screen readers.

Checkbox, switch, toggle button or radio: which one fits?

Choose by what the click does. Checkboxes select one or several options in a list, radios select one option from many, and the switch and toggle button are checkboxes dressed for specific jobs.

ControlUse it forTrade-off
CheckboxOne or several independent optionsValue is sent only on submit
SwitchA setting that applies at onceAnnounced as on/off only with role=”switch”
Toggle buttonFilters and toolbar optionsAnnounced as checked, not as pressed
RadioOne choice from a setCannot be cleared once chosen

Screen readers announce a checkbox toggle as checked or not checked, while the button plugin’s toggles come through as button and button pressed (Bootstrap 5.3 documentation). The docs leave the pick to you, depending on whether the toggle makes sense to users when announced as a checkbox or as a real button.

If the form has a Save button, a checkbox fits. A setting that changes the page the moment it is clicked suits a switch with role=”switch”. Mutually exclusive options belong in radios, not in a row of checkboxes with a script that unticks the others.

When two options fit, take the plain checkbox. Every browser and every screen reader already knows how to handle it.

How does validation work for a required Bootstrap checkbox?

Add the required attribute to the input, then pick a feedback route. You can use the browser’s own message, or Bootstrap’s styles triggered by .was-validated on the form. Custom messages need novalidate on the form, a short script and an .invalid-feedback element placed after the label.

Single required checkbox

The required attribute does the blocking. An unticked required checkbox counts as invalid, and the browser stops the submission.

.was-validated goes on the form, usually after a failed submit. Without it, any empty required field shows as invalid the moment the page loads, which looks broken.

.invalid-feedback sits inside the .form-check wrapper after the label and appears when the input is invalid. It has to follow the input in the markup. Bootstrap reveals it through a sibling selector, and the same selector turns the label text the invalid color, so a label placed before the input never picks that up.

For forms rendered on the server, .is-invalid and .is-valid do the same job without .was-validated. Point aria-describedby on the input at the id of the feedback element so assistive technology reads the message.

Required checkbox group

There is no native “at least one” rule.

Unlike radio buttons, a checkbox group has no built-in required validation, which is why Cloud Four’s tutorial on validating a checkbox group writes the check in script. Bootstrap’s own validation examples cover one checkbox, not a set.

The script can count the ticked boxes with FormData getAll on the shared name and validate when focus leaves the group. Or it can call setCustomValidity on one box while none is ticked, then clear it with an empty string. Either way, wrap the group in a fieldset and legend, and put “(required)” in the legend.

Adding required to every box until one is ticked is a shortcut seen in support threads. It makes each box report as required when one tick is enough.

Where custom validation falls short

Bootstrap’s 5.3 documentation states that the client-side custom validation styles and tooltips are not exposed to assistive technologies. It recommends server-side validation or the browser default method instead.

For a checkbox that gates a form, such as “Agree to terms”, that points to browser validation or server-rendered .is-invalid. Browser messages cannot be styled with CSS, but the text can be changed through setCustomValidity.

Making Bootstrap checkboxes accessible

Every checkbox needs an accessible name, and a visible label tied to the input is the best one. Related checkboxes need a shared name through a fieldset and legend.

Naming a checkbox

When no visible label fits the design, Bootstrap’s form documentation lists a few fallbacks.

  • A label hidden with the .visually-hidden class
  • aria-labelledby pointing at existing text
  • A title attribute
  • aria-label on the input itself

The same page adds that visible text still beats all of them, since a lack of visible label text can still be a problem for certain users. The ARIA attributes in that list fill a gap and nothing more.

Grouping related checkboxes

W3C’s forms tutorial groups related checkboxes in a fieldset, with the legend acting as the group’s heading.

Screen readers differ here. Depending on configuration, some read the legend with every control, some once, and a few rarely or never, so keep the legend short and make each label clear on its own.

WCAG technique H71 adds that grouping is not required when the labels already carry clear instructions and distinct choices. Bootstrap’s docs use fieldset mainly to disable a block of controls, and once in the horizontal form layout example to group radios under a legend, but .form-check wrappers sit inside one without extra classes.

More label and error patterns are covered in this guide to accessible forms.

Keep the focus ring

Bootstrap draws the checkbox focus ring with a box-shadow, so overriding outline or box-shadow to tidy the look removes it.

W3C lists that exact move as failure F78, which covers success criteria 1.4.11, 2.4.7 and 2.4.13: styling outlines and borders so the visual focus indicator is no longer visible. Restyle the ring if you want, but never delete it.

Changing Bootstrap checkbox color, size and dark mode appearance

Compile Bootstrap from Sass and override the variables for a site-wide change, or override the form-check CSS variables for one control at runtime. Dark mode comes from the data-bs-theme attribute, with no checkbox-specific CSS.

Sass variables

$form-check-input-checked-bg-color sets the box fill when checked, and $form-check-input-checked-color sets the color of the check mark. The size lives in $form-check-input-width (1em by default), and $form-check-input-border covers border width, style and color.

Set them after the functions import and before the variables import, or the defaults win (Bootstrap 5.2.0 migration notes). Label spacing follows the size, because $form-check-padding-start is the input width plus .5em.

The check mark and the indeterminate dash are SVG background images, with the stroke color baked into a data URI built from those variables. That is why a plain background-color override leaves the old check mark untouched.

CSS variables at runtime

Per the 5.3.0 migration notes, .form-check and .form-switch build their background-image from CSS variables. In the 5.3.8 source, .form-check-input exposes –bs-form-check-bg and –bs-form-check-bg-image, and switches use –bs-form-switch-bg.

States reassign the image variables instead of setting them on the base class. Override the variable inside the matching state selector, such as the checked checkbox, or the checked rule wins.

The cascade side of this is covered in this guide on how to override Bootstrap CSS without fighting specificity.

Dark mode

Add data-bs-theme=”dark” to the html element for the whole page, or to any wrapper to scope it to one block. Bootstrap’s color modes docs mark the feature as added in 5.3.

Validation colors adapt too, shifting to lighter green and red shades in dark mode. Switches carry a separate dark-mode thumb variable, $form-switch-color-dark, so a recolored switch needs both.

Check contrast after recoloring

Recoloring is where color contrast problems start. WCAG 1.4.11 (Level AA) asks for visible information that identifies a control and its state to meet a minimum ratio against adjacent colors.

  • The threshold is 3:1 for control boundaries and state indicators such as the check (W3C, Understanding 1.4.11)
  • W3C’s failing example is a checkbox border of #9D9D9D on white at 2.7:1
  • Bootstrap’s default border, #dee2e6 on white, computes to 1.3:1 with the WCAG formula
  • The default check mark, white on #0d6efd, computes to 4.5:1
  • Disabled controls are exempt as inactive components

The unchecked border is the weak spot, not the check. By the standard of W3C’s own checkbox example, Bootstrap’s default would fail too.

W3C’s passing text input example uses a #767676 border, so pointing $form-check-input-border at a color like that clears the bar.

Bootstrap checkbox markup in versions 3, 4 and 5

Bootstrap 5 builds a checkbox from .form-check with a sibling input and label. Bootstrap 4 added a parallel .custom-control pattern, and Bootstrap 3 put an input inside a label wrapped in .checkbox.

PartBootstrap 3Bootstrap 4Bootstrap 5
Wrapper.checkbox.custom-control .custom-checkbox, or .form-check.form-check
InputPlain input inside the label.custom-control-input.form-check-input
LabelWraps the input.custom-control-label.form-check-label, after the input
Inline.checkbox-inline.custom-control-inline.form-check-inline
SwitchNone.custom-switch.form-switch on the wrapper

What changed and when

Version 4.0 consolidated .checkbox and .radio into .form-check and dropped .checkbox-inline. Then 5.0 turned .custom-control.custom-checkbox into .form-check, and .custom-switch into .form-check.form-switch. Toggle buttons also lost their JavaScript requirement in that release.

.form-check-reverse arrived in 5.2. Color modes landed in 5.3, along with the CSS variables behind the check backgrounds.

Staying on Bootstrap 4 carries its own risk. The 4.6 documentation displays a notice stating that Bootstrap 4 has reached end of life (1 January 2023).

Do you still need a checkbox plugin?

Not on Bootstrap 5.

Version 5.0.0 dropped jQuery, so the jQuery plugin adds a dependency the framework itself no longer needs. The awesome-bootstrap-checkbox README documents Bootstrap 3 and Bootstrap 4 installs and points to a separate branch for Bootstrap 5.

A look beyond what the Sass variables reach is better handled with plain CSS checkbox styling than with a plugin layered over the form-check markup.

When a Bootstrap checkbox renders unstyled or ignores clicks

Most broken checkboxes come down to classes from the wrong Bootstrap version, an input nested inside its label, a label that does not match the input’s id, or custom CSS that erases the check mark.

The box looks like the browser default

The stylesheet is not matching the markup.

  • Bootstrap CSS is missing, or loaded after your own overrides
  • v3 or v4 classes sit on a v5 page, where .custom-control was replaced by .form-check in 5.0.0
  • .form-check-input is missing from the input, so only the wrapper is styled

Swap in the v5 class names and the styling returns.

The label does not toggle or change with the input

Usually the for value does not match the input id. Two inputs sharing an id causes it as well, and in that case the label toggles the first one on the page.

Structure matters too. An input nested inside the label, or a label placed before the input, breaks the state styling, because Bootstrap styles labels through the sibling selector. The awesome-bootstrap-checkbox README gives the same warning for its own markup.

Keep the order wrapper, input, label, with a unique id on every input.

A custom color erased the check mark

The check mark is the input’s background-image. A background shorthand, or a background-image override, replaces it.

Set background-color instead, or override the variable that holds the image, and test the checked and indeterminate states side by side.

Bootstrap Checkbox FAQ

How do I read or set the checked state with JavaScript?

Bootstrap ships no JavaScript API for this, so use the standard checked property on the input element. Read it to get true or false, or assign it to tick or untick the box.

Setting it from script does not fire a change event. Dispatch one yourself if other code listens for it.

Why is an unchecked checkbox missing from the submitted form data?

Browsers send only checked checkboxes. An unticked box is left out of the submission entirely, with no false value.

On the server, treat a missing field as false. Or add a hidden input with the same name and a value of 0 before the checkbox, as long as your framework reads the last value.

Can I use checkboxes inside a list group or a table?

Yes. Bootstrap’s list group documentation shows .form-check-input inside a .list-group-item, and a table cell takes the same input.

Table cells rarely have a visible label, so add an aria-label to each input. Without it, a screen reader announces only “checkbox”.

Is there a ready-made Bootstrap checkbox for React or Vue?

React-Bootstrap provides a Form.Check component that renders the same .form-check markup. Vue wrappers exist too, but confirm which Bootstrap version each one targets before installing, since the class names differ between 4 and 5.

Fixing Bootstrap Checkbox Problems in the Right Order

Audit a Bootstrap checkbox in this order: function first, accessibility second, appearance last. Each layer only matters once the one beneath it works.

Start with broken markup and mismatched ids. Then check for missing names and removed focus rings, move on to validation on required boxes, and finish with border contrast and color overrides.

Markup leads because a label that does not toggle the input fails every later check too. Contrast goes last because the fix is a single Sass variable that touches nothing else.

There is a cost. A border dark enough for WCAG AA makes every unticked box heavier on the page, and designers who prefer pale outlines will object.

This order was verified against the Bootstrap 5.3 documentation in October 2026. A new major release could rename the Sass variables, so recheck them before upgrading.