Skip to content

Calendar Examples

Complete calendar implementations in different frameworks.

Month Calendar

A full-featured month calendar with navigation and selection:

vue
<template>
  <div class="calendar">
    <!-- Navigation -->
    <header>
      <button @click="navigateMonth(-1)">Previous</button>
      <h2>{{ monthLabel }}</h2>
      <button @click="navigateMonth(1)">Next</button>
    </header>
    
    <!-- Week days header -->
    <div class="weekdays">
      <div v-for="day in weekDays" :key="day">{{ day }}</div>
    </div>
    
    <!-- Calendar grid -->
    <div class="days-grid">
      <div 
        v-for="(day, index) in calendarDays" 
        :key="index"
        :class="{
          empty: !day,
          today: day && isToday(day),
          weekend: day && isWeekend(day)
        }"
        @click="day && selectDay(day)"
      >
        {{ day?.date.getDate() }}
      </div>
    </div>
  </div>
</template>

<script setup>
import { computed } from 'vue'
import { createTemporal, usePeriod, divide, go, isSame } from 'usetemporal'

const temporal = createTemporal({ weekStartsOn: 1 })
const month = usePeriod(temporal, 'month')
const days = computed(() => divide(temporal, month.value, 'day'))

const weekDays = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']

const monthLabel = computed(() => 
  month.value.date.toLocaleDateString('en', { 
    month: 'long', 
    year: 'numeric' 
  })
)

const calendarDays = computed(() => {
  const allDays = days.value
  const firstDay = allDays[0]
  const startPadding = (firstDay.date.getDay() + 6) % 7
  
  const calendar = Array(startPadding).fill(null)
  calendar.push(...allDays)
  
  while (calendar.length % 7 !== 0) {
    calendar.push(null)
  }
  
  return calendar
})

function navigateMonth(direction) {
  temporal.browsing.value = go(temporal, month.value, direction)
}

function selectDay(day) {
  temporal.browsing.value = day
  console.log('Selected:', day.date)
}

function isToday(day) {
  return isSame(temporal, day.date, new Date(), 'day')
}

function isWeekend(day) {
  const dayOfWeek = day.date.getDay()
  return dayOfWeek === 0 || dayOfWeek === 6
}
</script>

<style scoped>
.calendar {
  max-width: 400px;
  margin: 0 auto;
  font-family: system-ui, sans-serif;
}

header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 1rem;
}

.weekdays {
  display: grid;
  grid-template-columns: repeat(7, 1fr);
  text-align: center;
  font-weight: bold;
  margin-bottom: 0.5rem;
}

.days-grid {
  display: grid;
  grid-template-columns: repeat(7, 1fr);
  gap: 1px;
}

.days-grid > div {
  aspect-ratio: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  background: #f5f5f5;
  cursor: pointer;
}

.days-grid > div:not(.empty):hover {
  background: #e0e0e0;
}

.today {
  background: #007aff !important;
  color: white;
}

.weekend {
  color: #ff3b30;
}

.empty {
  cursor: default !important;
  background: transparent !important;
}
</style>
tsx
import React, { useMemo } from 'react'
import { createTemporal, usePeriod, divide, go, isSame } from 'usetemporal'

function Calendar() {
  const temporal = useMemo(() => 
    createTemporal({ weekStartsOn: 1 }), []
  )
  
  const month = usePeriod(temporal, 'month')
  const days = useMemo(() => 
    divide(temporal, month.value, 'day'), 
    [month.value]
  )
  
  const weekDays = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
  
  const monthLabel = month.value.date.toLocaleDateString('en', { 
    month: 'long', 
    year: 'numeric' 
  })
  
  const calendarDays = useMemo(() => {
    const firstDay = days[0]
    const startPadding = (firstDay.date.getDay() + 6) % 7
    
    const calendar = Array(startPadding).fill(null)
    calendar.push(...days)
    
    while (calendar.length % 7 !== 0) {
      calendar.push(null)
    }
    
    return calendar
  }, [days])
  
  const navigateMonth = (direction) => {
    temporal.browsing.value = go(temporal, month.value, direction)
  }
  
  const selectDay = (day) => {
    temporal.browsing.value = day
    console.log('Selected:', day.date)
  }
  
  const isToday = (day) => 
    isSame(temporal, day.date, new Date(), 'day')
  
  const isWeekend = (day) => {
    const dayOfWeek = day.date.getDay()
    return dayOfWeek === 0 || dayOfWeek === 6
  }
  
  return (
    <div className="calendar">
      <header>
        <button onClick={() => navigateMonth(-1)}>Previous</button>
        <h2>{monthLabel}</h2>
        <button onClick={() => navigateMonth(1)}>Next</button>
      </header>
      
      <div className="weekdays">
        {weekDays.map(day => (
          <div key={day}>{day}</div>
        ))}
      </div>
      
      <div className="days-grid">
        {calendarDays.map((day, index) => (
          <div 
            key={index}
            className={`
              ${!day ? 'empty' : ''}
              ${day && isToday(day) ? 'today' : ''}
              ${day && isWeekend(day) ? 'weekend' : ''}
            `}
            onClick={() => day && selectDay(day)}
          >
            {day?.date.getDate()}
          </div>
        ))}
      </div>
    </div>
  )
}
svelte
<script>
  import { createTemporal, usePeriod, divide, go, isSame } from 'usetemporal'
  
  const temporal = createTemporal({ weekStartsOn: 1 })
  const month = usePeriod(temporal, 'month')
  const weekDays = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
  
  $: days = divide(temporal, $month, 'day')
  $: firstDay = days[0]
  $: startPadding = (firstDay.date.getDay() + 6) % 7
  
  $: monthLabel = $month.date.toLocaleDateString('en', { 
    month: 'long', 
    year: 'numeric' 
  })
  
  $: calendarDays = [
    ...Array(startPadding).fill(null),
    ...days,
    ...Array((7 - ((startPadding + days.length) % 7)) % 7).fill(null)
  ]
  
  function navigateMonth(direction) {
    temporal.browsing.value = go(temporal, $month, direction)
  }
  
  function selectDay(day) {
    temporal.browsing.value = day
    console.log('Selected:', day.date)
  }
  
  function isToday(day) {
    return isSame(temporal, day.date, new Date(), 'day')
  }
  
  function isWeekend(day) {
    const dayOfWeek = day.date.getDay()
    return dayOfWeek === 0 || dayOfWeek === 6
  }
</script>

<div class="calendar">
  <header>
    <button on:click={() => navigateMonth(-1)}>Previous</button>
    <h2>{monthLabel}</h2>
    <button on:click={() => navigateMonth(1)}>Next</button>
  </header>
  
  <div class="weekdays">
    {#each weekDays as day}
      <div>{day}</div>
    {/each}
  </div>
  
  <div class="days-grid">
    {#each calendarDays as day, i}
      <div 
        class:empty={!day}
        class:today={day && isToday(day)}
        class:weekend={day && isWeekend(day)}
        on:click={() => day && selectDay(day)}
      >
        {day?.date.getDate() || ''}
      </div>
    {/each}
  </div>
</div>

Year Overview

Display an entire year with month statistics:

typescript
import { createTemporal, usePeriod, divide, isSame } from 'usetemporal'

function createYearOverview(temporal) {
  const year = usePeriod(temporal, 'year')
  const months = computed(() => divide(temporal, year.value, 'month'))
  
  const overview = computed(() => 
    months.value.map(month => {
      const days = divide(temporal, month, 'day')
      const weeks = Math.ceil((days[0].date.getDay() + days.length) / 7)
      
      const workDays = days.filter(day => {
        const dow = day.date.getDay()
        return dow >= 1 && dow <= 5
      })
      
      return {
        name: month.date.toLocaleDateString('en', { month: 'long' }),
        totalDays: days.length,
        workDays: workDays.length,
        weekends: days.length - workDays.length,
        weeks: weeks,
        hasToday: days.some(day => 
          isSame(temporal, day.date, new Date(), 'day')
        )
      }
    })
  )
  
  return { year, overview }
}

Time Picker

Create time slots with business hours:

typescript
import { createTemporal, usePeriod, divide } from 'usetemporal'

function createTimePicker(temporal) {
  const day = usePeriod(temporal, 'day')
  const hours = computed(() => divide(temporal, day.value, 'hour'))
  
  const timeSlots = computed(() => {
    const slots = []
    
    // Business hours only (9 AM - 5 PM)
    const businessHours = hours.value.filter(hour => {
      const h = hour.date.getHours()
      return h >= 9 && h < 17
    })
    
    businessHours.forEach(hour => {
      const minutes = divide(temporal, hour, 'minute')
      
      // Create 30-minute slots
      for (let i = 0; i < 60; i += 30) {
        slots.push({
          label: `${hour.date.getHours().toString().padStart(2, '0')}:${i.toString().padStart(2, '0')}`,
          available: minutes[i].date > new Date(),
          period: minutes[i]
        })
      }
    })
    
    return slots
  })
  
  return { day, timeSlots }
}

Week View

Show a week with hourly breakdown:

typescript
const week = usePeriod(temporal, 'week')
const days = computed(() => divide(temporal, week.value, 'day'))

const weekView = computed(() => 
  days.value.map(day => ({
    name: day.date.toLocaleDateString('en', { weekday: 'short' }),
    date: day.date.getDate(),
    hours: divide(temporal, day, 'hour').map(hour => ({
      time: hour.date.getHours(),
      isBusinessHour: hour.date.getHours() >= 9 && hour.date.getHours() < 17
    }))
  }))
)

Released under the MIT License.