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
- Load the Bootstrap 5 stylesheet in the page head. The JavaScript bundle is not needed for a checkbox.
- Wrap the control in a div with the class form-check.
- Add the input with the class form-check-input, type checkbox, a value and a unique id.
- 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.
| Control | Use it for | Trade-off |
|---|
| Checkbox | One or several independent options | Value is sent only on submit |
| Switch | A setting that applies at once | Announced as on/off only with role=”switch” |
| Toggle button | Filters and toolbar options | Announced as checked, not as pressed |
| Radio | One choice from a set | Cannot 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.
| Part | Bootstrap 3 | Bootstrap 4 | Bootstrap 5 |
|---|
| Wrapper | .checkbox | .custom-control .custom-checkbox, or .form-check | .form-check |
| Input | Plain input inside the label | .custom-control-input | .form-check-input |
| Label | Wraps the input | .custom-control-label | .form-check-label, after the input |
| Inline | .checkbox-inline | .custom-control-inline | .form-check-inline |
| Switch | None | .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.