Building a whole Tailwind app, not just this component?
You just browsed 29 Tailwind checkboxes. Untitled UI Pro unlocks 5,000+ components and sections and 250+ page examples for React, styled with Tailwind CSS v4. One licence is a one-time $349 with lifetime updates; teams start at $999.
The settings-page classic in utilities: a pill track and a gliding thumb, driven by a peer checkbox that stays in the tab order via sr-only. The track paints with peer-checked:bg-tcb01-acc; the thumb, nested one level deeper, moves with group-has-checked:translate-x-5.
The e-commerce facet bar in a single class list per chip: has-checked:bg-tcb02-acc repaints the whole <label>, a leading tick unfolds, and a pure-CSS counter tallies the active filters live — has-checked:[counter-increment:tcb02] plus after:content-[counter(tcb02)]. Zero JavaScript.
Selectable pricing cards that lift, outline and badge themselves on selection — all from has-checked: on the card label. Because plans are mutually exclusive, the port quietly swaps the original checkboxes for a named radio group: same look, correct semantics, arrow-key navigation for free.
A smooth-height FAQ accordion with zero script: each panel animates grid-rows-[0fr] → peer-checked:grid-rows-[1fr], the only reliable way to transition to auto height. The chevron chip flips and fills from group-has-checked:.
The "select all" pattern with a real mixed state: peer-indeterminate:bg-tcb05-acc fills the master box and group-has-indeterminate:opacity-100 reveals its dash. A 20-line controller syncs parent and children — and now announces "2 of 4 selected" through a polite live region.
The reference implementation for keyboard users: a double focus ring (white gap + 3px blue) that only appears for keyboard focus via peer-focus-visible:, a disabled row that stays legible instead of fading to 50% opacity, and a described-by hint so screen readers explain why it's disabled.
Newsletter
Enjoying these demos? Get new ones by email.
A short email when a new collection ships. Unsubscribe any time.
A wider switch that prints ON and OFF inside its own track via before:content-['ON'] / after:content-['OFF'], cross-fading on group-has-checked:. The thumb glides on transform-gpu with will-change-transform, and each row card picks up an accent edge when on.
A cyan glow that breathes around every checked box: a layered shadow-tcb08-glow token on a before: pseudo-element, pulsed with animate-tcb08-pulse — opacity only, so the glow never repaints the layout. Checked labels brighten to full white.
The checkmark draws itself: an inline SVG path starts fully dashed-out with [stroke-dashoffset:24] and animates to group-has-checked:[stroke-dashoffset:0]. Rows become a pipeline with a connecting rail and step captions.
A squash-and-stretch pop on check: peer-checked:animate-tcb10-pop squeezes the box to 82% then overshoots to 112%, while the tick springs in on an ease-[cubic-bezier(.34,1.7,.5,1)] overshoot curve. Pressing now squashes the box before release.
Frosted-glass checkboxes on a panel that actually has something to blur: two soft colour orbs drift behind backdrop-blur-xl backdrop-saturate-150. Browsers without backdrop-filter get an opaque tinted panel via supports-[backdrop-filter:blur(1px)]:, so the white text never lands on a bright gradient.
Soft-UI boxes that look extruded when off and pressed-in when on: the two states are two shadow tokens, shadow-tcb12-out and peer-checked:shadow-tcb12-in. Checked labels darken and a thin accent edge keeps the state legible — neumorphism's usual weakness.
Water rises into the box on check: an oversized, wavy-topped blob slides up from translate-y-[105%] to group-has-checked:translate-y-0, then keeps sloshing with animate-tcb13-wave. Hovering an empty box now lets the water peek in, previewing the action.
A circular checkbox whose ring spins a full turn as the tick twists into place. The port fixes a quiet bug: the original rotated a uniform ring, which looks identical at every angle — here the ring has a lighter arc (border-t-tcb14-acc/25), so the group-has-checked:rotate-360 is actually visible.
Newsletter
Enjoying these demos? Get new ones by email.
A short email when a new collection ships. Unsubscribe any time.
Thick black borders, a 4px offset shadow and a box that physically presses into its shadow on check (group-has-checked:translate-1 shadow-none). New: the checked label gets a yellow marker highlight with box-decoration-clone, so the whole row shouts its state.
Checked boxes fill with a rainbow that sweeps endlessly: a 300%-wide gradient on before:, scrolled by animate-tcb16-sweep. New: the checked label picks up the same gradient as text via bg-clip-text, tying label and box together.
Each box is a two-faced card that flips on its Y axis to reveal a filled, ticked back — now using Tailwind v4's native 3D utilities: perspective-[600px], transform-3d, backface-hidden and rotate-y-180. Reduced-motion users get a cross-fade instead.
Material-style checkboxes with the halo done right: a 40px circular state layer sits behind the box, tints on hover and focus, and emits a ripple (group-has-checked:animate-tcb18-ripple) that escapes the box instead of being clipped inside it.
An arcade menu where every tick is drawn in pixels: one 3px square plus a shadow-tcb19-tick token that stamps six more. Now with the real game-menu touch — a blinking ▶ cursor that appears beside the row you're on (hover or keyboard focus), in Press Start 2P.
The to-do list done properly: ticking a task draws a strike across its label with after:scale-x-0 → group-has-checked:after:scale-x-100, and a zero-JS header counter reports "2 of 4 done" using CSS counters. Done items stay readable at 4.6:1 instead of fading to 2.3:1.
Two blobs merge into a fill like drops of ink: a satellite orbits out and back while the main blob swells, and blur-[4px] contrast-[14] on their parent fuses them into one sticky shape. The port fixes the effect itself — the contrast trick only works over an opaque backdrop, which the original lacked.
A day/night switch that actually changes the theme around it: has-checked: on the stage and group-has-checked/stage: on the card recolour the whole demo, while the thumb morphs from a glowing sun into a cratered moon over a star field. Zero JavaScript.
Newsletter
Enjoying these demos? Get new ones by email.
A short email when a new collection ships. Unsubscribe any time.
Round checkboxes that broadcast when on: two rings pulse outward half a cycle apart ([animation-delay:1s]), and checked rows gain a red LIVE chip. The rings live behind the box as siblings, so no negative z-index games.
A pending dash retracts as a checkmark draws in — two SVG paths trading stroke-dashoffset with a 120ms stagger, while the whole glyph tilts -rotate-8. New: a status label on the right swaps Pending → Approved, so the row states its meaning in words.
A review checklist whose boxes cycle Unset → Done → N/A. The script only writes aria-checked; every visual — fill colour, glyph, status chip — is an ARIA variant: group-aria-checked/btn: and group-aria-[checked=mixed]/btn:. Now the whole row is clickable and every target is 44px.
The box leans toward your pointer as it crosses the row and springs back when you leave. The script writes only two custom properties, which v4 reads with translate-x-(--tcb26-dx); the label now follows at half strength for a layered parallax, and updates are batched per frame.
Eight confetti flakes burst from the box on check — squares and dots, each flying to its own vector via per-particle custom properties ([--tx:-16px] [--ty:-14px] [--rot:120deg]) read by one shared animate-tcb27-pop keyframe. The tick springs in behind the burst. Zero JavaScript.
Checking a row slides a SAVED tag in from the left, pushing the label along. The port rebuilds the mechanics: instead of translating a tag into overlap and patching the gap with margin-right, the tag sits in a grid column that animates grid-cols-[0fr_auto] → grid-cols-[1fr_auto], so the label moves because the layout does.
A premium dark-UI switch whose track fills with a drifting aurora mesh — three radial gradients over a deep base, animated by animate-tcb29-drift. The mesh is the collection's one @utility, because a four-layer background repeated in three class lists is worse than a named utility. The thumb now glows when on. Near-zero CSS.
FAQ
Frequently asked questions
How do these checkboxes hide the native box but stay keyboard accessible?
Every demo keeps a real <input type="checkbox"> in the DOM and visually hides it with a sr-only/peer pattern — clipped to a 1px box, not removed. The visible box is a sibling element styled entirely with peer-checked: and peer-focus-visible: utilities. This is the load-bearing rule: never use hidden or display:none on the input, because that pulls it out of the tab order and stops screen readers announcing checked state. Wrap the input and its fake box in a <label> so a click or tap on the drawn box toggles the input for free, and the whole row becomes one hit target. Because the input still fires natively, Space toggles it and forms submit its value with no script — see demo 06, the accessible custom checkbox.
What is the difference between peer-checked and has-checked in these demos?
peer-checked: styles a later sibling of the input — you mark the input peer, then any following element reacts, which is why the fake box and label must come after the input in source order. has-checked: styles the ancestor that contains a checked input, using the :has() selector, so the whole card, chip or stage can repaint from one checkbox anywhere inside it. Demo 02's filter chip and demo 03's selectable pricing card use has-checked: to light up the entire container; demo 22's dark-mode toggle puts has-checked: on the stage to swap the surrounding theme. The common bug is reaching for peer-checked: when the element you want to style is a parent or an earlier sibling — peer only looks forward, so nothing happens. Choose by asking whether the target sits after the input or wraps it.
How does the pure-CSS accordion animate height with no JavaScript?
Demo 04 animates grid-template-rows, not height, because height:auto is not animatable but a grid track between grid-rows-[0fr] and grid-rows-[1fr] is. The panel is a grid whose single row goes from 0fr (collapsed) to 1fr (open) via peer-checked:grid-rows-[1fr], with a transition on the rows. The inner content needs overflow-hidden and min-h-0 or it refuses to shrink below its intrinsic height and the collapse looks broken. The toggle itself is a hidden checkbox with a <label> as the clickable header. Because it is a checkbox rather than radios, more than one panel can be open at once; swap to grouped radios if you need a single-open accordion. It respects prefers-reduced-motion by dropping the transition, so the panel snaps instead of sliding for users who ask for less motion.
How does the indeterminate 'select all' checkbox work with a tri-state master?
Demo 05 uses the real DOM property: an input is neither checked nor unchecked but indeterminate, targeted in Tailwind with peer-indeterminate: to fill the master box with a dash instead of a tick. The catch is that indeterminate cannot be set in HTML — there is no attribute for it, so it needs a small script that watches the child boxes and sets master.indeterminate = someButNotAll while setting master.checked only when every child is on. Clicking the master should then check or uncheck all children and clear its own indeterminate flag. Keep aria-checked="mixed" in sync so screen readers announce the partial state. Do not fake this with a third image swap on a plain checkbox — assistive tech reads the property, not the pixels, so the real indeterminate flag is what makes the pattern honest.
Why does my custom checkbox lose its focus ring, and how do these keep it?
The ring disappears because hiding the native input with sr-only also hides the browser's default outline — the outline was on the input you just clipped, and your drawn box has none of its own. Demo 06 fixes it by moving the ring onto the visible box with peer-focus-visible:, layering a white gap ring plus a 3px blue ring so it reads on any background. Use focus-visible, not focus, so the ring shows for keyboard users but not on every mouse click. Do not set outline-none without a replacement — that is the single most common accessibility regression in custom checkboxes and it strands keyboard users with no visible position. Because the input still receives focus natively, Tab lands on it and Space toggles; the styling is the only thing you relocated. Test by tabbing through with the mouse untouched.
How does the dark-mode toggle change the theme with no JavaScript?
Demo 22 is a checkbox, not a script. The sun/moon switch is a hidden input marked peer, and the surrounding stage carries has-checked: utilities so that when the box is checked the container's background, text and card colours all flip together via :has(). The thumb slides with peer-checked:translate-x and the sun/moon icons cross-fade with opacity. This works because everything that recolours lives inside the element carrying has-checked: — the theme is scoped to that subtree, not the document. That is also its limit: it cannot theme the whole page or persist across reloads, since there is no localStorage and no <html> class toggle. For a real site-wide theme you still need a script to write a class on the root and remember the choice; this pattern is right for a self-contained widget or a settings preview, not the global switch.
How do the animated checkmarks draw themselves, and what breaks the effect?
Demos 09 and 24 draw an inline <svg> path using the stroke-dash trick: the tick's stroke-dasharray equals its own length and the idle stroke-dashoffset is set to that length too, so the line is one long gap and invisible. On peer-checked: the offset transitions to 0, revealing the stroke as if a pen drew it. The most common bug is a wrong offset number — if stroke-dashoffset is smaller than the real path length, part of the tick shows before checking; larger and it never fully draws. Measure the path or use pathLength="..." to normalise it. Demo 24 trades two paths, retracting a dash while the check draws, which needs both offsets tuned to their own lengths. Guard the transition with prefers-reduced-motion so the tick appears instantly for users who opt out, otherwise the checked state depends on an animation they never see.
When should I use a toggle switch versus a checkbox, and which demos are which?
Semantics decide it, not looks. A toggle switch — demos 01, 07, 22, 29 — means an instant on/off setting that takes effect immediately, like enabling notifications; a checkbox means a selection you confirm later, usually before submitting a form. Do not dress a checkbox as a switch for a multi-select list, and do not use a switch where the user expects to tick several boxes and press a button. Under the hood both are the same <input type="checkbox"> here, so keyboard and form behaviour are identical; the difference is the affordance you show and the timing you imply. Avoid the flashy demos — confetti (27), gooey blobs (21), 3D flip (17) — for long forms or dense settings panels, where 30 competing animations become noise and the motion slows perceived response. Reserve those for a single hero interaction, and keep the workhorse rows on demos 01 or 06.