data-toggle-tags

Style checkboxes as selectable pill chips for tag-based filtering and multi-select. Pure CSS with optional JavaScript for max selection limits.

Overview

The data-toggle-tags attribute transforms a fieldset of checkboxes into selectable pill-shaped tags. The styling is pure CSS via :has(:checked) — JavaScript is only loaded when you need a max selection limit via data-max.

<fieldset data-toggle-tags>\n <legend>Topics</legend>\n <label><input type="checkbox" name="topics" value="ai"> AI / ML</label>\n <label><input type="checkbox" name="topics" value="web"> Web Dev</label>\n <label><input type="checkbox" name="topics" value="mobile"> Mobile</label>\n</fieldset>

How It Works

Add data-toggle-tags to a <fieldset> containing checkbox inputs inside labels. The mechanism is primarily CSS:

  1. Each <label> is styled as a pill chip with padding, border, and rounded corners
  2. The checkbox inside each label is visually hidden with opacity: 0
  3. CSS :has(:checked) detects when a checkbox is selected and applies the active style
  4. Clicking the label toggles the hidden checkbox, which toggles the visual state
  5. No JavaScript is needed for the basic toggle behavior

When data-max is set, a small JavaScript module is loaded. It listens for checkbox changes and disables unchecked checkboxes once the limit is reached, re-enabling them when selections are removed.

Attributes

Attribute Type Description
data-toggle-tags boolean Placed on a <fieldset>. Enables pill chip styling for child checkbox labels.
data-max number Maximum number of selections allowed. When reached, unchecked checkboxes are disabled. Requires JavaScript.

Basic Tags

Without data-max, toggle tags are entirely CSS-driven. Users can select and deselect any number of tags freely.

Topics
<fieldset data-toggle-tags>\n <legend>Topics</legend>\n <label><input type="checkbox" name="topics" value="ai"> AI / ML</label>\n <label><input type="checkbox" name="topics" value="web"> Web Dev</label>\n <label><input type="checkbox" name="topics" value="mobile"> Mobile</label>\n</fieldset>

Max Selection Limit

Add data-max to cap the number of selections. When the limit is reached, remaining unchecked tags are disabled and visually muted. Deselecting a tag re-enables the others.

Pick up to 3
<fieldset data-toggle-tags data-max="3">\n <legend>Pick up to 3</legend>\n <label><input type="checkbox" name="skills" value="js"> JavaScript</label>\n <label><input type="checkbox" name="skills" value="py"> Python</label>\n <label><input type="checkbox" name="skills" value="go"> Go</label>\n <label><input type="checkbox" name="skills" value="rust"> Rust</label>\n <label><input type="checkbox" name="skills" value="ts"> TypeScript</label>\n <label><input type="checkbox" name="skills" value="ruby"> Ruby</label>\n</fieldset>

Pre-checked Tags

Add the native checked attribute to pre-select tags on load. This works with or without data-max.

Interests
<fieldset data-toggle-tags>\n <legend>Interests</legend>\n <label><input type="checkbox" name="interest" value="music" checked> Music</label>\n <label><input type="checkbox" name="interest" value="film"> Film</label>\n <label><input type="checkbox" name="interest" value="books" checked> Books</label>\n <label><input type="checkbox" name="interest" value="sports"> Sports</label>\n</fieldset>

Filter UI Pattern

Toggle tags work naturally in filter forms. The checkboxes submit as standard form values, making server-side filtering straightforward.

<form> <fieldset data-toggle-tags> <legend>Filter by category</legend> <label><input type="checkbox" name="cat" value="design"> Design</label> <label><input type="checkbox" name="cat" value="dev"> Development</label> <label><input type="checkbox" name="cat" value="marketing"> Marketing</label> <label><input type="checkbox" name="cat" value="product"> Product</label> </fieldset> <button type="submit">Apply filters</button> </form>

Form Participation

Because toggle tags use real checkboxes, they participate fully in form behavior without JavaScript:

  • Submission — checked tags submit as name=value pairs
  • Reset<button type="reset"> restores original checked states
  • Validation — native required works on individual checkboxes
  • FormDatanew FormData(form) includes all selected tags

Styling

The pill chip appearance is driven entirely by CSS. The :has(:checked) selector eliminates the need for JavaScript class toggling.

/* Tags use CSS :has() — no JS needed for styling */ [data-toggle-tags] label { display: inline-flex; align-items: center; padding: var(--size-2xs) var(--size-s); border: 1px solid var(--color-border); border-radius: var(--radius-pill); cursor: pointer; transition: background-color 0.15s, border-color 0.15s; } /* Checked state via :has() */ [data-toggle-tags] label:has(:checked) { background: var(--color-primary); border-color: var(--color-primary); color: var(--color-on-primary); } /* Hide the checkbox visually */ [data-toggle-tags] input[type="checkbox"] { position: absolute; opacity: 0; width: 0; height: 0; } /* Disabled state when max reached */ [data-toggle-tags] label:has(:disabled:not(:checked)) { opacity: 0.5; cursor: not-allowed; }

The checked state swaps the background to --color-primary and the text to --color-on-primary. Disabled tags (from data-max) use reduced opacity.

Dynamic Elements

Fieldsets added to the DOM after page load are automatically enhanced via a MutationObserver when data-max is present. No manual initialization is needed. Tags without data-max require no JavaScript at all.

// Dynamically added toggle-tags fieldsets are auto-enhanced via MutationObserver const fieldset = document.createElement('fieldset'); fieldset.dataset.toggleTags = ''; fieldset.dataset.max = '2'; fieldset.innerHTML = ` <legend>Pick up to 2</legend> <label><input type="checkbox" name="opt" value="a"> Option A</label> <label><input type="checkbox" name="opt" value="b"> Option B</label> <label><input type="checkbox" name="opt" value="c"> Option C</label> `; document.body.appendChild(fieldset); // fieldset is ready — no manual init needed

Accessibility

  • Native <input type="checkbox"> elements provide full keyboard support — Tab to navigate, Space to toggle
  • The <fieldset> and <legend> provide a group label announced by screen readers
  • Checked/unchecked state is conveyed natively by the checkbox semantics
  • Disabled state (from data-max) is announced via the native disabled attribute
  • Form reset restores original checked states, including visual styling
  • Focus indicators use :focus-visible on the label for clear keyboard navigation
  • Without JavaScript, checkboxes render as standard checkboxes with label text (progressive enhancement)