Basic Popover
A click-triggered panel anchored to its button, with Escape and outside-click dismiss.
6 frameworksIntermediate
A swatch grid in a dialog, wired as a radiogroup so the choice is single-select.
<div class="relative inline-block">
<button type="button" aria-haspopup="dialog" aria-expanded="true" aria-controls="pcolor-panel" class="inline-flex items-center gap-2 rounded-lg border border-gray-300 bg-white px-3.5 py-2 text-sm font-semibold text-gray-700 hover:bg-gray-50 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-blue-600 focus-visible:ring-offset-2 focus-visible:ring-offset-white dark:border-gray-700 dark:bg-gray-900 dark:text-gray-300 dark:hover:bg-gray-800 dark:focus-visible:ring-blue-400 dark:focus-visible:ring-offset-gray-900">
<span aria-hidden="true" class="h-3.5 w-3.5 rounded-full bg-blue-500"></span>
Label: Blue
</button>
<div id="pcolor-panel" role="dialog" aria-label="Label" class="absolute left-0 top-[calc(100%+0.5rem)] z-20 w-56 max-w-[calc(100vw-2rem)] rounded-xl border border-gray-200 bg-white p-3 shadow-lg dark:border-gray-700 dark:bg-gray-900">
<!-- radiogroup + radio: a colour choice is single-select, so a reader
announces "6 of 8, selected". Each swatch needs an aria-label. -->
<div role="radiogroup" aria-label="Label" class="grid grid-cols-4 gap-2">
<button type="button" role="radio" aria-checked="false" aria-label="Slate" class="flex h-9 w-full items-center justify-center rounded-md focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-blue-600 focus-visible:ring-offset-2 focus-visible:ring-offset-white dark:focus-visible:ring-blue-400 dark:focus-visible:ring-offset-gray-900"><span class="h-6 w-6 rounded-full bg-slate-500"></span></button>
<button type="button" role="radio" aria-checked="false" aria-label="Red" class="flex h-9 w-full items-center justify-center rounded-md focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-blue-600 focus-visible:ring-offset-2 focus-visible:ring-offset-white dark:focus-visible:ring-blue-400 dark:focus-visible:ring-offset-gray-900"><span class="h-6 w-6 rounded-full bg-red-500"></span></button>
<button type="button" role="radio" aria-checked="false" aria-label="Green" class="flex h-9 w-full items-center justify-center rounded-md focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-blue-600 focus-visible:ring-offset-2 focus-visible:ring-offset-white dark:focus-visible:ring-blue-400 dark:focus-visible:ring-offset-gray-900"><span class="h-6 w-6 rounded-full bg-green-500"></span></button>
<button type="button" role="radio" aria-checked="true" aria-label="Blue" class="flex h-9 w-full items-center justify-center rounded-md ring-2 ring-gray-900 ring-offset-2 ring-offset-white focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-blue-600 dark:ring-gray-100 dark:ring-offset-gray-900 dark:focus-visible:ring-blue-400"><span class="h-6 w-6 rounded-full bg-blue-500"></span></button>
</div>
</div>
</div>| Prop | Type | Default | Description |
|---|---|---|---|
labelrequired | string | - | Accessible label announced while loading. |
colorsrequired | ReadonlyArray<{ name: string; swatch: string }> | - | Colors |
value | string | - | The metric's current value, pre-formatted. |
onChange | (name: string) => void | - | On change |
Swatches are `role="radio"` inside a `role="radiogroup"` - a colour choice is single-select, and that pairing is what makes a reader announce "6 of 8, selected". Each swatch carries an `aria-label` because a colour block has no text of its own. Pass `colors` as objects with a literal Tailwind class (e.g. `bg-blue-500`) so the JIT compiler can see and generate it - a computed class name would be purged.