Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/react/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
- **Locale**: Added `useDateFormatter` hook for localized date formatting using `@internationalized/date`
- **Swap**: Added new `Swap` component for toggling between two visual states with CSS animations using dual presence
instances
- **Checkbox**: Added `maxSelectedValues` prop to `CheckboxGroup` to limit the number of selected values

## [5.31.0] - 2026-02-04

Expand Down
1 change: 1 addition & 0 deletions packages/react/src/components/checkbox/checkbox-group.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export const CheckboxGroup = forwardRef<HTMLDivElement, CheckboxGroupProps>((pro
'invalid',
'readOnly',
'name',
'maxSelectedValues',
])

const checkboxGroup = useCheckboxGroup(checkboxGroupProps)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export { GroupControlled } from './examples/group-controlled'
export { GroupProvider } from './examples/group-provider'
export { GroupWithForm } from './examples/group-with-form'
export { GroupWithInvalid } from './examples/group-with-invalid'
export { GroupWithMaxSelected } from './examples/group-with-max-selected'
export { GroupWithSelectAll } from './examples/group-with-select-all'
export { Indeterminate } from './examples/indeterminate'
export { Context } from './examples/context'
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { Checkbox } from '@ark-ui/react/checkbox'
import { CheckIcon } from 'lucide-react'
import styles from 'styles/checkbox.module.css'

export const GroupWithMaxSelected = () => (
<Checkbox.Group className={styles.Group} defaultValue={['react']} maxSelectedValues={2} name="framework">
{items.map((item) => (
<Checkbox.Root className={styles.Root} value={item.value} key={item.value}>
<Checkbox.Control className={styles.Control}>
<Checkbox.Indicator className={styles.Indicator}>
<CheckIcon />
</Checkbox.Indicator>
</Checkbox.Control>
<Checkbox.Label className={styles.Label}>{item.label}</Checkbox.Label>
<Checkbox.HiddenInput />
</Checkbox.Root>
))}
</Checkbox.Group>
)

const items = [
{ label: 'React', value: 'react' },
{ label: 'Solid', value: 'solid' },
{ label: 'Vue', value: 'vue' },
{ label: 'Svelte', value: 'svelte' },
]
13 changes: 11 additions & 2 deletions packages/react/src/components/checkbox/use-checkbox-group.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@ export interface UseCheckboxGroupProps {
* If `true`, the checkbox group is invalid
*/
invalid?: boolean | undefined
/**
* The maximum number of selected values
*/
maxSelectedValues?: number | undefined
}

export interface CheckboxGroupItemProps {
Expand All @@ -48,6 +52,7 @@ export function useCheckboxGroup(props: UseCheckboxGroupProps = {}) {
readOnly,
name,
invalid = fieldset?.invalid,
maxSelectedValues,
} = props

const interactive = !(disabled || readOnly)
Expand All @@ -68,9 +73,12 @@ export function useCheckboxGroup(props: UseCheckboxGroupProps = {}) {
isChecked(val) ? removeValue(val) : addValue(val)
}

const isAtMax = maxSelectedValues != null && value.length >= maxSelectedValues

const addValue = (val: string) => {
if (!interactive) return
if (isChecked(val)) return
if (isAtMax) return
setValue(value.concat(val))
}

Expand All @@ -80,15 +88,16 @@ export function useCheckboxGroup(props: UseCheckboxGroupProps = {}) {
}

const getItemProps = (props: CheckboxGroupItemProps) => {
const checked = props.value != null ? isChecked(props.value) : undefined
return {
checked: props.value != null ? isChecked(props.value) : undefined,
checked,
onCheckedChange() {
if (props.value != null) {
toggleValue(props.value)
}
},
name,
disabled,
disabled: disabled || (isAtMax && !checked),
readOnly,
invalid,
}
Expand Down
1 change: 1 addition & 0 deletions packages/solid/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
- **Locale**: Added `useDateFormatter` hook for localized date formatting using `@internationalized/date`
- **Swap**: Added new `Swap` component for toggling between two visual states with CSS animations using dual presence
instances
- **Checkbox**: Added `maxSelectedValues` prop to `CheckboxGroup` to limit the number of selected values

## [5.31.0] - 2026-02-04

Expand Down
1 change: 1 addition & 0 deletions packages/solid/src/components/checkbox/checkbox-group.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ export const CheckboxGroup = (props: CheckboxGroupProps) => {
'invalid',
'readOnly',
'name',
'maxSelectedValues',
])
const checkboxGroup = useCheckboxGroup(checkboxGroupProps)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export { GroupControlled } from './examples/group-controlled'
export { GroupProvider } from './examples/group-provider'
export { GroupWithForm } from './examples/group-with-form'
export { GroupWithInvalid } from './examples/group-with-invalid'
export { GroupWithMaxSelected } from './examples/group-with-max-selected'
export { GroupWithSelectAll } from './examples/group-with-select-all'
export { Indeterminate } from './examples/indeterminate'
export { Context } from './examples/context'
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { Checkbox } from '@ark-ui/solid/checkbox'
import { CheckIcon } from 'lucide-solid'
import { For } from 'solid-js'
import styles from 'styles/checkbox.module.css'

export const GroupWithMaxSelected = () => (
<Checkbox.Group class={styles.Group} defaultValue={['react']} maxSelectedValues={2} name="framework">
<For each={items}>
{(item) => (
<Checkbox.Root class={styles.Root} value={item.value}>
<Checkbox.Control class={styles.Control}>
<Checkbox.Indicator class={styles.Indicator}>
<CheckIcon />
</Checkbox.Indicator>
</Checkbox.Control>
<Checkbox.Label class={styles.Label}>{item.label}</Checkbox.Label>
<Checkbox.HiddenInput />
</Checkbox.Root>
)}
</For>
</Checkbox.Group>
)

const items = [
{ label: 'React', value: 'react' },
{ label: 'Solid', value: 'solid' },
{ label: 'Vue', value: 'vue' },
{ label: 'Svelte', value: 'svelte' },
]
12 changes: 10 additions & 2 deletions packages/solid/src/components/checkbox/use-checkbox-group.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@ export interface UseCheckboxGroupProps {
* If `true`, the checkbox group is invalid
*/
invalid?: boolean
/**
* The maximum number of selected values
*/
maxSelectedValues?: number
}

export interface CheckboxGroupItemProps {
Expand Down Expand Up @@ -59,9 +63,12 @@ export function useCheckboxGroup(props: UseCheckboxGroupProps = {}) {
isChecked(val) ? removeValue(val) : addValue(val)
}

const isAtMax = props.maxSelectedValues != null && value().length >= props.maxSelectedValues

const addValue = (val: string) => {
if (!interactive()) return
if (isChecked(val)) return
if (isAtMax) return
setValue(value().concat(val))
}

Expand All @@ -71,15 +78,16 @@ export function useCheckboxGroup(props: UseCheckboxGroupProps = {}) {
}

const getItemProps = (itemProps: CheckboxGroupItemProps) => {
const checked = itemProps.value != null ? isChecked(itemProps.value) : undefined
return {
checked: itemProps.value != null ? isChecked(itemProps.value) : undefined,
checked,
onCheckedChange() {
if (itemProps.value != null) {
toggleValue(itemProps.value)
}
},
name: props.name,
disabled: disabled(),
disabled: disabled() || (isAtMax && !checked),
readOnly: props.readOnly,
invalid: invalid(),
}
Expand Down
1 change: 1 addition & 0 deletions packages/svelte/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ description: All notable changes will be documented in this file.
- **Locale**: Added `useDateFormatter` hook for localized date formatting using `@internationalized/date`
- **Swap**: Added new `Swap` component for toggling between two visual states with CSS animations using dual presence
instances
- **Checkbox**: Added `maxSelectedValues` prop to `CheckboxGroup` to limit the number of selected values

## [5.16.0] - 2026-02-04

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import DisabledExample from './examples/disabled.svelte'
import GroupProviderExample from './examples/group-provider.svelte'
import GroupWithFormExample from './examples/group-with-form.svelte'
import GroupWithInvalidExample from './examples/group-with-invalid.svelte'
import GroupWithMaxSelectedExample from './examples/group-with-max-selected.svelte'
import GroupWithSelectAllExample from './examples/group-with-select-all.svelte'
import GroupExample from './examples/group.svelte'
import IndeterminateExample from './examples/indeterminate.svelte'
Expand Down Expand Up @@ -78,6 +79,12 @@ export const GroupWithInvalid = {
}),
}

export const GroupWithMaxSelected = {
render: () => ({
Component: GroupWithMaxSelectedExample,
}),
}

export const GroupWithSelectAll = {
render: () => ({
Component: GroupWithSelectAllExample,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<script lang="ts">
import { Checkbox } from '@ark-ui/svelte/checkbox'
import { CheckIcon } from 'lucide-svelte'
import styles from 'styles/checkbox.module.css'

const items = [
{ label: 'React', value: 'react' },
{ label: 'Solid', value: 'solid' },
{ label: 'Vue', value: 'vue' },
{ label: 'Svelte', value: 'svelte' },
]
</script>

<Checkbox.Group class={styles.Group} defaultValue={['react']} maxSelectedValues={2} name="framework">
{#each items as item (item.value)}
<Checkbox.Root class={styles.Root} value={item.value}>
<Checkbox.Control class={styles.Control}>
<Checkbox.Indicator class={styles.Indicator}>
<CheckIcon />
</Checkbox.Indicator>
</Checkbox.Control>
<Checkbox.Label class={styles.Label}>{item.label}</Checkbox.Label>
<Checkbox.HiddenInput />
</Checkbox.Root>
{/each}
</Checkbox.Group>
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,13 @@ import type { UseCheckboxGroupProps } from './use-checkbox-group.svelte'
const splitFn = createSplitProps<UseCheckboxGroupProps>()

export const splitCheckboxGroupProps = <T extends UseCheckboxGroupProps>(props: T) =>
splitFn(props, ['defaultValue', 'value', 'onValueChange', 'disabled', 'invalid', 'readOnly', 'name'])
splitFn(props, [
'defaultValue',
'value',
'onValueChange',
'disabled',
'invalid',
'readOnly',
'name',
'maxSelectedValues',
])
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@ export interface UseCheckboxGroupProps {
* If `true`, the checkbox group is invalid
*/
invalid?: boolean
/**
* The maximum number of selected values
*/
maxSelectedValues?: number
}

export interface CheckboxGroupItemProps {
Expand Down Expand Up @@ -67,9 +71,12 @@ export const useCheckboxGroup = (props: MaybeFunction<UseCheckboxGroupProps> = {
isChecked(val) ? removeValue(val) : addValue(val)
}

const isAtMax = $derived(resolvedProps.maxSelectedValues != null && value.length >= resolvedProps.maxSelectedValues)

const addValue = (val: string) => {
if (!interactive) return
if (isChecked(val)) return
if (isAtMax) return
setValue(value.concat(val))
}

Expand All @@ -79,15 +86,16 @@ export const useCheckboxGroup = (props: MaybeFunction<UseCheckboxGroupProps> = {
}

const getItemProps = (itemProps: CheckboxGroupItemProps) => {
const checked = itemProps.value != null ? isChecked(itemProps.value) : undefined
return {
checked: itemProps.value != null ? isChecked(itemProps.value) : undefined,
checked,
onCheckedChange() {
if (itemProps.value != null) {
toggleValue(itemProps.value)
}
},
name: resolvedProps.name,
disabled: !!disabled,
disabled: !!disabled || (isAtMax && !checked),
readOnly: !!resolvedProps.readOnly,
invalid: !!invalid,
}
Expand Down
1 change: 1 addition & 0 deletions packages/vue/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
- **Locale**: Added `useDateFormatter` hook for localized date formatting using `@internationalized/date`
- **Swap**: Added new `Swap` component for toggling between two visual states with CSS animations using dual presence
instances
- **Checkbox**: Added `maxSelectedValues` prop to `CheckboxGroup` to limit the number of selected values

### Fixed

Expand Down
4 changes: 4 additions & 0 deletions packages/vue/src/components/checkbox/checkbox-group.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@ export interface GroupProps {
* If `true`, the checkbox group is invalid
*/
invalid?: boolean
/**
* The maximum number of selected values
*/
maxSelectedValues?: number
}

export type GroupEmits = {
Expand Down
8 changes: 8 additions & 0 deletions packages/vue/src/components/checkbox/checkbox.stories.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import GroupProviderExample from './examples/group-provider.vue'
import GroupWithFieldsetExample from './examples/group-with-fieldset.vue'
import GroupWithFormExample from './examples/group-with-form.vue'
import GroupWithInvalidExample from './examples/group-with-invalid.vue'
import GroupWithMaxSelectedExample from './examples/group-with-max-selected.vue'
import GroupWithSelectAllExample from './examples/group-with-select-all.vue'
import GroupExample from './examples/group.vue'
import IndeterminateExample from './examples/indeterminate.vue'
Expand Down Expand Up @@ -128,6 +129,13 @@ export const GroupWithInvalid = {
}),
}

export const GroupWithMaxSelected = {
render: () => ({
components: { Component: GroupWithMaxSelectedExample },
template: '<Component />',
}),
}

export const GroupWithSelectAll = {
render: () => ({
components: { Component: GroupWithSelectAllExample },
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<script setup lang="ts">
import { Checkbox } from '@ark-ui/vue/checkbox'
import { CheckIcon } from 'lucide-vue-next'
import styles from 'styles/checkbox.module.css'

const items = [
{ label: 'React', value: 'react' },
{ label: 'Solid', value: 'solid' },
{ label: 'Vue', value: 'vue' },
{ label: 'Svelte', value: 'svelte' },
]
</script>

<template>
<Checkbox.Group :class="styles.Group" :defaultValue="['react']" :maxSelectedValues="2" name="framework">
<Checkbox.Root :class="styles.Root" v-for="item in items" :value="item.value" :key="item.value">
<Checkbox.Control :class="styles.Control">
<Checkbox.Indicator :class="styles.Indicator">
<CheckIcon />
</Checkbox.Indicator>
</Checkbox.Control>
<Checkbox.Label :class="styles.Label">{{ item.label }}</Checkbox.Label>
<Checkbox.HiddenInput />
</Checkbox.Root>
</Checkbox.Group>
</template>
Loading