# Checkbox List

## Usage

`CheckboxList` is a field around a list of `CheckboxListItem` rows. With `v-model`, the value is the array of checked item values.

```vue
<script setup lang="ts">
import { CheckboxList, CheckboxListItem } from '@astryx-vue/core/CheckboxList'
import { shallowRef } from 'vue'

const selected = shallowRef(['email'])
</script>

<template>
  <CheckboxList v-model="selected" label="Notifications">
    <CheckboxListItem label="Email" value="email" />
    <CheckboxListItem label="SMS" value="sms" />
  </CheckboxList>
</template>
```

Omit the bound value when a row owns its own checked state, such as a select-all row. Compact density uses the small checkbox. A checked row uses the accent-muted background. Read-only and disabled rows drop that background.

A read-only group keeps the checks and does not dim, but the checks cannot change. Disabled wins when both are set. When the group is disabled and a reason is set, every row stays focusable, and hovering or keyboard focus shows the reason. A row that is loading, or whose change is still running, shows a spinner inside the checkbox and cannot be toggled.

### Anatomy

- **Group**: Container for the labeled checkbox group.
- **Group label**: Text identifying what the checkbox options represent.
- **Description**: Helper text below the group label.
- **Options list**: List containing the available checkbox options.
- **Option row**: Selectable row containing one option.
- **Checkbox**: Selection indicator for an option.
- **Option label**: Primary content identifying an option.
- **Option description**: Secondary text below an option label.
- **End content**: Caller-provided content at the end of an option row.
- **Spinner**: Loading indicator shown inside the pending checkbox.
- **Status message**: Error, warning, or success message below the group.

## Best practices

| Guideline | Practice |
| --- | --- |
| Do | Use a select-all row without a value when the list itself is not the collection. |
| Don't | Use a checkbox list for mutually exclusive choices. Use RadioList. |

## Examples

### Select all

Select all is indeterminate until every document is included.

```vue
<script setup lang="ts">
import { CheckboxList, CheckboxListItem } from '@astryx-vue/core/CheckboxList'
import { computed, shallowRef } from 'vue'

const documents = [
  { id: 'transactions', label: 'Transaction history' },
  { id: 'statements', label: 'Account statements' },
  { id: 'tax', label: 'Tax documents' },
  { id: 'invoices', label: 'Invoices' },
]
const selected = shallowRef(['transactions'])
const allIds = documents.map(document => document.id)
const allChecked = computed(() => allIds.every(id => selected.value.includes(id)))
const selectAllState = computed(() => allChecked.value ? true : selected.value.length === 0 ? false : 'indeterminate' as const)

function toggleAll(checked: boolean): void {
  selected.value = checked ? [...allIds] : []
}

function toggle(id: string, checked: boolean): void {
  selected.value = checked ? [...selected.value, id] : selected.value.filter(value => value !== id)
}
</script>

<template>
  <CheckboxList label="Include in export" has-dividers>
    <CheckboxListItem label="Select all" :is-checked="selectAllState" @check="toggleAll" />
    <CheckboxListItem
      v-for="document in documents"
      :key="document.id"
      :label="document.label"
      :is-checked="selected.includes(document.id)"
      @check="checked => toggle(document.id, checked)"
    />
  </CheckboxList>
</template>
```
### With end content

Each row can end with a price badge.

```vue
<script setup lang="ts">
import { Badge } from '@astryx-vue/core/Badge'
import { CheckboxList, CheckboxListItem } from '@astryx-vue/core/CheckboxList'
import { shallowRef } from 'vue'

const value = shallowRef(['free'])
</script>

<template>
  <CheckboxList v-model="value" label="Add-on packages" has-dividers>
    <CheckboxListItem label="Free tier" value="free" description="Basic features included">
      <template #end>
        <Badge variant="success">
          $0/mo
        </Badge>
      </template>
    </CheckboxListItem>
    <CheckboxListItem label="Pro tier" value="pro" description="Advanced analytics and integrations">
      <template #end>
        <Badge variant="info">
          $9/mo
        </Badge>
      </template>
    </CheckboxListItem>
    <CheckboxListItem label="Enterprise" value="enterprise" description="Custom solutions and dedicated support">
      <template #end>
        <Badge variant="purple">
          Custom
        </Badge>
      </template>
    </CheckboxListItem>
  </CheckboxList>
</template>
```
### Read-only, reason, and loading

Read-only keeps the checks and does not dim. A disabled group with a reason stays focusable. A loading row cannot be toggled.

```vue
<script setup lang="ts">
import { CheckboxList, CheckboxListItem } from '@astryx-vue/core/CheckboxList'
import { VStack } from '@astryx-vue/core/Stack'
import { ref } from 'vue'

const saved = ref(['csv'])
const locked = ref<string[]>([])
const pending = ref<string[]>([])
</script>

<template>
  <div style="width: 320px; max-width: 100%">
    <VStack :gap="6">
      <CheckboxList v-model="saved" label="Saved export" is-read-only>
        <CheckboxListItem label="CSV" value="csv" />
        <CheckboxListItem label="JSON" value="json" />
      </CheckboxList>
      <CheckboxList v-model="locked" label="Managed export" is-disabled disabled-reason="Ask an administrator">
        <CheckboxListItem label="CSV" value="csv" />
        <CheckboxListItem label="JSON" value="json" />
      </CheckboxList>
      <CheckboxList v-model="pending" label="Live export">
        <CheckboxListItem label="CSV" value="csv" is-loading />
        <CheckboxListItem label="JSON" value="json" />
      </CheckboxList>
    </VStack>
  </div>
</template>
```

## CheckboxList Props

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `v-model` | `string[]` | — | Checked values. Omit it for standalone rows. |
| `label` | `string` | — | Required accessible name of the group. |
| `description` | `string` | — | Help text under the label. |
| `density` | `'compact' \| 'balanced' \| 'spacious'` | `'balanced'` | Row padding. Compact uses a small checkbox. |
| `hasDividers` | `boolean` | `false` | Draws a divider between rows. |
| `status` | `FieldStatusInput` | — | Error, warning, or success. |
| `statusVariant` | `'attached' \| 'detached' \| 'tooltip'` | `'detached'` | Where the status message sits. |
| `isDisabled` | `boolean` | `false` | Disables every row. |
| `disabledMessage` | `string` | — | Why the whole group is disabled. With `isDisabled`, the message shows in a tooltip on hover or keyboard focus and every row stays focusable with `aria-disabled`. The older `disabledReason` name works too. |
| `isReadOnly` | `boolean` | `false` | Read-only group. Checks stay and do not dim, but cannot change. Disabled wins when both are set. |
| `changeAction` | `(values) => void \| Promise<void>` | — | Runs after a check changes. The row shows a spinner and cannot be toggled until it finishes. |
| `containerClass` | `string` | — | Class on the field shell. It has no effect inside a horizontal-labels form. |
| `containerStyle` | `StyleValue` | — | Inline style on the field shell. Same placement as `containerClass`. |
| `controlClass` | `string` | — | Class on the option group (`role="group"`). It does not land on each checkbox. |
| `controlStyle` | `StyleValue` | — | Inline style on the option group. |

## CheckboxListItem Props

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `label` | `string` | — | Required row label. It names the checkbox. Use the `label` slot for rich content such as a link; the checkbox name comes from the visible text. |
| `ariaLabel` | `string` | — | Overrides the checkbox name. Use it when a rich label has no readable text. |
| `value` | `string` | — | Required in collection mode. Omit it for a standalone row. |
| `description` | `string` | — | Secondary text, also the checkbox description. Rich content goes in the `description` slot and stays linked with `aria-describedby`. |
| `isChecked` | `boolean \| 'indeterminate'` | — | Standalone checked state. Ignored in collection mode. |
| `isDisabled` | `boolean` | `false` | Disables this row. |
| `isLoading` | `boolean` | `false` | Loading for this row. The checkbox shows a spinner and cannot be toggled. |

## CheckboxListItem events and slots

`check` emits the next boolean for a standalone row. The `label` and `description` slots accept rich content. The `end` slot renders after the label.
