Overlays
Segmented code
Copy the implementation or inspect every file that belongs to this component unit.
resources/views/components/ui/segmented.blade.php
@props([
'name' => null,
'options' => [],
'value' => null,
'size' => 'base',
])
@php
/*
| Segmented control — a compact single-select toggle between a few options.
| Pass `options` as [value => label]. Submits the active value via a hidden
| input when `name` is set.
|
| size: sm, base
|
| Usage:
| <x-ui.segmented name="view" :options="['list' => 'List', 'grid' => 'Grid']" value="list" />
*/
$first = $value ?? array_key_first($options);
$sizes = [
'sm' => 'text-xs px-2.5 py-1',
'base' => 'text-sm px-3.5 py-1.5',
];
$btnSize = $sizes[$size] ?? $sizes['base'];
$optionKeys = array_map(fn ($key) => (string) $key, array_keys($options));
$defaultIndex = array_search((string) $first, $optionKeys, true);
$defaultIndex = $defaultIndex === false ? 0 : $defaultIndex;
$groupLabel = $name ? Str::headline(str_replace(['-', '_'], ' ', str_replace(['[', ']'], '', (string) $name))) : null;
@endphp
<div
x-data="{
active: @js((string) $first),
keys: @js($optionKeys),
focusIndex: @js($defaultIndex),
activate(key) {
const index = this.keys.indexOf(key);
if (index === -1) {
return;
}
this.active = key;
this.focusIndex = index;
},
focusKey(key) {
this.$nextTick(() => this.$refs['option-' + key]?.focus());
},
move(direction) {
if (this.keys.length === 0) {
return;
}
const next = (this.focusIndex + direction + this.keys.length) % this.keys.length;
const key = this.keys[next];
this.activate(key);
this.focusKey(key);
},
}"
{{ $attributes->merge(['class' => 'inline-flex items-center gap-1 rounded-xl bg-gray-100 dark:bg-gray-800 p-1']) }}
role="radiogroup"
@if ($groupLabel) aria-label="{{ $groupLabel }}" @endif
>
@if ($name)
<input type="hidden" name="{{ $name }}" :value="active">
@endif
@foreach ($options as $key => $label)
<button
type="button"
x-ref="option-{{ $key }}"
role="radio"
:aria-checked="(active === @js((string) $key)).toString()"
:tabindex="active === @js((string) $key) ? '0' : '-1'"
@click="activate(@js((string) $key))"
@keydown.arrow-right.prevent="move(1)"
@keydown.arrow-down.prevent="move(1)"
@keydown.arrow-left.prevent="move(-1)"
@keydown.arrow-up.prevent="move(-1)"
@keydown.home.prevent="activate(keys[0]); focusKey(keys[0])"
@keydown.end.prevent="activate(keys[keys.length - 1]); focusKey(keys[keys.length - 1])"
:class="active === @js((string) $key) ? 'bg-white dark:bg-gray-900 text-gray-900 dark:text-white shadow-sm' : 'text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-200'"
class="rounded-lg font-medium transition cursor-pointer {{ $btnSize }}"
>{{ $label }}</button>
@endforeach
</div>