Component
Date Time Picker
Date Time Picker.
- Component
- Vanilla
- HTML
- MIT
Preview
Live component
No interactive preview for this component.
Install it with the CLI below, or read the implementation on this page.
Install
Add to your project
npx devsnips add Vanilla/Components/Forms/date-time-picker Vanilla/Components/Forms/date-time-picker <!--
Date & Time Picker Layout
- Native input enhancements
- Custom calendar interface
- Time selection with meridian
- Keyboard accessible
- Mobile-optimized display
-->
<div class="datetime-picker">
<div class="input-group">
<label for="date">Date</label>
<div class="picker-input">
<input
type="text"
id="date"
name="date"
readonly
placeholder="Select date"
aria-haspopup="true"
aria-expanded="false"
aria-controls="calendar"
>
<button type="button" class="calendar-toggle" aria-label="Show calendar">
<svg viewBox="0 0 24 24" width="24" height="24">
<path d="M19 3h-1V1h-2v2H8V1H6v2H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm0 16H5V8h14v11zM7 10h5v5H7v-5z" fill="currentColor"/>
</svg>
</button>
</div>
</div>
<div class="input-group">
<label for="time">Time</label>
<div class="picker-input">
<input
type="text"
id="time"
name="time"
readonly
placeholder="Select time"
aria-haspopup="true"
aria-expanded="false"
aria-controls="time-picker"
>
<button type="button" class="time-toggle" aria-label="Show time picker">
<svg viewBox="0 0 24 24" width="24" height="24">
<path d="M11.99 2C6.47 2 2 6.48 2 12s4.47 10 9.99 10C17.52 22 22 17.52 22 12S17.52 2 11.99 2zM12 20c-4.42 0-8-3.58-8-8s3.58-8 8-8 8 3.58 8 8-3.58 8-8 8zm.5-13H11v6l5.25 3.15.75-1.23-4.5-2.67V7z" fill="currentColor"/>
</svg>
</button>
</div>
</div>
<div id="calendar" class="calendar" role="dialog" aria-modal="true" hidden>
<div class="calendar-header">
<button type="button" class="prev-month" aria-label="Previous month">
<svg viewBox="0 0 24 24" width="24" height="24">
<path d="M15.41 7.41L14 6l-6 6 6 6 1.41-1.41L10.83 12z" fill="currentColor"/>
</svg>
</button>
<h2 class="calendar-title">September 2025</h2>
<button type="button" class="next-month" aria-label="Next month">
<svg viewBox="0 0 24 24" width="24" height="24">
<path d="M10 6L8.59 7.41 13.17 12l-4.58 4.59L10 18l6-6z" fill="currentColor"/>
</svg>
</button>
</div>
<div class="calendar-grid">
<div class="calendar-weekdays" role="row">
<span role="columnheader">Su</span>
<span role="columnheader">Mo</span>
<span role="columnheader">Tu</span>
<span role="columnheader">We</span>
<span role="columnheader">Th</span>
<span role="columnheader">Fr</span>
<span role="columnheader">Sa</span>
</div>
<div class="calendar-days" role="grid">
<!-- Days will be generated by JavaScript -->
</div>
</div>
</div>
<div id="time-picker" class="time-picker" role="dialog" aria-modal="true" hidden>
<div class="time-columns">
<div class="time-column" role="listbox" aria-label="Hours">
<!-- Hours: 1-12 -->
</div>
<div class="time-column" role="listbox" aria-label="Minutes">
<!-- Minutes: 00-59 -->
</div>
<div class="time-column" role="listbox" aria-label="Period">
<div role="option" tabindex="0">AM</div>
<div role="option" tabindex="-1">PM</div>
</div>
</div>
</div>
</div>
<style>
.datetime-picker {
max-width: 300px;
margin: 2rem auto;
padding: 0 1rem;
}
.input-group {
margin-bottom: 1rem;
}
label {
display: block;
margin-bottom: 0.25rem;
font-weight: 500;
color: var(--ds-foreground, #333);
}
.picker-input {
position: relative;
display: flex;
}
input {
width: 100%;
padding: 0.75rem;
padding-right: 3rem;
border: 2px solid var(--ds-border, #ddd);
border-radius: var(--ds-radius-sm, 4px);
font-size: 1rem;
cursor: pointer;
transition: all 0.3s;
}
input:focus {
outline: none;
border-color: var(--ds-accent, #4a90e2);
box-shadow: 0 0 0 2px rgba(74, 144, 226, 0.1);
}
.calendar-toggle,
.time-toggle {
position: absolute;
right: 0.75rem;
top: 50%;
transform: translateY(-50%);
padding: 0.25rem;
border: none;
background: none;
color: var(--ds-muted, #666);
cursor: pointer;
transition: color 0.3s;
}
.calendar-toggle:hover,
.time-toggle:hover {
color: var(--ds-foreground, #333);
}
/* Calendar styles */
.calendar {
position: absolute;
top: 100%;
left: 0;
right: 0;
margin-top: 0.5rem;
padding: 1rem;
background: white;
border-radius: var(--ds-radius-sm, 4px);
box-shadow: var(--ds-shadow-lg, 0 4px 12px rgba(0, 0, 0, 0.15));
z-index: 1000;
}
.calendar-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 1rem;
}
.calendar-title {
margin: 0;
font-size: 1rem;
font-weight: 500;
}
.prev-month,
.next-month {
padding: 0.25rem;
border: none;
background: none;
color: var(--ds-muted, #666);
cursor: pointer;
transition: color 0.3s;
}
.prev-month:hover,
.next-month:hover {
color: var(--ds-foreground, #333);
}
.calendar-weekdays {
display: grid;
grid-template-columns: repeat(7, 1fr);
gap: 0.5rem;
text-align: center;
font-size: 0.875rem;
font-weight: 500;
color: var(--ds-muted, #666);
margin-bottom: 0.5rem;
}
.calendar-days {
display: grid;
grid-template-columns: repeat(7, 1fr);
gap: 0.5rem;
}
.calendar-day {
aspect-ratio: 1;
display: flex;
align-items: center;
justify-content: center;
border: none;
background: none;
font-size: 0.875rem;
color: var(--ds-foreground, #333);
cursor: pointer;
border-radius: 50%;
transition: all 0.3s;
}
.calendar-day:hover:not(:disabled) {
background: var(--ds-surface-2, #f5f5f5);
}
.calendar-day.today {
font-weight: 700;
color: var(--ds-accent, #4a90e2);
}
.calendar-day.selected {
background: var(--ds-accent, #4a90e2);
color: white;
}
.calendar-day:disabled {
color: var(--ds-border-strong, #ccc);
cursor: not-allowed;
}
/* Time picker styles */
.time-picker {
position: absolute;
top: 100%;
left: 0;
right: 0;
margin-top: 0.5rem;
background: white;
border-radius: var(--ds-radius-sm, 4px);
box-shadow: var(--ds-shadow-lg, 0 4px 12px rgba(0, 0, 0, 0.15));
z-index: 1000;
}
.time-columns {
display: grid;
grid-template-columns: 2fr 2fr 1fr;
height: 200px;
overflow: hidden;
}
.time-column {
overflow-y: auto;
scrollbar-width: none;
-ms-overflow-style: none;
}
.time-column::-webkit-scrollbar {
display: none;
}
.time-option {
padding: 0.5rem;
text-align: center;
cursor: pointer;
transition: all 0.3s;
}
.time-option:hover {
background: var(--ds-surface-2, #f5f5f5);
}
.time-option.selected {
background: var(--ds-accent, #4a90e2);
color: white;
}
/* Focus styles */
.calendar-toggle:focus-visible,
.time-toggle:focus-visible,
.prev-month:focus-visible,
.next-month:focus-visible,
.calendar-day:focus-visible,
.time-option:focus-visible {
outline: 2px solid var(--ds-accent, #4a90e2);
outline-offset: 2px;
border-radius: var(--ds-radius-sm, 4px);
}
/* Mobile optimization */
@media (max-width: 600px) {
input {
font-size: 16px;
}
.calendar,
.time-picker {
position: fixed;
top: auto;
bottom: 0;
left: 0;
right: 0;
margin: 0;
border-radius: var(--ds-radius-lg, 12px) 12px 0 0;
max-height: 80vh;
overflow-y: auto;
}
}
/* devsnips-qa:quality-bar */
@media (prefers-reduced-motion: reduce){*{animation-duration:0.01ms !important;animation-iteration-count:1 !important;transition-duration:0.01ms !important;scroll-behavior:auto !important;}}
/* DevSnips design tokens (Swiss) — edit tokens.css to re-theme */
:root{
--ds-bg:#fff;--ds-surface:#fff;--ds-surface-2:#f7f7f7;--ds-surface-3:#ededed;--ds-foreground:#0a0a0a;--ds-muted:#525252;--ds-subtle:#a3a3a3;--ds-border:#e5e5e5;--ds-border-strong:#d4d4d4;--ds-accent:#2563eb;--ds-accent-hover:#1d4ed8;--ds-accent-fg:#fff;--ds-accent-soft:#eff6ff;--ds-success:#16a34a;--ds-success-soft:#f0fdf4;--ds-warning:#d97706;--ds-warning-soft:#fffbeb;--ds-danger:#dc2626;--ds-danger-soft:#fef2f2;--ds-info:#2563eb;--ds-info-soft:#eff6ff;--ds-font-sans:system-ui,-apple-system,'Segoe UI',Roboto,Inter,'Helvetica Neue',Arial,sans-serif;--ds-font-mono:ui-monospace,'SF Mono','JetBrains Mono',Menlo,Consolas,monospace;--ds-text-xs:.75rem;--ds-text-sm:.875rem;--ds-text-base:1rem;--ds-text-lg:1.125rem;--ds-text-xl:1.25rem;--ds-text-2xl:1.5rem;--ds-text-3xl:1.875rem;--ds-text-4xl:2.25rem;--ds-leading-tight:1.25;--ds-leading-normal:1.5;--ds-leading-relaxed:1.625;--ds-weight-normal:400;--ds-weight-medium:500;--ds-weight-semibold:600;--ds-weight-bold:700;--ds-space-0:0;--ds-space-px:1px;--ds-space-1:.25rem;--ds-space-2:.5rem;--ds-space-3:.75rem;--ds-space-4:1rem;--ds-space-5:1.25rem;--ds-space-6:1.5rem;--ds-space-8:2rem;--ds-space-10:2.5rem;--ds-space-12:3rem;--ds-space-16:4rem;--ds-radius-sm:.25rem;--ds-radius-md:.5rem;--ds-radius-lg:.75rem;--ds-radius-xl:1rem;--ds-radius-2xl:1.5rem;--ds-radius-full:9999px;--ds-shadow-sm:0 1px 2px 0 rgba(0,0,0,.05);--ds-shadow-md:0 4px 6px -1px rgba(0,0,0,.08),0 2px 4px -2px rgba(0,0,0,.05);--ds-shadow-lg:0 10px 15px -3px rgba(0,0,0,.1),0 4px 6px -4px rgba(0,0,0,.05);--ds-duration-fast:120ms;--ds-duration-normal:200ms;--ds-duration-slow:300ms;--ds-ease-out:cubic-bezier(.16,1,.3,1);--ds-ease-in-out:cubic-bezier(.4,0,.2,1);--ds-ring:0 0 0 2px #2563eb;--ds-container:1200px;--ds-gutter:1rem;
}
@media (prefers-color-scheme: dark){:root{--ds-bg:#0a0a0a;--ds-surface:#111;--ds-surface-2:#171717;--ds-surface-3:#1f1f1f;--ds-foreground:#fafafa;--ds-muted:#a3a3a3;--ds-subtle:#737373;--ds-border:#262626;--ds-border-strong:#404040;--ds-accent:#3b82f6;--ds-accent-hover:#60a5fa;--ds-accent-fg:#fff;--ds-accent-soft:#172554;--ds-success:#22c55e;--ds-success-soft:#052e16;--ds-warning:#f59e0b;--ds-warning-soft:#422006;--ds-danger:#ef4444;--ds-danger-soft:#450a0a;--ds-info:#3b82f6;--ds-info-soft:#172554;--ds-ring:0 0 0 2px #3b82f6;}}
</style>
<script>
class DateTimePicker {
constructor(container) {
this.container = container;
this.dateInput = container.querySelector('#date');
this.timeInput = container.querySelector('#time');
this.calendar = container.querySelector('#calendar');
this.timePicker = container.querySelector('#time-picker');
this.selectedDate = null;
this.selectedTime = null;
this.init();
}
init() {
this.bindDateEvents();
this.bindTimeEvents();
this.generateCalendar();
this.generateTimePicker();
// Close pickers when clicking outside
document.addEventListener('click', (e) => {
if (!this.container.contains(e.target)) {
this.calendar.hidden = true;
this.timePicker.hidden = true;
this.dateInput.setAttribute('aria-expanded', 'false');
this.timeInput.setAttribute('aria-expanded', 'false');
}
});
}
bindDateEvents() {
const calendarToggle = this.container.querySelector('.calendar-toggle');
const prevMonth = this.container.querySelector('.prev-month');
const nextMonth = this.container.querySelector('.next-month');
[this.dateInput, calendarToggle].forEach(el => {
el.addEventListener('click', () => {
this.timePicker.hidden = true;
this.calendar.hidden = !this.calendar.hidden;
this.dateInput.setAttribute('aria-expanded', !this.calendar.hidden);
});
});
prevMonth.addEventListener('click', () => this.changeMonth(-1));
nextMonth.addEventListener('click', () => this.changeMonth(1));
}
bindTimeEvents() {
const timeToggle = this.container.querySelector('.time-toggle');
[this.timeInput, timeToggle].forEach(el => {
el.addEventListener('click', () => {
this.calendar.hidden = true;
this.timePicker.hidden = !this.timePicker.hidden;
this.timeInput.setAttribute('aria-expanded', !this.timePicker.hidden);
});
});
}
generateCalendar(date = new Date()) {
const year = date.getFullYear();
const month = date.getMonth();
const firstDay = new Date(year, month, 1);
const lastDay = new Date(year, month + 1, 0);
const startPadding = firstDay.getDay();
const daysInMonth = lastDay.getDate();
this.container.querySelector('.calendar-title').textContent =
date.toLocaleString('default', { month: 'long', year: 'numeric' });
const daysContainer = this.container.querySelector('.calendar-days');
daysContainer.innerHTML = '';
// Add padding days
for (let i = 0; i < startPadding; i++) {
const padDay = document.createElement('button');
padDay.className = 'calendar-day';
padDay.disabled = true;
daysContainer.appendChild(padDay);
}
// Add month days
for (let i = 1; i <= daysInMonth; i++) {
const dayButton = document.createElement('button');
dayButton.className = 'calendar-day';
dayButton.textContent = i;
dayButton.type = 'button';
dayButton.setAttribute('role', 'gridcell');
const currentDate = new Date(year, month, i);
// Mark today
if (this.isToday(currentDate)) {
dayButton.classList.add('today');
}
// Mark selected date
if (this.isSelectedDate(currentDate)) {
dayButton.classList.add('selected');
}
dayButton.addEventListener('click', () => this.selectDate(currentDate));
daysContainer.appendChild(dayButton);
}
}
generateTimePicker() {
const hoursColumn = this.timePicker.querySelector('.time-column:nth-child(1)');
const minutesColumn = this.timePicker.querySelector('.time-column:nth-child(2)');
// Generate hours (1-12)
for (let i = 1; i <= 12; i++) {
const hour = document.createElement('div');
hour.className = 'time-option';
hour.textContent = i.toString().padStart(2, '0');
hour.setAttribute('role', 'option');
hour.setAttribute('tabindex', '0');
hour.addEventListener('click', () => this.selectHour(i));
hoursColumn.appendChild(hour);
}
// Generate minutes (00-59)
for (let i = 0; i < 60; i++) {
const minute = document.createElement('div');
minute.className = 'time-option';
minute.textContent = i.toString().padStart(2, '0');
minute.setAttribute('role', 'option');
minute.setAttribute('tabindex', '0');
minute.addEventListener('click', () => this.selectMinute(i));
minutesColumn.appendChild(minute);
}
// Handle AM/PM selection
const periodOptions = this.timePicker.querySelectorAll('.time-column:nth-child(3) [role="option"]');
periodOptions.forEach(option => {
option.addEventListener('click', () => {
periodOptions.forEach(opt => {
opt.classList.remove('selected');
opt.setAttribute('tabindex', '-1');
});
option.classList.add('selected');
option.setAttribute('tabindex', '0');
this.updateTimeValue();
});
});
}
changeMonth(delta) {
const currentDate = this.selectedDate || new Date();
const newDate = new Date(currentDate.getFullYear(), currentDate.getMonth() + delta, 1);
this.generateCalendar(newDate);
}
selectDate(date) {
this.selectedDate = date;
this.dateInput.value = date.toLocaleDateString();
this.calendar.hidden = true;
this.dateInput.setAttribute('aria-expanded', 'false');
// Update calendar display
const days = this.container.querySelectorAll('.calendar-day');
days.forEach(day => day.classList.remove('selected'));
days[date.getDate() + date.getDay() - 1].classList.add('selected');
}
selectHour(hour) {
const hours = this.timePicker.querySelectorAll('.time-column:nth-child(1) .time-option');
hours.forEach(h => h.classList.remove('selected'));
hours[hour - 1].classList.add('selected');
this.updateTimeValue();
}
selectMinute(minute) {
const minutes = this.timePicker.querySelectorAll('.time-column:nth-child(2) .time-option');
minutes.forEach(m => m.classList.remove('selected'));
minutes[minute].classList.add('selected');
this.updateTimeValue();
}
updateTimeValue() {
const selectedHour = this.timePicker.querySelector('.time-column:nth-child(1) .time-option.selected');
const selectedMinute = this.timePicker.querySelector('.time-column:nth-child(2) .time-option.selected');
const selectedPeriod = this.timePicker.querySelector('.time-column:nth-child(3) .time-option.selected');
if (selectedHour && selectedMinute && selectedPeriod) {
this.timeInput.value = `${selectedHour.textContent}:${selectedMinute.textContent} ${selectedPeriod.textContent}`;
}
}
isToday(date) {
const today = new Date();
return date.getDate() === today.getDate() &&
date.getMonth() === today.getMonth() &&
date.getFullYear() === today.getFullYear();
}
isSelectedDate(date) {
if (!this.selectedDate) return false;
return date.getDate() === this.selectedDate.getDate() &&
date.getMonth() === this.selectedDate.getMonth() &&
date.getFullYear() === this.selectedDate.getFullYear();
}
}
// Initialize date time picker
new DateTimePicker(document.querySelector('.datetime-picker'));
</script> # Snippet 18 Date Time Picker
Snippet 18 Date Time Picker with responsive design with animations and hover interactions.
**Technology:** vanilla
**Category:** components
**Subcategory:** forms
**Tags:** validation, date, animation, hover-effect, shadow, responsive, flexbox, grid
**Responsive:** Yes 555 lines UTF-8 · LF · Spaces: 2
Continue browsing