Datepicker
Material Design 3 datepicker component with single date, date range, and inline calendar support
Datepicker
The Datepicker component allows users to select dates through an intuitive calendar interface. @duskmoon-dev/core provides a complete Material Design 3-inspired datepicker with support for single dates, date ranges, and inline calendars. Uses the native HTML Popover API for dropdown behavior.
Basic Usage
Basic Datepicker
<div class="datepicker">
<div class="datepicker-input-container">
<input type="date" id="date-basic" name="date-basic" class="datepicker-input">
<button class="datepicker-trigger" popovertarget="datepicker-1">
<span class="datepicker-icon">📅</span>
</button>
</div>
<div class="datepicker-dropdown" id="datepicker-1" popover="auto">
<div class="datepicker-header">
<button class="datepicker-nav">‹</button>
<div class="datepicker-title">January 2024</div>
<button class="datepicker-nav">›</button>
</div>
<div class="datepicker-calendar">
<!-- Weekday headers -->
<div class="datepicker-weekday">Su</div>
<div class="datepicker-weekday">Mo</div>
<div class="datepicker-weekday">Tu</div>
<div class="datepicker-weekday">We</div>
<div class="datepicker-weekday">Th</div>
<div class="datepicker-weekday">Fr</div>
<div class="datepicker-weekday">Sa</div>
<!-- Day cells -->
<button class="datepicker-day datepicker-day-other-month">31</button>
<button class="datepicker-day">1</button>
<button class="datepicker-day">2</button>
<button class="datepicker-day datepicker-day-today">3</button>
<button class="datepicker-day">4</button>
<button class="datepicker-day">5</button>
<button class="datepicker-day">6</button>
<button class="datepicker-day">7</button>
<button class="datepicker-day">8</button>
<button class="datepicker-day">9</button>
<button class="datepicker-day">10</button>
<button class="datepicker-day">11</button>
<button class="datepicker-day">12</button>
<button class="datepicker-day">13</button>
<button class="datepicker-day">14</button>
<button class="datepicker-day datepicker-day-selected">15</button>
<button class="datepicker-day">16</button>
<button class="datepicker-day">17</button>
<button class="datepicker-day">18</button>
<button class="datepicker-day">19</button>
<button class="datepicker-day">20</button>
</div>
</div>
</div>Variants
Single Date Selection
The default behavior allows users to select a single date:
Single Date Selection
<div class="datepicker">
<div class="datepicker-input-container">
<input type="date" id="date-single" name="date-single" class="datepicker-input">
<button class="datepicker-trigger" popovertarget="datepicker-single">
<span class="datepicker-icon">📅</span>
</button>
</div>
<div class="datepicker-dropdown" id="datepicker-single" popover="auto">
<div class="datepicker-header">
<button class="datepicker-nav">‹</button>
<div class="datepicker-title">January 2024</div>
<button class="datepicker-nav">›</button>
</div>
<div class="datepicker-calendar">
<div class="datepicker-weekday">Su</div>
<div class="datepicker-weekday">Mo</div>
<div class="datepicker-weekday">Tu</div>
<div class="datepicker-weekday">We</div>
<div class="datepicker-weekday">Th</div>
<div class="datepicker-weekday">Fr</div>
<div class="datepicker-weekday">Sa</div>
<button class="datepicker-day">1</button>
<button class="datepicker-day">2</button>
<button class="datepicker-day">3</button>
<button class="datepicker-day">4</button>
<button class="datepicker-day">5</button>
<button class="datepicker-day">6</button>
<button class="datepicker-day">7</button>
<button class="datepicker-day">8</button>
<button class="datepicker-day">9</button>
<button class="datepicker-day">10</button>
<button class="datepicker-day">11</button>
<button class="datepicker-day">12</button>
<button class="datepicker-day">13</button>
<button class="datepicker-day">14</button>
<button class="datepicker-day datepicker-day-selected">15</button>
</div>
</div>
</div>Date Range Selection
For selecting a date range, use the range-specific classes:
Date Range Selection
<div class="datepicker">
<div class="datepicker-input-container">
<input type="date" id="date-range" name="date-range" class="datepicker-input">
<button class="datepicker-trigger" popovertarget="datepicker-range">
<span class="datepicker-icon">📅</span>
</button>
</div>
<div class="datepicker-dropdown" id="datepicker-range" popover="auto">
<div class="datepicker-header">
<button class="datepicker-nav">‹</button>
<div class="datepicker-title">January 2024</div>
<button class="datepicker-nav">›</button>
</div>
<div class="datepicker-calendar">
<div class="datepicker-weekday">Su</div>
<div class="datepicker-weekday">Mo</div>
<div class="datepicker-weekday">Tu</div>
<div class="datepicker-weekday">We</div>
<div class="datepicker-weekday">Th</div>
<div class="datepicker-weekday">Fr</div>
<div class="datepicker-weekday">Sa</div>
<button class="datepicker-day">1</button>
<button class="datepicker-day">2</button>
<button class="datepicker-day">3</button>
<button class="datepicker-day">4</button>
<button class="datepicker-day">5</button>
<button class="datepicker-day">6</button>
<button class="datepicker-day">7</button>
<button class="datepicker-day">8</button>
<button class="datepicker-day">9</button>
<button class="datepicker-day datepicker-day-selected datepicker-day-range-start">10</button>
<button class="datepicker-day datepicker-day-in-range">11</button>
<button class="datepicker-day datepicker-day-in-range">12</button>
<button class="datepicker-day datepicker-day-in-range">13</button>
<button class="datepicker-day datepicker-day-in-range">14</button>
<button class="datepicker-day datepicker-day-selected datepicker-day-range-end">15</button>
</div>
</div>
</div>Inline Calendar
Display the calendar inline without a dropdown:
Inline Calendar
<div class="datepicker">
<div class="datepicker-dropdown datepicker-inline">
<div class="datepicker-header">
<button class="datepicker-nav">‹</button>
<div class="datepicker-title">January 2024</div>
<button class="datepicker-nav">›</button>
</div>
<div class="datepicker-calendar">
<div class="datepicker-weekday">Su</div>
<div class="datepicker-weekday">Mo</div>
<div class="datepicker-weekday">Tu</div>
<div class="datepicker-weekday">We</div>
<div class="datepicker-weekday">Th</div>
<div class="datepicker-weekday">Fr</div>
<div class="datepicker-weekday">Sa</div>
<button class="datepicker-day">1</button>
<button class="datepicker-day">2</button>
<button class="datepicker-day">3</button>
<button class="datepicker-day">4</button>
<button class="datepicker-day">5</button>
<button class="datepicker-day">6</button>
<button class="datepicker-day">7</button>
<button class="datepicker-day">8</button>
<button class="datepicker-day">9</button>
<button class="datepicker-day">10</button>
<button class="datepicker-day">11</button>
<button class="datepicker-day">12</button>
<button class="datepicker-day">13</button>
<button class="datepicker-day">14</button>
<button class="datepicker-day datepicker-day-selected">15</button>
</div>
</div>
</div>Color Variants
Secondary Color
Use the secondary theme color:
Secondary Color
<div class="datepicker datepicker-secondary">
<div class="datepicker-input-container">
<input type="date" id="date-secondary" name="date-secondary" class="datepicker-input">
<button class="datepicker-trigger" popovertarget="datepicker-secondary">
<span class="datepicker-icon">📅</span>
</button>
</div>
<div class="datepicker-dropdown" id="datepicker-secondary" popover="auto">
<div class="datepicker-header">
<button class="datepicker-nav">‹</button>
<div class="datepicker-title">January 2024</div>
<button class="datepicker-nav">›</button>
</div>
<div class="datepicker-calendar">
<div class="datepicker-weekday">Su</div>
<div class="datepicker-weekday">Mo</div>
<div class="datepicker-weekday">Tu</div>
<div class="datepicker-weekday">We</div>
<div class="datepicker-weekday">Th</div>
<div class="datepicker-weekday">Fr</div>
<div class="datepicker-weekday">Sa</div>
<button class="datepicker-day">1</button>
<button class="datepicker-day">2</button>
<button class="datepicker-day datepicker-day-selected">15</button>
</div>
</div>
</div>Tertiary Color
Use the tertiary theme color:
Tertiary Color
<div class="datepicker datepicker-tertiary">
<div class="datepicker-input-container">
<input type="date" id="date-tertiary" name="date-tertiary" class="datepicker-input">
<button class="datepicker-trigger" popovertarget="datepicker-tertiary">
<span class="datepicker-icon">📅</span>
</button>
</div>
<div class="datepicker-dropdown" id="datepicker-tertiary" popover="auto">
<div class="datepicker-header">
<button class="datepicker-nav">‹</button>
<div class="datepicker-title">January 2024</div>
<button class="datepicker-nav">›</button>
</div>
<div class="datepicker-calendar">
<div class="datepicker-weekday">Su</div>
<div class="datepicker-weekday">Mo</div>
<div class="datepicker-weekday">Tu</div>
<div class="datepicker-weekday">We</div>
<div class="datepicker-weekday">Th</div>
<div class="datepicker-weekday">Fr</div>
<div class="datepicker-weekday">Sa</div>
<button class="datepicker-day">1</button>
<button class="datepicker-day">2</button>
<button class="datepicker-day datepicker-day-selected">15</button>
</div>
</div>
</div>Special Day States
Today’s Date
Highlight the current date:
Today's Date
<button class="datepicker-day datepicker-day-today">15</button>Selected Date
Mark a date as selected:
Selected Date
<button class="datepicker-day datepicker-day-selected">15</button>Disabled Date
Disable certain dates:
Disabled Date
<button class="datepicker-day datepicker-day-disabled">15</button>Other Month Days
Days from adjacent months:
Other Month Days
<button class="datepicker-day datepicker-day-other-month">31</button>Month and Year Picker
Switch between month and year selection views:
Month Picker
Month Picker
<div class="datepicker-dropdown datepicker-inline">
<div class="datepicker-header">
<button class="datepicker-nav">‹</button>
<div class="datepicker-title">2024</div>
<button class="datepicker-nav">›</button>
</div>
<div class="datepicker-months">
<button class="datepicker-month datepicker-month-selected">Jan</button>
<button class="datepicker-month">Feb</button>
<button class="datepicker-month">Mar</button>
<button class="datepicker-month">Apr</button>
<button class="datepicker-month">May</button>
<button class="datepicker-month">Jun</button>
<button class="datepicker-month">Jul</button>
<button class="datepicker-month">Aug</button>
<button class="datepicker-month">Sep</button>
<button class="datepicker-month">Oct</button>
<button class="datepicker-month">Nov</button>
<button class="datepicker-month">Dec</button>
</div>
</div>Year Picker
Year Picker
<div class="datepicker-dropdown datepicker-inline">
<div class="datepicker-header">
<button class="datepicker-nav">‹</button>
<div class="datepicker-title">2020-2029</div>
<button class="datepicker-nav">›</button>
</div>
<div class="datepicker-years">
<button class="datepicker-year">2020</button>
<button class="datepicker-year">2021</button>
<button class="datepicker-year">2022</button>
<button class="datepicker-year">2023</button>
<button class="datepicker-year datepicker-year-selected">2024</button>
<button class="datepicker-year">2025</button>
<button class="datepicker-year">2026</button>
<button class="datepicker-year">2027</button>
<button class="datepicker-year">2028</button>
<button class="datepicker-year">2029</button>
</div>
</div>Footer Actions
Add action buttons to the datepicker footer:
Footer Actions
<div class="datepicker-dropdown datepicker-inline">
<div class="datepicker-header">
<button class="datepicker-nav">‹</button>
<div class="datepicker-title">January 2024</div>
<button class="datepicker-nav">›</button>
</div>
<div class="datepicker-calendar">
<div class="datepicker-weekday">Su</div>
<div class="datepicker-weekday">Mo</div>
<div class="datepicker-weekday">Tu</div>
<div class="datepicker-weekday">We</div>
<div class="datepicker-weekday">Th</div>
<div class="datepicker-weekday">Fr</div>
<div class="datepicker-weekday">Sa</div>
<button class="datepicker-day">1</button>
<button class="datepicker-day">2</button>
<button class="datepicker-day datepicker-day-selected">15</button>
</div>
<div class="datepicker-footer">
<button class="btn btn-text">Clear</button>
<button class="btn btn-text btn-primary">Today</button>
</div>
</div>Best Practices
Accessibility
- Use
readonlyattribute on the input to prevent manual text entry - Provide clear labels for navigation buttons with
aria-label - Ensure keyboard navigation is supported
- Mark disabled dates appropriately
Accessibility Example
<div class="datepicker">
<label for="date-input" class="sr-only">Select a date</label>
<div class="datepicker-input-container">
<input id="date-input" type="date" class="datepicker-input">
<button class="datepicker-trigger" popovertarget="datepicker-a11y" aria-label="Open calendar">
<span class="datepicker-icon" aria-hidden="true">📅</span>
</button>
</div>
<div class="datepicker-dropdown" id="datepicker-a11y" popover="auto">
<div class="datepicker-header">
<button class="datepicker-nav" aria-label="Previous month">‹</button>
<div class="datepicker-title" aria-live="polite">January 2024</div>
<button class="datepicker-nav" aria-label="Next month">›</button>
</div>
<div class="datepicker-calendar">
<div class="datepicker-weekday">Su</div>
<div class="datepicker-weekday">Mo</div>
<div class="datepicker-weekday">Tu</div>
<div class="datepicker-weekday">We</div>
<div class="datepicker-weekday">Th</div>
<div class="datepicker-weekday">Fr</div>
<div class="datepicker-weekday">Sa</div>
<button class="datepicker-day">1</button>
<button class="datepicker-day datepicker-day-selected">15</button>
</div>
</div>
</div>Date Formatting
Display dates in a user-friendly format:
Date Formatting
<!-- Date inputs use ISO format (YYYY-MM-DD) -->
<input type="date" id="date-format-1" name="date-format-1" class="datepicker-input" value="2024-01-15">
<!-- With a default value -->
<input type="date" id="date-format-2" name="date-format-2" class="datepicker-input" value="2024-06-20">Range Selection UX
When implementing range selection, provide clear visual feedback:
Range Selection UX
<div class="datepicker">
<div class="datepicker-input-container">
<input type="date" id="date-range-ux" name="date-range-ux" class="datepicker-input">
<button class="datepicker-trigger" popovertarget="datepicker-range-ux">
<span class="datepicker-icon">📅</span>
</button>
</div>
<div class="datepicker-dropdown" id="datepicker-range-ux" popover="auto">
<div class="datepicker-calendar">
<div class="datepicker-weekday">Su</div>
<div class="datepicker-weekday">Mo</div>
<div class="datepicker-weekday">Tu</div>
<div class="datepicker-weekday">We</div>
<div class="datepicker-weekday">Th</div>
<div class="datepicker-weekday">Fr</div>
<div class="datepicker-weekday">Sa</div>
<!-- Start date -->
<button class="datepicker-day datepicker-day-selected datepicker-day-range-start">10</button>
<!-- Dates in range -->
<button class="datepicker-day datepicker-day-in-range">11</button>
<button class="datepicker-day datepicker-day-in-range">12</button>
<!-- End date -->
<button class="datepicker-day datepicker-day-selected datepicker-day-range-end">15</button>
</div>
</div>
</div>Responsive Design
Ensure the datepicker works well on mobile devices:
Responsive Design
<!-- Use appropriate viewport for mobile -->
<div class="datepicker w-full max-w-sm">
<div class="datepicker-input-container">
<input type="date" id="date-responsive" name="date-responsive" class="datepicker-input">
<button class="datepicker-trigger" popovertarget="datepicker-responsive">
<span class="datepicker-icon">📅</span>
</button>
</div>
<div class="datepicker-dropdown" id="datepicker-responsive" popover="auto">
<div class="datepicker-header">
<button class="datepicker-nav">‹</button>
<div class="datepicker-title">January 2024</div>
<button class="datepicker-nav">›</button>
</div>
<div class="datepicker-calendar">
<div class="datepicker-weekday">Su</div>
<div class="datepicker-weekday">Mo</div>
<div class="datepicker-weekday">Tu</div>
<div class="datepicker-weekday">We</div>
<div class="datepicker-weekday">Th</div>
<div class="datepicker-weekday">Fr</div>
<div class="datepicker-weekday">Sa</div>
<button class="datepicker-day">1</button>
<button class="datepicker-day datepicker-day-selected">15</button>
</div>
</div>
</div>Framework Examples
React
import { useState } from 'react';
interface DatepickerProps {
value?: Date;
onChange?: (date: Date) => void;
variant?: 'primary' | 'secondary' | 'tertiary';
inline?: boolean;
}
export function Datepicker({
value,
onChange,
variant = 'primary',
inline = false
}: DatepickerProps) {
const [currentMonth, setCurrentMonth] = useState(new Date());
const popoverId = `datepicker-${Math.random().toString(36).slice(2)}`;
const formatDate = (date: Date) => {
return date.toLocaleDateString('en-US', {
year: 'numeric',
month: 'long',
day: 'numeric'
});
};
return (
<div className={`datepicker ${variant ? `datepicker-${variant}` : ''}`}>
{!inline && (
<div className="datepicker-input-container">
<input
type="text"
className="datepicker-input"
value={value ? formatDate(value) : ''}
placeholder="Select date"
readOnly
/>
<button className="datepicker-trigger" popoverTarget={popoverId}>
<span className="datepicker-icon">📅</span>
</button>
</div>
)}
<div
id={popoverId}
className={`datepicker-dropdown ${inline ? 'datepicker-inline' : ''}`}
popover={inline ? undefined : ''}
>
{/* Calendar implementation */}
</div>
</div>
);
}
// Usage
<Datepicker
value={selectedDate}
onChange={setSelectedDate}
variant="primary"
/>
Vue
<template>
<div :class="['datepicker', variant && `datepicker-${variant}`]">
<div v-if="!inline" class="datepicker-input-container">
<input
type="text"
class="datepicker-input"
:value="formattedDate"
placeholder="Select date"
readonly
/>
<button
class="datepicker-trigger"
:popovertarget="popoverId"
>
<span class="datepicker-icon">📅</span>
</button>
</div>
<div
:id="popoverId"
:class="[
'datepicker-dropdown',
{ 'datepicker-inline': inline }
]"
:popover="inline ? undefined : ''"
>
<!-- Calendar implementation -->
</div>
</div>
</template>
<script setup>
import { ref, computed } from 'vue';
const props = defineProps({
modelValue: Date,
variant: {
type: String,
default: 'primary'
},
inline: {
type: Boolean,
default: false
}
});
const emit = defineEmits(['update:modelValue']);
const popoverId = `datepicker-${Math.random().toString(36).slice(2)}`;
const formattedDate = computed(() => {
if (!props.modelValue) return '';
return props.modelValue.toLocaleDateString('en-US', {
year: 'numeric',
month: 'long',
day: 'numeric'
});
});
</script>
<!-- Usage -->
<Datepicker
v-model="selectedDate"
variant="primary"
/>
API Reference
Class Names
| Class | Description |
|---|---|
.datepicker | Base datepicker container (required) |
.datepicker-input-container | Container for input and trigger button |
.datepicker-trigger | Icon button trigger (use with popovertarget) |
.datepicker-input | Input field for displaying selected date |
.datepicker-icon | Calendar icon positioned over input |
.datepicker-dropdown | Calendar dropdown container (use with popover attribute) |
.datepicker-inline | Display calendar inline (always visible) |
.datepicker-header | Calendar header with navigation |
.datepicker-title | Month/Year title in header |
.datepicker-nav | Previous/Next navigation buttons |
.datepicker-calendar | Calendar grid container |
.datepicker-weekday | Weekday name header |
.datepicker-day | Individual day cell |
.datepicker-day-today | Current date highlight |
.datepicker-day-selected | Selected date |
.datepicker-day-disabled | Disabled/unavailable date |
.datepicker-day-other-month | Days from adjacent months |
.datepicker-day-in-range | Date within selected range |
.datepicker-day-range-start | Range start date |
.datepicker-day-range-end | Range end date |
.datepicker-months | Month picker grid |
.datepicker-month | Individual month cell |
.datepicker-month-selected | Selected month |
.datepicker-years | Year picker grid |
.datepicker-year | Individual year cell |
.datepicker-year-selected | Selected year |
.datepicker-footer | Footer with action buttons |
.datepicker-secondary | Use secondary theme color |
.datepicker-tertiary | Use tertiary theme color |
HTML Structure with Popover API
<div class="datepicker">
<div class="datepicker-input-container">
<input type="date" id="my-date" name="my-date" class="datepicker-input">
<button class="datepicker-trigger" popovertarget="dropdown-id">
<span class="datepicker-icon">📅</span>
</button>
</div>
<div class="datepicker-dropdown" id="dropdown-id" popover="auto">
<div class="datepicker-header">
<button class="datepicker-nav">‹</button>
<div class="datepicker-title">January 2024</div>
<button class="datepicker-nav">›</button>
</div>
<div class="datepicker-calendar">
<!-- Weekday headers and day cells -->
</div>
</div>
</div>
Combinations
You can combine classes for different effects:
Class Combinations
<!-- Secondary color with inline display -->
<div class="datepicker datepicker-secondary">
<div class="datepicker-dropdown datepicker-inline">
<div class="datepicker-header">
<button class="datepicker-nav">‹</button>
<div class="datepicker-title">January 2024</div>
<button class="datepicker-nav">›</button>
</div>
<div class="datepicker-calendar">
<div class="datepicker-weekday">Su</div>
<div class="datepicker-weekday">Mo</div>
<div class="datepicker-weekday">Tu</div>
<div class="datepicker-weekday">We</div>
<div class="datepicker-weekday">Th</div>
<div class="datepicker-weekday">Fr</div>
<div class="datepicker-weekday">Sa</div>
<button class="datepicker-day datepicker-day-selected">15</button>
</div>
</div>
</div>