Feature Guide

Date & Time Pickers: Advanced Patterns

February 2026 · 10 min read

Date pickers seem simple until you need real constraints. "No weekends." "At least 48 hours notice." "End date after start date." "Business hours only on weekdays, extended hours on weekends." These requirements turn a simple field into a logic puzzle.

FormTs date and time pickers support reactive constraints. The minimum date can depend on another field. Available times can change based on the selected day. Everything updates automatically as users make selections.

Basic Date and Time Fields

The simplest case: a date picker with no constraints.

form.addRow(row => {
    row.addDatepicker('appointmentDate', {
        label: 'Appointment Date',
        isRequired: true
    });
});

Values come back as ISO date strings: '2026-02-15'. For time:

form.addRow(row => {
    row.addTimepicker('appointmentTime', {
        label: 'Appointment Time',
        isRequired: true
    });
});

Time values use 24-hour format: '14:30' for 2:30 PM. The picker displays in the user's locale preference, but the underlying value is always consistent.

Date Ranges

Start and end dates are a common pattern. The end date should never be before the start date. Make minDate reactive.

// Start and end date with range validation
form.addRow(row => {
    row.addDatepicker('startDate', {
        label: 'Start Date',
        isRequired: true
    });
    row.addDatepicker('endDate', {
        label: 'End Date',
        isRequired: true,
        // End date must be after start date
        minDate: () => form.datepicker('startDate')?.value() ?? undefined
    });
});

When the user picks a start date, the end date picker automatically prevents selecting anything earlier. Change the start date, and the constraint updates instantly. If the end date becomes invalid (start moved past it), the picker shows the error.

Pro tip

For multi-day bookings, you might want end date's minDate to be the day after start date, not the same day. Add one day to the start date value in your reactive function.

Future Dates Only

Appointments, bookings, and events usually need future dates. Set minDate to today.

// Only allow dates in the future
const today = new Date().toISOString().split('T')[0];

form.addRow(row => {
    row.addDatepicker('eventDate', {
        label: 'Event Date',
        minDate: today,
        isRequired: true
    });
});

This calculates today's date once when the form loads. For forms that stay open for days (unlikely, but possible), you'd want to recalculate in a reactive function.

Booking Windows

Many businesses limit how far ahead users can book. A restaurant might accept reservations 1-30 days in advance. A popular venue might book 6 months out.

// Booking window: 1 day to 30 days ahead
const tomorrow = new Date();
tomorrow.setDate(tomorrow.getDate() + 1);

const maxDate = new Date();
maxDate.setDate(maxDate.getDate() + 30);

form.addRow(row => {
    row.addDatepicker('bookingDate', {
        label: 'Booking Date',
        minDate: tomorrow.toISOString().split('T')[0],
        maxDate: maxDate.toISOString().split('T')[0]
    });
});

Both minDate and maxDate constrain the calendar. Dates outside the range are disabled - users can see them but can't select them.

Business Hours

Time pickers support minTime and maxTime to restrict available hours.

// Restrict to business hours
form.addRow(row => {
    row.addTimepicker('meetingTime', {
        label: 'Meeting Time',
        minTime: '09:00',
        maxTime: '17:00'
    });
});

Users can only select times between 9 AM and 5 PM. Times outside this range are disabled in the picker.

Conditional Time Ranges

What if your hours vary? Weekdays 8-6, weekends 10-4. Make the constraints reactive.

// Different time ranges based on day type
form.addRow(row => {
    row.addDropdown('dayType', {
        label: 'Day Type',
        options: [
            { id: 'weekday', name: 'Weekday' },
            { id: 'weekend', name: 'Weekend' }
        ],
        defaultValue: 'weekday'
    });
});

form.addRow(row => {
    row.addTimepicker('serviceTime', {
        label: 'Service Time',
        minTime: () => {
            const dayType = form.dropdown('dayType')?.value();
            return dayType === 'weekend' ? '10:00' : '08:00';
        },
        maxTime: () => {
            const dayType = form.dropdown('dayType')?.value();
            return dayType === 'weekend' ? '16:00' : '18:00';
        }
    });
});

When the user switches from weekday to weekend, the time picker updates its valid range immediately. A previously valid 8:30 AM selection becomes invalid on weekends.

Date-Time Pairs

Appointment scheduling often needs both date and time. You can require the date first before showing the time picker.

// Combined date and time selection
form.addRow(row => {
    row.addDatepicker('appointmentDate', {
        label: 'Date',
        isRequired: true
    });
    row.addTimepicker('appointmentTime', {
        label: 'Time',
        isRequired: true,
        // Only show time picker after date is selected
        isVisible: () => !!form.datepicker('appointmentDate')?.value()
    });
});

The time picker stays hidden until a date is selected. This reduces visual clutter and guides users through the selection in order.

Duration Calculation

When users select start and end times, calculate and display the duration automatically.

// Calculate duration between start and end times
const startTime = form.state('09:00');
const endTime = form.state('10:00');

const duration = form.computedValue(() => {
    const start = startTime();
    const end = endTime();

    if (!start || !end) return null;

    const [startH, startM] = start.split(':').map(Number);
    const [endH, endM] = end.split(':').map(Number);

    const startMinutes = startH * 60 + startM;
    const endMinutes = endH * 60 + endM;

    const diff = endMinutes - startMinutes;
    if (diff <= 0) return null;

    const hours = Math.floor(diff / 60);
    const minutes = diff % 60;

    if (hours === 0) return `${minutes} minutes`;
    if (minutes === 0) return `${hours} hour${hours > 1 ? 's' : ''}`;
    return `${hours}h ${minutes}m`;
});

form.addRow(row => {
    row.addTimepicker('start', {
        label: 'Start Time',
        defaultValue: '09:00',
        onValueChange: (val) => startTime.set(val ?? '09:00')
    });
    row.addTimepicker('end', {
        label: 'End Time',
        defaultValue: '10:00',
        minTime: () => startTime(),
        onValueChange: (val) => endTime.set(val ?? '10:00')
    });
});

form.addRow(row => {
    row.addTextPanel('duration', {
        label: () => duration() ? `Duration: ${duration()}` : 'Select times'
    });
});

The duration updates as users change either time. The end time picker enforces that it must be after the start time. Invalid combinations (end before start) show as null duration.

See booking forms with date/time pickers.

Days Until Event

Show users how far away their selected date is. Helpful for event planning, delivery estimates, or appointment reminders.

// Show days until selected date
const selectedDate = form.state<string | null>(null);

const daysUntil = form.computedValue(() => {
    const dateStr = selectedDate();
    if (!dateStr) return null;

    const selected = new Date(dateStr);
    const today = new Date();
    today.setHours(0, 0, 0, 0);

    const diffTime = selected.getTime() - today.getTime();
    const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24));

    if (diffDays === 0) return 'Today';
    if (diffDays === 1) return 'Tomorrow';
    return `${diffDays} days from now`;
});

form.addRow(row => {
    row.addDatepicker('eventDate', {
        label: 'Event Date',
        minDate: new Date().toISOString().split('T')[0],
        onValueChange: (val) => selectedDate.set(val ?? null)
    });
});

form.addRow(row => {
    row.addTextPanel('countdown', {
        label: () => daysUntil() ?? '',
        isVisible: () => !!daysUntil()
    });
});

The countdown updates when the date changes. "Today", "Tomorrow", or "X days from now" provides immediate context.

Lead Time Requirements

Many services need advance notice. A catering company might require 48 hours. A custom order might need 2 weeks.

// Require 48-hour lead time for bookings
const minBookingDate = form.computedValue(() => {
    const date = new Date();
    date.setHours(date.getHours() + 48);
    return date.toISOString().split('T')[0];
});

form.addRow(row => {
    row.addDatepicker('serviceDate', {
        label: 'Service Date',
        minDate: () => minBookingDate(),
        isRequired: true
    });
});

form.addRow(row => {
    row.addTextPanel('leadTimeNote', {
        label: 'Bookings require 48-hour advance notice.'
    });
});

The minimum date is computed from current time plus the lead time. This is more accurate than just adding days - 48 hours from Monday afternoon is Wednesday afternoon, not just "Wednesday".

Seasonal Variations

Booking windows might vary by season. A summer camp books further ahead than an after-school program.

// Different date ranges by season
const currentMonth = new Date().getMonth();
const isSummer = currentMonth >= 5 && currentMonth <= 8;

form.addRow(row => {
    row.addDatepicker('visitDate', {
        label: 'Visit Date',
        minDate: new Date().toISOString().split('T')[0],
        // Extend booking window during summer
        maxDate: () => {
            const max = new Date();
            max.setDate(max.getDate() + (isSummer ? 90 : 30));
            return max.toISOString().split('T')[0];
        }
    });
});

form.addRow(row => {
    row.addTextPanel('seasonNote', {
        label: isSummer
            ? 'Summer season: Book up to 90 days ahead'
            : 'Book up to 30 days in advance'
    });
});

The maximum date extends during peak season. You could make this fully reactive based on the current date, adjusting the season detection dynamically.

Common Patterns Summary

Appointment Booking

  • Future dates only (minDate = today)
  • Limited advance booking (maxDate = today + 30 days)
  • Business hours only (minTime/maxTime)
  • Show availability after date selected

Event Registration

  • Registration closes on event date (maxDate = event date)
  • Early bird deadline highlighted
  • Show days until event

Travel Booking

  • Departure and return dates (return minDate = departure)
  • Minimum stay requirements
  • Blackout dates for holidays

Rental/Lease Periods

  • Start and end dates
  • Minimum rental period (end minDate = start + minimum)
  • Calculate total days/weeks/months
  • Prorate pricing based on duration

Best Practices

Show Constraints Clearly

Don't just disable invalid dates silently. Add help text explaining the rules. "Book at least 48 hours ahead" is clearer than users wondering why tomorrow is grayed out.

Handle Timezone Considerations

Date strings are timezone-agnostic. '2026-02-15' is the same date everywhere. But "48 hours from now" depends on the current moment. Be clear about whether constraints are calendar-based or time-based.

Validate Server-Side Too

Client-side constraints are for UX. A determined user can bypass them. Always validate dates and times on the server before processing bookings or registrations.

Consider Mobile Users

Date and time pickers on mobile often use native OS controls. These generally respect your min/max constraints, but test on actual devices to ensure the experience is smooth.

Common Questions

Can I disable specific dates like holidays or fully-booked days?

The built-in minDate/maxDate work for ranges. For specific blackout dates (holidays, unavailable days), you'd validate after selection and show an error if the date is blocked. A future enhancement may add a disabledDates option.

What format are date and time values?

Dates are ISO format: 'YYYY-MM-DD' (e.g., '2026-02-15'). Times are 24-hour format: 'HH:mm' (e.g., '14:30' for 2:30 PM). These formats are consistent regardless of the user's locale.

How do I set a default date to today?

Calculate today's date and use it as defaultValue: defaultValue: new Date().toISOString().split('T')[0]. For time, use a string like defaultValue: '09:00'.

Can I combine date and time into a single datetime field?

FormTs has separate date and time pickers. For a combined datetime, use both fields in a row and combine their values: const datetime = date.value() + 'T' + time.value(). This gives you 'YYYY-MM-DDTHH:mm' format.

Build Smart Scheduling Forms

Date ranges, time constraints, automatic validation. Booking forms that just work.