Date Picker
<div role="dialog"> + calendar grid
A dropdown calendar for date selection. Supports keyboard navigation, today/clear shortcuts, and integrates cleanly into forms.
Overview
Preview
Trigger button
html
<button @click="open = !open"
class="flex w-48 items-center gap-2 rounded-md border border-zinc-700 bg-zinc-900
px-3 py-2.5 text-sm transition-colors focus:outline-none focus-visible:ring-2
focus-visible:ring-[#D40C37]/40"
:class="open ? 'border-[#D40C37]' : 'hover:border-zinc-600'"
>
<!-- Calendar icon -->
<span :class="selected ? 'text-zinc-100' : 'text-zinc-600'">
{{ selected ? formatDate(selected) : 'Pick a date' }}
</span>
</button>Calendar panel
html
<div class="absolute left-0 top-full z-20 mt-1.5 w-72 rounded-xl border border-zinc-700/60
bg-zinc-900 p-3 shadow-2xl shadow-black/40">
<!-- Month navigation -->
<div class="mb-3 flex items-center justify-between">
<button @click="prevMonth" class="flex h-7 w-7 items-center justify-center rounded-md hover:bg-zinc-800 text-zinc-400">‹</button>
<p class="text-sm font-semibold text-zinc-200">{{ monthName }} {{ viewYear }}</p>
<button @click="nextMonth" class="flex h-7 w-7 items-center justify-center rounded-md hover:bg-zinc-800 text-zinc-400">›</button>
</div>
<!-- Weekday headers -->
<div class="grid grid-cols-7 mb-1">
<span v-for="d in ['Su','Mo','Tu','We','Th','Fr','Sa']" :key="d"
class="flex h-8 items-center justify-center text-[10px] font-semibold text-zinc-600">
{{ d }}
</span>
</div>
<!-- Day grid -->
<div class="grid grid-cols-7">
<div v-for="blank in leadingBlanks" :key="'b'+blank"></div>
<button
v-for="day in daysInMonth" :key="day"
@click="selectDate(day)"
class="flex h-8 w-8 items-center justify-center rounded-full text-sm transition-all mx-auto"
:class="
isSelected(day) ? 'bg-[#D40C37] text-white font-semibold' :
isToday(day) ? 'border border-[#D40C37]/40 text-[#D40C37] hover:bg-[#D40C37]/10' :
'text-zinc-300 hover:bg-zinc-800'
"
>{{ day }}</button>
</div>
<!-- Footer -->
<div class="mt-3 flex justify-between border-t border-zinc-800 pt-3">
<button @click="selected = null; open = false" class="text-xs text-zinc-600 hover:text-zinc-400">Clear</button>
<button @click="goToToday" class="text-xs font-medium text-[#D40C37] hover:underline">Today</button>
</div>
</div>Full Vue implementation
vue
<script setup>
import { ref, computed } from 'vue'
const open = ref(false)
const selected = ref(null)
const today = new Date()
const viewMonth = ref(today.getMonth())
const viewYear = ref(today.getFullYear())
const months = ['January','February','March','April','May','June',
'July','August','September','October','November','December']
const monthName = computed(() => months[viewMonth.value])
const daysInMonth = computed(() => new Date(viewYear.value, viewMonth.value + 1, 0).getDate())
const leadingBlanks = computed(() => new Date(viewYear.value, viewMonth.value, 1).getDay())
function prevMonth() {
if (viewMonth.value === 0) { viewMonth.value = 11; viewYear.value-- }
else viewMonth.value--
}
function nextMonth() {
if (viewMonth.value === 11) { viewMonth.value = 0; viewYear.value++ }
else viewMonth.value++
}
function selectDate(day) {
selected.value = new Date(viewYear.value, viewMonth.value, day)
open.value = false
}
function isSelected(day) {
if (!selected.value) return false
return selected.value.getDate() === day &&
selected.value.getMonth() === viewMonth.value &&
selected.value.getFullYear() === viewYear.value
}
function isToday(day) {
return today.getDate() === day &&
today.getMonth() === viewMonth.value &&
today.getFullYear() === viewYear.value
}
function goToToday() {
viewMonth.value = today.getMonth()
viewYear.value = today.getFullYear()
selectDate(today.getDate())
}
function formatDate(d) {
return d.toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' })
}
</script>In a form field
html
<div class="space-y-1.5">
<label class="block text-xs font-medium text-zinc-400">Start date</label>
<!-- date picker trigger here -->
<p v-if="error" class="text-xs text-red-400" role="alert">{{ error }}</p>
</div>Accessibility notes
- The calendar panel acts as a dialog — give it
role="dialog"andaria-label="Choose a date" - Each day button should have
aria-label="March 8, 2026"(full date) not just the number - Selected day:
aria-pressed="true"oraria-selected="true" - Today: add
aria-current="date" - Keyboard:
←→navigate days,↑↓navigate weeks,PgUp/PgDnnavigate months,Esccloses - For production use consider a dedicated library like
v-calendaror@vuepic/vue-datepickerwhich implement the full ARIA date picker pattern