CheckboxGroup 复选框组

一组复选框,共用一个数组值。

'use client'

import { useState } from 'react'
import { CheckboxGroup } from '@hina-ui/react'

const options = [
  { value: 'gal', label: 'Galgame' },
  { value: 'ln', label: '轻小说' },
  { value: 'manga', label: '漫画' },
]

export default function Demo() {
  const [types, setTypes] = useState<Array<string | number>>(['gal', 'ln'])

  return (
    <CheckboxGroup value={types} onValueChange={setTypes} options={options} aria-label="作品类型" />
  )
}
tsx

用法

import { CheckboxGroup } from '@hina-ui/react'
ts

复选框组按 options 渲染一列 Checkbox,value / onValueChange 绑定已选值的数组,选项的类型见 Select。未声明的属性都会传给根元素,应当用 aria-label 或者 aria-labelledby 给整组命名。

当前值:[ "mail" ]

'use client'

import { useState } from 'react'
import { CheckboxGroup, Stack, Text } from '@hina-ui/react'

const options = [
  { value: 'mail', label: '邮件' },
  { value: 'push', label: '站内推送' },
  { value: 'sms', label: '短信' },
]

export default function Demo() {
  const [channels, setChannels] = useState<Array<string | number>>(['mail'])

  return (
    <Stack gap="sm">
      <CheckboxGroup
        value={channels}
        onValueChange={setChannels}
        options={options}
        aria-label="通知方式"
      />
      <Text size="sm" tone="muted">
        当前值:{JSON.stringify(channels, null, 2)}
      </Text>
    </Stack>
  )
}
tsx

示例

横排

orientation 为 horizontal 时选项横向排列,放不下时折行。

'use client'

import { useState } from 'react'
import { CheckboxGroup } from '@hina-ui/react'

const options = [
  { value: 'mon', label: '周一' },
  { value: 'wed', label: '周三' },
  { value: 'fri', label: '周五' },
  { value: 'sat', label: '周六' },
  { value: 'sun', label: '周日' },
]

export default function Demo() {
  const [days, setDays] = useState<Array<string | number>>(['sat', 'sun'])

  return (
    <CheckboxGroup
      value={days}
      onValueChange={setDays}
      options={options}
      orientation="horizontal"
      aria-label="更新日"
    />
  )
}
tsx

描述

选项的 description 显示在文字下方。

'use client'

import { useState } from 'react'
import { CheckboxGroup } from '@hina-ui/react'

const options = [
  { value: 'read', label: '读取', description: '查看收藏与阅读进度' },
  { value: 'write', label: '写入', description: '修改收藏与评分' },
  { value: 'admin', label: '管理', description: '管理令牌与授权范围' },
]

export default function Demo() {
  const [scopes, setScopes] = useState<Array<string | number>>(['read'])

  return (
    <CheckboxGroup
      value={scopes}
      onValueChange={setScopes}
      options={options}
      aria-label="授权范围"
    />
  )
}
tsx

设置行

使用 controlPlacement="end" 将控件放到文案末端,配合 block 撑满容器宽度。说明始终位于标题下方;start 和 end 会跟随文字方向。启用 block 后,纵向选项各自撑满一行,横向选项等分行宽。orientation 仍表示多个选项的排列方向。

'use client'

import { useState } from 'react'
import { CheckboxGroup } from '@hina-ui/react'

const options = [
  { value: 'read', label: '读取', description: '查看收藏与阅读进度' },
  { value: 'write', label: '写入', description: '修改收藏与评分' },
  { value: 'admin', label: '管理', description: '管理令牌与授权范围' },
]

export default function Demo() {
  const [scopes, setScopes] = useState<Array<string | number>>(['read'])

  return (
    <CheckboxGroup
      value={scopes}
      onValueChange={setScopes}
      controlPlacement="end"
      block
      options={options}
      aria-label="授权范围"
    />
  )
}
tsx

尺寸

size 下发到每个复选框。

import { CheckboxGroup, Inline } from '@hina-ui/react'

const options = [
  { value: 'gal', label: 'Galgame' },
  { value: 'ln', label: '轻小说' },
]

export default function Demo() {
  return (
    <Inline gap="lg" align="start">
      <CheckboxGroup size="sm" options={options} value={['gal']} aria-label="小号" />
      <CheckboxGroup size="md" options={options} value={['gal']} aria-label="中号" />
      <CheckboxGroup size="lg" options={options} value={['gal']} aria-label="大号" />
    </Inline>
  )
}
tsx

状态

选项上的 disabled 只禁用该项,组上的 disabled 禁用整组;invalid 落到每个方框。

import { CheckboxGroup, Inline } from '@hina-ui/react'

const options = [
  { value: 'gal', label: 'Galgame' },
  { value: 'ln', label: '轻小说' },
  { value: 'manga', label: '漫画', disabled: true },
]

export default function Demo() {
  return (
    <Inline gap="lg" align="start">
      <CheckboxGroup options={options} value={['gal']} aria-label="含禁用项" />
      <CheckboxGroup disabled options={options} value={['gal']} aria-label="整组禁用" />
      <CheckboxGroup invalid options={options} value={[]} aria-label="校验未通过" />
    </Inline>
  )
}
tsx

定制内容

renderOption 属性定制每一项的文字。

组件从 options 推断完整选项类型,渲染函数中的 option 保留额外字段及其类型;value / onValueChange 仍绑定选项的 value 数组。类型定义见 Select。

'use client'

import { useState } from 'react'
import { BookOpen, Gamepad2, Images } from 'lucide-react'
import { CheckboxGroup, Inline } from '@hina-ui/react'

const icons = { gal: Gamepad2, ln: BookOpen, manga: Images }
const options = [
  { value: 'gal', label: 'Galgame' },
  { value: 'ln', label: '轻小说' },
  { value: 'manga', label: '漫画' },
]

export default function Demo() {
  const [types, setTypes] = useState<Array<string | number>>(['gal'])

  return (
    <CheckboxGroup
      value={types}
      onValueChange={setTypes}
      options={options}
      aria-label="作品类型"
      renderOption={({ option }) => {
        const Icon = icons[option.value as keyof typeof icons]
        return (
          <Inline as="span" gap="xs" align="center">
            <Icon className="text-muted size-4" />
            {option.label}
          </Inline>
        )
      }}
    />
  )
}
tsx

在表单中

放进 FormField 后,标签通过 aria-labelledby 关联到整组,说明与错误信息由字段渲染;校验规则与提交交给 Form。

选一到两项

'use client'

import { useState } from 'react'
import * as v from 'valibot'
import { Button, CheckboxGroup, Form, FormField, Text } from '@hina-ui/react'

const options = [
  { label: '游戏', value: 'game' },
  { label: '小说', value: 'novel' },
  { label: '漫画', value: 'manga' },
  { label: '动画', value: 'anime' },
]

const schema = v.object({
  interests: v.pipe(
    v.array(v.string('请选择兴趣')),
    v.minLength(1, '至少选择一项'),
    v.maxLength(2, '最多选择两项'),
  ),
})

export default function Demo() {
  const [values, setValues] = useState({ interests: [] as Array<string | number> })
  const [saved, setSaved] = useState('')

  async function save(data: unknown) {
    await new Promise(resolve => setTimeout(resolve, 600))
    setSaved(JSON.stringify(data))
  }

  return (
    <Form values={values} rules={schema} className="w-80" onSubmit={save}>
      {({ submitting }) => (
        <>
          <FormField name="interests" label="兴趣" description="选一到两项" required>
            <CheckboxGroup
              value={values.interests}
              onValueChange={interests => setValues({ ...values, interests })}
              options={options}
            />
          </FormField>
          <Button type="submit" loading={submitting} className="self-start">
            保存
          </Button>
          {saved && (
            <Text tone="muted" size="sm">
              已保存:{saved}
            </Text>
          )}
        </>
      )}
    </Form>
  )
}
tsx

行为

  • 点选把值加入数组,再点移除,数组顺序与点选顺序一致。
  • 每个复选框都是独立的 Tab 停靠点,空格切换焦点所在项,方向键不移动焦点,与原生复选框组一致。

无障碍

  • 根元素是 role="group",通过 aria-label 或者 aria-labelledby 命名;每一项沿用 Checkbox 的语义。

API

Props

T extends SelectOption 从 options 推断,默认是 SelectOption。

属性
类型
默认值
说明
value
Array<string | number>
[]
已选值
options
T[]
—
选项,类型见 Select
orientation
'vertical' | 'horizontal'
'vertical'
排列方向
size
'sm' | 'md' | 'lg'
'md'
每个复选框的尺寸
controlPlacement
'start' | 'end'
'start'
控件相对于文案的位置
block
boolean
false
整组撑满,横向选项等分宽度
disabled
boolean
false
是否禁用整组
invalid
boolean
false
是否处于校验未通过状态
className
string
—
追加至根元素的类名

内容属性

属性
参数
说明
renderOption
{ option: T }
每一项的文字

回调

回调
参数
说明
onValueChange
value: Array<string | number>
已选值变化