Forms
toggle
Two-state button built on a native checkbox, submits with the form.
<div class="flex items-center gap-2">
<x-ui.toggle name="bold" :pressed="true" aria-label="Bold">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none"
stroke="currentColor" stroke-width="2" stroke-linecap="round"
stroke-linejoin="round" aria-hidden="true">
<path d="M6 12h9a4 4 0 0 1 0 8H7a1 1 0 0 1-1-1V5a1 1 0 0 1 1-1h7a4 4 0 0 1 0 8" />
</svg>
</x-ui.toggle>
<x-ui.toggle name="italic" aria-label="Italic">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none"
stroke="currentColor" stroke-width="2" stroke-linecap="round"
stroke-linejoin="round" aria-hidden="true">
<line x1="19" x2="10" y1="4" y2="4" />
<line x1="14" x2="5" y1="20" y2="20" />
<line x1="15" x2="9" y1="4" y2="20" />
</svg>
</x-ui.toggle>
<x-ui.toggle variant="outline" name="underline">Underline</x-ui.toggle>
<x-ui.toggle size="sm" name="small">Small</x-ui.toggle>
<x-ui.toggle size="lg" name="large">Large</x-ui.toggle>
</div>
Installation
php artisan ui:add toggle
1. Install dependencies
- composer:
gehrisandro/tailwind-merge-laravel
2.
Copy the source into
resources/views/components/ui/
resources/views/components/ui/toggle.blade.php
@props([
'variant' => 'default',
'size' => 'md',
'name' => null,
'value' => '1',
'pressed' => false,
])
@php
// A native checkbox carries the pressed state: it submits with the form,
// answers the spacebar and exposes checkedness to assistive tech, so this
// component needs no script and no aria-pressed bookkeeping.
$base =
"inline-flex cursor-pointer items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors has-[:checked]:bg-accent has-[:checked]:text-accent-foreground has-[:focus-visible]:border-ring has-[:focus-visible]:ring-ring/50 has-[:focus-visible]:ring-[3px] has-[:disabled]:pointer-events-none has-[:disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 [&_svg]:shrink-0";
// Scoped with :not(:has(:checked)) rather than a plain hover: so the
// pressed background cannot be repainted on hover. Both rules would
// otherwise have equal specificity and the winner would come down to the
// order Tailwind happens to emit them in.
$hover =
'[&:not(:has(:checked))]:hover:bg-muted [&:not(:has(:checked))]:hover:text-muted-foreground';
$variants = match ($variant) {
'outline'
=> 'border border-input bg-transparent shadow-xs hover:bg-accent hover:text-accent-foreground',
default => 'bg-transparent',
};
$sizes = match ($size) {
'sm' => 'h-8 min-w-8 px-1.5',
'lg' => 'h-10 min-w-10 px-2.5',
default => 'h-9 min-w-9 px-2',
};
$classes = \TailwindMerge\Laravel\Facades\TailwindMerge::merge(
$base,
$hover,
$variants,
$sizes,
$attributes->get('class'),
);
@endphp
<label {{ $attributes->except('class')->merge(['class' => $classes]) }}>
<input type="checkbox" class="peer sr-only"
@if ($name) name="{{ $name }}" @endif value="{{ $value }}"
@checked($pressed) />
{{ $slot }}
</label>
Usage
<x-ui.toggle name="bold" :pressed="true" aria-label="Bold">B</x-ui.toggle>
<x-ui.toggle variant="outline" name="italic">Italic</x-ui.toggle>
{{-- size: sm, md (default) or lg. value is submitted when pressed. --}}
<x-ui.toggle name="mode" value="grid" size="sm">Grid</x-ui.toggle>
<x-ui.toggle name="mode" value="list" size="lg">List</x-ui.toggle>
Pure CSS over a native checkbox, no JavaScript. Variants: default, outline. Sizes: sm, md, lg. Pass `name` to submit its state and `pressed` to start on.
Props
Every prop with its default. Pass any of them as attributes,
using : to bind a PHP value.
@props([
'variant' => 'default',
'size' => 'md',
'name' => null,
'value' => '1',
'pressed' => false,
])