Nobody uses <input type="date"> in a real app. It shows the browser's format instead of the page's, it cannot be styled and it has no ranges.
Wirepicker replaces it with one Blade component: single dates and ranges, shortcuts, keyboard, every language, no runtime dependencies. It works with Livewire, with Alpine or with a plain form.
Install
It is a regular Laravel package. Require it with Composer and publish the assets:
composer require edulazaro/wirepicker
php artisan vendor:publish --tag=wirepicker-assets
Then load the CSS and the JS in your layout:
<link rel="stylesheet" href="{{ asset('vendor/wirepicker/css/wirepicker.css') }}">
<script src="{{ asset('vendor/wirepicker/js/wirepicker.js') }}" defer></script>
Or with Vite, straight from vendor:
@import "../../vendor/edulazaro/wirepicker/resources/css/wirepicker.css";
import '../../vendor/edulazaro/wirepicker/resources/js/wirepicker.js';
One date
Put the component where the input would go and bind it like any field:
<x-wirepicker wire:model="startsOn" />
The property gets a Y-m-d string, or '' when empty.
A range
Add range and bind it to an array with a start and an end:
<x-wirepicker range wire:model.live="period" />
public array $period = ['start' => '', 'end' => ''];
Click both ends in any order. With shortcuts:
<x-wirepicker range wire:model.live="period" :presets="['this-month', 'next-month', 'this-year']" />
Available: today, this-week, next-7-days, this-month, next-month, last-month, this-year.
Without Livewire
Outside Livewire, give it a name and an optional value, and it posts like any other field:
<x-wirepicker name="starts_on" value="2026-10-08" />
<x-wirepicker range name="period" :value="['start' => '2026-10-01', 'end' => '2026-10-31']" />
A range posts period[start] and period[end]. For Alpine, listen to wirepicker:change:
<div x-data="{ date: null }" x-on:wirepicker:change="date = $event.detail.value">
<x-wirepicker />
</div>
Options
Everything is a prop on the component. These are all of them:
| Prop | Default | |
|---|---|---|
range |
false |
Two dates instead of one |
presets |
[] |
Shortcuts, range only |
placeholder |
translated | Text while empty |
min, max
|
none | First and last day that can be picked |
clearable |
true |
A button to empty it |
name, value
|
none | For a plain form |
size |
md |
sm for toolbars |
locale |
app locale | Language, format and first day of the week |
week-start |
from the locale |
0 Sunday to 6 Saturday |
id |
none | For <label for>
|
Languages
Months, weekdays, date format and first day of the week come from Intl: es shows 08/10/2026 starting on Monday, en-US shows 10/08/2026 starting on Sunday. No locale files. The few words of its own ship in English and Spanish (vendor:publish --tag=wirepicker-lang to add more).
Keyboard
Arrows move a day or a week, Page Up and Page Down a month, Enter picks, Escape closes.
How it works with Livewire
The point of the package is that it keeps its value through Livewire's requests, which is where most date pickers break. Three decisions make that work:
-
wire:modelgoes on hidden fields inside the component. The JS writes them and firesinputandchange, so there is no glue to write and nothing to initialise: pickers added later just work. - Those fields are
<input type="text" hidden>, nottype="hidden". On a hidden input the value is the attribute, and Livewire's morph removes it after every request. - The calendar is drawn on
<body>, so tables, cards and modals cannot clip it, and it opens above wiremodal modals.
Themes
Same attribute as the rest of the wire* family:
<html data-wire-theme="studio">
11 themes, dark mode with data-wire-theme-mode="dark" or .dark. For your own look, override the --wire-* variables (whole family) or --wp-* (just the picker):
:root {
--wp-trigger-radius: 0.5rem;
--wp-day-size: 34px;
}
Links
👉 Source: https://github.com/edulazaro/wirepicker
👉 Packagist: https://packagist.org/packages/edulazaro/wirepicker
👉 Documentation: https://edulazaro.com/portfolio/wirepicker
Top comments (2)
Returning a plain Y-m-d string is the right call. A Date object ends up a day off for someone the moment it goes through UTC.
And week start from the locale, nice. A Sunday "this-week" looks broken to most of Europe.
Thanks! Both were on purpose, as dates stay plain strings so no time zone can move them, and the week start comes from Intl, so nobody has to configure it.