---
title: Swatch Group
description: A choice of colors or patterns as swatches, one value as radio buttons or several as checkboxes, with every option named in words.
visibility: guest
draft: false
---

# Swatch Group

`<x-wirekit::swatch-group>` lets the reader choose a color or a pattern: the color of a product variant on its page, or several colors in a filter. Each option is a native radio button, or a checkbox with `multiple`, drawn as a [swatch](/components/swatch) and named in words.

Because the options are native form controls, the group needs no script of its own. It posts with a form, binds with `wire:model`, and the keyboard works the way it does in every other radio group or set of checkboxes.

## Choosing One Value

Give each option a `value`, a `label` and a `color`. The label is the option's name, read to a reader who cannot see the color:

:::preview{title="The color of a variant"}
<x-wirekit::swatch-group name="color" label="Color" value="navy" :options="[
    ['value' => 'navy', 'label' => 'Navy', 'color' => '#1e3a8a'],
    ['value' => 'chalk', 'label' => 'Chalk', 'color' => '#f5f5f4'],
    ['value' => 'coral', 'label' => 'Coral', 'color' => 'oklch(62% 0.19 30)'],
    ['value' => 'forest', 'label' => 'Forest', 'color' => '#166534'],
]" />
:::

With Livewire, bind the choice with `wire:model`. The chosen name next to the label is yours to show, from the bound value, through the `labelAfter` slot:

```blade
<x-wirekit::swatch-group wire:model.live="color" label="Color" :options="$colors">
    <x-slot:label-after>
        <x-wirekit::text as="span" size="sm" intent="muted">{{ $colorName }}</x-wirekit::text>
    </x-slot:label-after>
</x-wirekit::swatch-group>
```

Without Livewire, `value` sets the chosen option and the group posts its `name` with the form.

## Choosing Several: A Filter

`multiple` makes each option a checkbox, as a filter takes several values at once. The group posts its name as a list, `colors[]`, and a `wire:model` binds an array:

:::preview{title="A color filter"}
<x-wirekit::swatch-group name="colors" label="Color" multiple size="sm" :value="['navy', 'coral']" :options="[
    ['value' => 'navy', 'label' => 'Navy', 'color' => '#1e3a8a'],
    ['value' => 'chalk', 'label' => 'Chalk', 'color' => '#f5f5f4'],
    ['value' => 'coral', 'label' => 'Coral', 'color' => 'oklch(62% 0.19 30)'],
    ['value' => 'forest', 'label' => 'Forest', 'color' => '#166534'],
    ['value' => 'sand', 'label' => 'Sand', 'color' => '#d6c7a1'],
]" />
:::

```blade
<x-wirekit::swatch-group wire:model.live="colors" multiple label="Color" size="sm" :options="$colors" />
```

## Names Under the Swatches

Each name is in the page for assistive technology. `labels="below"` shows it under its swatch as well, which helps where the colors are close to each other:

:::preview{title="Names under the swatches"}
<x-wirekit::swatch-group name="finish" label="Finish" labels="below" value="oak" :options="[
    ['value' => 'oak', 'label' => 'Oak', 'image' => '/placeholder/96x96?bg=c8a165'],
    ['value' => 'walnut', 'label' => 'Walnut', 'image' => '/placeholder/96x96?bg=7c4a2d'],
    ['value' => 'ash', 'label' => 'Ash', 'image' => '/placeholder/96x96?bg=e5dccb'],
]" />
:::

An option takes an `image` in place of a `color` for a pattern, as a [swatch](/components/swatch#a-pattern-instead-of-a-color) does.

## Unavailable and Disabled Options

`'available' => false` draws a line across the option's swatch and reads "unavailable" with its name. The option stays a choice, because a sold-out variant can still be looked at. `'disabled' => true` takes an option out of the choice, and `disabled` on the group takes all of them:

:::preview{title="An unavailable option and a disabled one"}
<x-wirekit::swatch-group name="size-color" label="Color" value="navy" :options="[
    ['value' => 'navy', 'label' => 'Navy', 'color' => '#1e3a8a'],
    ['value' => 'red', 'label' => 'Red', 'color' => '#b91c1c', 'available' => false],
    ['value' => 'chalk', 'label' => 'Chalk', 'color' => '#f5f5f4', 'disabled' => true],
]" />
:::

## Hints and Errors

The group takes `hint`, `error` and `help` as every field does. The message stands under the swatches and describes the choice, and an error is announced politely:

:::preview{title="A hint under the swatches"}
<x-wirekit::swatch-group name="color-hint" label="Color" hint="The color is printed on the front only." :options="[
    ['value' => 'navy', 'label' => 'Navy', 'color' => '#1e3a8a'],
    ['value' => 'coral', 'label' => 'Coral', 'color' => 'oklch(62% 0.19 30)'],
]" />
:::

With `required` the label carries the mark, and one value has to be chosen before the form submits. After a failed validation the group shows the message the error bag holds for its `name`, as every field does:

:::preview{title="A color that has to be chosen"}
<x-wirekit::swatch-group name="color-required" label="Color" required error="Choose a color." :options="[
    ['value' => 'navy', 'label' => 'Navy', 'color' => '#1e3a8a'],
    ['value' => 'coral', 'label' => 'Coral', 'color' => 'oklch(62% 0.19 30)'],
]" />
:::

A group of checkboxes, with `multiple`, has no native way to require one of them: `required` marks its label, and the rule that at least one is chosen belongs to your validation.

## What an Option Checks

An option without a `value` has nothing to send, and one without a `label` would leave its color to say what it is. WireKit reports both in development; an option without a label takes its value as its name. A `color` in a form other than a hex color, `rgb()`, `hsl()` or `oklch()`, and an `image` with a scheme other than `http` or `https`, are rejected as on a [swatch](/components/swatch#the-colors-it-accepts) and draw nothing.

## Props

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `label` | `string\|null` | `null` | The group's label, which names it |
| `options` | `array` | `[]` | The options, each an array with `value`, `label` and `color` or `image`, and optionally `available` (default `true`) and `disabled` (default `false`) |
| `value` | `mixed` | `null` | The chosen value, or with `multiple` a list of them, for a form without `wire:model` |
| `multiple` | `bool` | `false` | Choose several values, as checkboxes. Without it one value, as radio buttons |
| `labels` | `string` | `hidden` | `hidden` keeps each name for assistive technology, `below` shows it under its swatch |
| `size` | `string` | `md` | The swatches' size: `sm` (24px), `md` (32px) or `lg` (40px) |
| `shape` | `string` | `circle` | The swatches' shape: `circle` or `square` |
| `disabled` | `bool` | `false` | Disable every option |
| `required` | `bool` | `false` | One value has to be chosen: the label carries the mark, the radio buttons the native constraint, and the group says it is required. With `multiple` it marks the label only |
| `hint` | `string\|null` | `null` | Text under the swatches |
| `error` | `string\|null` | `null` | An error message under the swatches, in place of the hint |
| `announceError` | `bool\|null` | `null` | Announce the error politely. Falls back to the surrounding form, then to `config('wirekit.a11y.announce_error')` |
| `help` | `string\|null` | `null` | An explanation in a tooltip from a question mark beside the label, read as the group's description |
| `scope` | `string\|null` | `null` | Scoped personalization key |

`name`, `wire:model` and `x-model` reach every option. The defaults for `size`, `shape` and `labels` come from `config('wirekit.components.swatch-group')`, see [Customization](#customization).

## Slots

| Slot | Description |
| --- | --- |
| `labelAfter` | A mark after the label and its help, in the same row: the chosen name, or a badge |

## Accessibility

- **The group is named by its label**, as a `radiogroup` for one value or a `group` for several, and every option is a native radio button or checkbox.
- **No option rests on its color.** Each has its name in words, in the page whether it is shown or not; an unavailable one has a line across it and "unavailable" in its name; the chosen one has an outline in the accent color and is checked.
- **The hint or the error describes the choice.** For one value it stands on the radiogroup, as the description a screen reader gives when the focus enters the group, and on no single radio, where it would be read once per option. For several values every checkbox carries it, as each is a control of its own. An error is a polite live region and marks the choice invalid.
- **The focus ring is the one every field draws**, and on a touch surface each option has a 44px hit area. It follows the arrow keys in every browser. Safari, and every browser on iOS, does not count the focus an arrow key moves as keyboard focus, so WireKit's scripts note when an arrow key brings the focus to an option, and the ring follows it there too. Without `@wirekitScripts` on the page, Safari draws the ring after `Tab` and not after an arrow key.
- **A forced-colors theme keeps the colors of the swatches**, which are content, and draws their edges and the outline of the chosen one in its own colors.

## Keyboard Interaction

| Key | Action |
| --- | --- |
| `Tab` | Into the group: to the chosen option, or the first one, as one stop for radio buttons; to each option in turn for checkboxes |
| `←` `→` `↑` `↓` | Choose the previous or next option, with radio buttons |
| `Space` | Choose the focused option, or check and uncheck it with checkboxes |

## Customization

Set the defaults of every swatch group in `config/wirekit.php`:

```php
'components' => [
    'swatch-group' => ['size' => 'lg', 'shape' => 'square', 'labels' => 'below'],
],
```
