AdminKitDocs
Forms

Next.js Bootstrap Checkbox and Radio

Next.js Bootstrap checkbox, radio and switch markup: plain checks in server components, and toggle buttons that keep their state in a client component.

Use a checkbox for an option people turn on or off, and radios when they pick one choice out of a few. A switch is a checkbox drawn as a toggle. AdminKit for Next.js draws all three with Bootstrap's .form-check markup in JSX. They are in the free and PRO versions of AdminKit for Next.js. In JSX, set the starting state with defaultChecked, not checked.

Because they are plain markup, not components, a page that only renders them stays a server component. It renders on the server and needs no "use client". When the checked state drives something else on the page, that state has to live in the browser. Move the checks into a client component, a file that starts with "use client", and use checked and onChange, as shown below.

Example

<div className="form-check">
  <input className="form-check-input" type="checkbox" value="" id="checkDefault" />
  <label className="form-check-label" htmlFor="checkDefault">Email me about new comments</label>
</div>
<div className="form-check">
  <input className="form-check-input" type="checkbox" value="" id="checkChecked" defaultChecked />
  <label className="form-check-label" htmlFor="checkChecked">Email me a weekly summary</label>
</div>

The demo pages also use a form without ids: a <label className="form-check"> wraps the input and a <span className="form-check-label">.

Classes

ClassElementDescription
form-checkwrapperLays out the input and its label
form-check-input<input>The checkbox or radio
form-check-label<label>, <span>The label text
form-check-inline.form-checkPuts the checks on one line
form-switch.form-checkDraws a checkbox as a switch
btn-check<input>Hides the input; its .btn label becomes a toggle button

Radios

For one choice out of a few, give the radios the same name. They then form one group.

<div className="form-check">
  <input className="form-check-input" type="radio" name="plan" value="monthly" id="radioMonthly" defaultChecked />
  <label className="form-check-label" htmlFor="radioMonthly">Monthly</label>
</div>
<div className="form-check">
  <input className="form-check-input" type="radio" name="plan" value="yearly" id="radioYearly" />
  <label className="form-check-label" htmlFor="radioYearly">Yearly</label>
</div>
<div className="form-check">
  <input className="form-check-input" type="radio" name="plan" value="lifetime" id="radioLifetime" disabled />
  <label className="form-check-label" htmlFor="radioLifetime">Lifetime</label>
</div>

Add form-check-inline to each .form-check to put them on one line.

Switches

For a setting, add form-switch to the .form-check. The checkbox is then drawn as a switch:

<div className="form-check form-switch">
  <input className="form-check-input" type="checkbox" id="switchDefault" />
  <label className="form-check-label" htmlFor="switchDefault">Default switch checkbox input</label>
</div>
<div className="form-check form-switch">
  <input className="form-check-input" type="checkbox" id="switchChecked" defaultChecked />
  <label className="form-check-label" htmlFor="switchChecked">Checked switch checkbox input</label>
</div>
<div className="form-check form-switch">
  <input className="form-check-input" type="checkbox" id="switchDisabled" disabled />
  <label className="form-check-label" htmlFor="switchDisabled">Disabled switch checkbox input</label>
</div>

Controlled toggle buttons

When a choice has to update other parts of the page, its state lives in a client component. The billing period toggle of the Pricing page works like this: two .btn-check radios whose checked comes from state.

src/components/billing-period-toggle.tsx
"use client";

import { useState } from "react";

export function BillingPeriodToggle() {
  const [period, setPeriod] = useState<"monthly" | "yearly">("monthly");

  return (
    <div className="btn-group" role="group" aria-label="Billing period">
      <input
        type="radio"
        className="btn-check"
        name="billing-period"
        id="billingMonthly"
        value="monthly"
        autoComplete="off"
        checked={period === "monthly"}
        onChange={() => setPeriod("monthly")}
      />
      <label className="btn btn-outline-primary" htmlFor="billingMonthly">Monthly</label>
      <input
        type="radio"
        className="btn-check"
        name="billing-period"
        id="billingYearly"
        value="yearly"
        autoComplete="off"
        checked={period === "yearly"}
        onChange={() => setPeriod("yearly")}
      />
      <label className="btn btn-outline-primary" htmlFor="billingYearly">Yearly</label>
    </div>
  );
}

In the demo, a context provider holds the period, so the prices elsewhere on the server-rendered page follow the toggle. The state starts at "monthly" on the server and in the browser. That keeps hydration, the browser taking over the server HTML, free of mismatches.

For a checkbox, read event.target.checked in onChange. A checked without onChange is read-only, and React warns about it.

Accessibility

  • Label each check: <label htmlFor> with the input's id, a wrapping <label>, or aria-label without visible text.
  • Put a group of radios or checkboxes in a <fieldset> with a <legend>, or give it role="group" and an aria-label.
  • Screen readers announce a switch as a checkbox, so its label should name the setting it turns on.
  • .btn-check inputs are hidden visually but stay focusable: Tab and the arrow keys reach the toggle buttons.

Last updated on

On this page