Forms

Checkbox Button Group

A multi-select group whose options are cards, built on a real checkbox group with keyboard selection and a per-item indicator.

Usage

Workspace add-onsBilled monthly, per workspace.

Anatomy

Import the CheckboxButtonGroup component and access all parts using dot notation.

import { CheckboxButtonGroup } from "@blakeui/pro-react";

<CheckboxButtonGroup>
  <CheckboxButtonGroup.Item>
    <CheckboxButtonGroup.ItemIcon />
    <CheckboxButtonGroup.ItemContent>
      <CheckboxButtonGroup.ItemTitle />
      <CheckboxButtonGroup.ItemDescription />
    </CheckboxButtonGroup.ItemContent>
    <CheckboxButtonGroup.Indicator />
  </CheckboxButtonGroup.Item>
</CheckboxButtonGroup>;

Controlled

Deploy to

Deploying to 1 region.

Custom Indicator

Token scopes

Disabled Group

Export formatsScheduled exports are paused while the warehouse resyncs.

Grid Layout

Notification channels

Icon Cards

Accepted payment methods

No Indicator

Optimise for

Render Prop Children

A function child receives this item's live selection state, so the card can change its own content on selection without lifting value out of the group.

Teams to invoice

Subscription Plans

Choose your planPlans stack, so pick every tier you want on the invoice.

Prices exclude VAT. Change or cancel at any time.

Compare every feature

With Icons

Project integrations

With Ripple

Neither package ships a web ripple, so this composes the third-party m3-ripple — the same one the free Button docs reach for. It sits inside the item as a pointer-events: none overlay, so the press still lands on the checkbox.

Connected apps

CSS Classes

Base Class

  • .checkbox-button-group — The root, which is also the free .checkbox-group. It declares the three custom properties below, sets a calc(--spacing * 2) gap between label, description, and list, and carries container-type: inline-size so the list can query it. An element can never match its own container query, which is why the containment sits here rather than on the list.

Layout Classes

  • .checkbox-button-group--flex — The default. The list stacks its cards in a column with a --spacing * 3 gap.
  • .checkbox-button-group--grid — The list becomes display: grid over repeat(var(--checkbox-button-group-columns), minmax(0, 1fr)) with the same gap. Below a 26rem container it collapses to a single column.

Element Classes

  • .checkbox-button-group__label — The group's <label>, with --spacing * 0.5 of bottom margin.
  • .checkbox-button-group__description — The supporting line under the label.
  • .checkbox-button-group__list — The box holding the items, and the one the layout modifiers apply to.
  • .checkbox-button-group__item — One card, and also the free .checkbox. A relatively-positioned flex row aligned to flex-start, --spacing * 4 of padding, 1px solid --border, border-radius: var(--checkbox-button-group-item-radius), and box-shadow: none — bordered-flat, like every card-family surface. Its rules are written as .checkbox-button-group .checkbox-button-group__item so they match the free checkbox rules' specificity and win on source order; without that the card would centre its contents and pick up a 16px top margin from .checkbox-group [data-slot="checkbox"].
  • .checkbox-button-group__indicator — The selection mark, absolutely positioned --spacing * 3.5 from the top and the inline end.
  • .checkbox-button-group__item-content — The text column. flex: 1 1 auto with a --spacing * 1 gap, and — only when the item actually has an indicator — padding-inline-end: var(--checkbox-button-group-indicator-gutter).
  • .checkbox-button-group__item-title — 0.875rem / 1.25rem at weight 500 in --foreground.
  • .checkbox-button-group__item-description — 0.75rem / 1rem in --muted, with text-wrap: pretty.
  • .checkbox-button-group__item-icon — A --spacing * 9 square in --default at calc(--radius * 2), holding a --spacing * 5 glyph. It repaints to --accent / --accent-foreground while the item is selected.

State Rules

  • [data-selected="true"] on the item — border-color: --accent, background-color: --accent-soft, and box-shadow: inset 0 0 0 1px var(--accent). The inset shadow is what takes the ring from 1px to 2px without reflowing the card, and it is a stroke rather than elevation.
  • [data-focus-visible="true"] / :focus-visible on the item — a two-stop outer ring built from --ring-offset-width, --background, and --focus. A selected card that is also focused keeps both rings, so "chosen" and "here" stay separately readable.
  • [data-disabled="true"] / [aria-disabled="true"] on the item — cursor: --cursor-disabled. The opacity and pointer-events: none already come from the free .checkbox, and the underlying control is a genuinely disabled input.
  • [data-custom="true"] on the indicator — set when Indicator is given children. It starts at opacity: 0; scale: 0.8 and reaches opacity: 1; scale: 1 once [data-selected="true"] is also present, which is how a custom mark shows only while the item is selected.
  • :is(:active, [data-pressed="true"]) on the item — transition-duration: 0s, so the press-in snaps and only the release eases back on the base 150ms.

Reduced Motion

The indicator's scale is dropped — and its transition-property narrowed to opacity alone — under both [data-reduce-motion="true"] and @media (prefers-reduced-motion: reduce), the media half scoped :not(:is([data-reduce-motion="true"], [data-reduce-motion="true"] *)) so the two never double-apply. The fade survives in both: reduced motion removes the movement, not the transition. The card's own transition is colour and shadow only, so it has nothing to opt out of.

CSS Variables

  • --checkbox-button-group-item-radius — The card corner. Declared on the root as var(--radius-2xl), the same radius the rest of the card family rounds to, which keeps the straight edge dominant on a short list row rather than reading as a pill. Note that @blakeui/styles redefines --radius-2xl as calc(var(--radius) * 2) rather than Tailwind's fixed 1rem, so it still tracks a theme that retunes the radius scale.
  • --checkbox-button-group-columns — Column count for the grid layout. Declared as 2 on the root; the columns prop sets it inline on the list.
  • --checkbox-button-group-indicator-gutter — Room reserved at the end of the content column so a wrapping title clears the indicator. Declared as calc(var(--spacing) * 7). Set it to 0 on a centred card, which reserves its indicator room at the top instead — the Icon Cards example above does exactly that.

Data Attributes

  • [data-slot="checkbox-button-group-indicator"], [data-slot="checkbox-button-group-item-content"], [data-slot="checkbox-button-group-item-title"], [data-slot="checkbox-button-group-item-description"], [data-slot="checkbox-button-group-item-icon"] — One per part, so any of them can be targeted without depending on its BEM class. The root and the item carry [data-slot="checkbox-group"] and [data-slot="checkbox"] from the free components they are built on.

API Reference

CheckboxButtonGroup

The root. A React Aria CheckboxGroup with role="group", so multi-selection, form value, and label association are handled for you.

PropTypeDefaultDescription
layout"flex" | "grid""flex"How the items are arranged. flex stacks them in a column; grid lays them out over columns tracks and collapses to one column below a 26rem container.
columnsnumber2Column count, applied as an inline --checkbox-button-group-columns on the list. It is read only when layout is "grid"; passing it to a flex group is ignored rather than an error.
labelReactNodeThe group's accessible name, rendered as a real label tied to the group. Omit it only when an aria-label names the group instead — a checkbox group with no name at all is a defect.
descriptionReactNodeA supporting line rendered under the label.
valuestring[]The selected values, for a controlled group. Pair it with onChange.
defaultValuestring[]The initially selected values, for an uncontrolled group.
onChange(value: string[]) => voidCalled with the full list of selected values whenever selection changes.
isDisabledbooleanfalseDisables every item in the group. Each one becomes a genuinely disabled input, so it leaves the tab order and is announced as disabled.
classNamestringMerged onto .checkbox-button-group, alongside the layout modifier. Use labelClassName, descriptionClassName, and listClassName to reach the parts the root renders for you.
childrenReactNodeThe items. They are wrapped in .checkbox-button-group__list, which is the box the layout applies to.

Also accepts every React Aria CheckboxGroup prop, including name, isRequired, isInvalid, and isReadOnly.

CheckboxButtonGroup.Item

One selectable card. A React Aria Checkbox, which renders a label around a visually-hidden input type="checkbox" — so the card's own text is its accessible name and Space toggles it.

PropTypeDefaultDescription
valuestringThe value this card contributes to the group while it is selected.
childrenReactNode | ((state) => ReactNode)The card's content. Pass a function to receive this item's live state — isSelected, isHovered, isPressed, isFocusVisible, isDisabled, and the rest of React Aria's checkbox render props.
isDisabledbooleanfalseDisables this card alone. It is skipped by the tab order and announced as disabled, because the underlying input really is disabled.
classNamestringMerged onto .checkbox-button-group__item. Tailwind utilities land in the utilities layer, so they win over the component's own rules — which is how the Icon Cards example turns the card into a centred column.

Also accepts every React Aria Checkbox prop.

CheckboxButtonGroup.Indicator

The card's selection mark, pinned to the top-right. It is aria-hidden: the checked state already lives on the input, so the indicator's job is to keep selection from being carried by colour alone.

PropTypeDefaultDescription
childrenReactNodeA custom mark, shown only while the item is selected. Left empty, the part renders the free checkbox — a control box that is visible at rest and fills in on selection. Omit the whole part for a card with no mark at all.
classNamestringMerged onto .checkbox-button-group__indicator.

Also accepts every native span attribute.

CheckboxButtonGroup.ItemContent

The card's text column.

PropTypeDefaultDescription
childrenReactNodeNormally ItemTitle and ItemDescription, though a card that needs a price or a chip can put arbitrary markup here — the Subscription Plans example does.
classNamestringMerged onto .checkbox-button-group__item-content.

Also accepts every native div attribute.

CheckboxButtonGroup.ItemTitle

The card's primary label. Rendered as a span, so it stays part of the checkbox's accessible name rather than introducing a heading.

PropTypeDefaultDescription
childrenReactNodeThe label text.
classNamestringMerged onto .checkbox-button-group__item-title.

Also accepts every native span attribute.

CheckboxButtonGroup.ItemDescription

The card's supporting line. Also a span, and also part of the accessible name — which is why it should read as more of the option, not as separate help text.

PropTypeDefaultDescription
childrenReactNodeThe secondary text. It wraps with text-wrap: pretty rather than truncating.
classNamestringMerged onto .checkbox-button-group__item-description.

Also accepts every native span attribute.

CheckboxButtonGroup.ItemIcon

The card's leading glyph square. aria-hidden, because the card is already named by its text and an icon that also announced would name the checkbox twice.

PropTypeDefaultDescription
childrenReactNodeThe glyph. An svg is sized to --spacing * 5 automatically.
classNamestringMerged onto .checkbox-button-group__item-icon.

Also accepts every native span attribute.

On this page