Special Field

Date Picker

Calendar-based date selection with configurable constraints.

stable
date calendar picker datetime

Interactive demo of date picker field

When were you born?

Choose a date for your appointment

When should this be completed?

/**
 * Date Field Demo - Interactive examples of date picker fields
 */

import { Form, FormBuilder } from '@saastro/forms';

import { FormProvider } from '@/components/FormProvider';
import { TooltipProvider } from '@/components/ui/tooltip';

const config = FormBuilder.create('date-demo')
  .layout('manual')
  .columns(12)
  .addField('birthdate', (f) =>
    f
      .type('date')
      .label('Date of Birth')
      .placeholder('Select your birth date')
      .helperText('When were you born?')
      .required('Date of birth is required')
      .columns({ default: 12, md: 6 }),
  )
  .addField('appointment', (f) =>
    f
      .type('date')
      .label('Appointment Date')
      .placeholder('Select appointment date')
      .helperText('Choose a date for your appointment')
      .required()
      .columns({ default: 12, md: 6 }),
  )
  .addField('deadline', (f) =>
    f
      .type('date')
      .label('Project Deadline')
      .placeholder('Select deadline')
      .helperText('When should this be completed?')
      .optional()
      .columns({ default: 12 }),
  )
  .addStep('main', ['birthdate', 'appointment', 'deadline'])
  .build();

export default function DateDemo() {
  const handleSubmit = (data: Record<string, unknown>) => {
    console.log('Form submitted:', data);
    alert('Form submitted! Check console for data.');
  };

  return (
    <TooltipProvider>
      <FormProvider>
        <Form config={config} onSubmit={handleSubmit} className="space-y-4" />
      </FormProvider>
    </TooltipProvider>
  );
}

Overview

The date picker field provides a calendar interface for selecting a single date, built on react-day-picker. It renders either a popover-triggered calendar (the default) or an inline calendar, controlled by the dateType prop, and supports date constraints through Zod schemas.

Usage

Basic Date Picker

import { FormBuilder } from '@saastro/forms';

const config = FormBuilder.create('form')
  .addField('birthdate', (f) =>
    f
      .type('date')
      .label('Date of Birth')
      .placeholder('Select your birth date')
      .required('Date of birth is required'),
  )
  .addStep('main', ['birthdate'])
  .build();

Display Modes (dateType)

The dateType prop switches between two render modes:

  • 'popover' (default) — an outline button that opens the calendar in a popover. The button shows the selected date (or the placeholder) and the calendar uses dropdown month/year navigation. The popover closes when a date is picked.
  • 'simple' — the calendar is rendered inline, always visible, with no trigger button.
// Inline calendar instead of the default popover
.addField('checkIn', (f) =>
  f.type('date')
    .label('Check-in Date')
    .dateType('simple')
    .required()
)

With Date Constraints

.addField('appointment', (f) =>
  f.type('date')
    .label('Appointment Date')
    .placeholder('Select a date')
    // Future dates only (via Zod) — z.date() already rejects empty values,
    // so the field is implicitly required
    .schema(z.date().min(new Date(), 'Must be a future date'))
)

Don’t mix .schema(zodSchema) with declarative validation methods like .required() or .regex() on the same field — calling a declarative method replaces a previously set Zod schema, silently dropping its constraints. Use one approach or the other.

Default Value

.addField('startDate', (f) =>
  f.type('date')
    .label('Start Date')
    .value(new Date())
    .required()
)

JSON Configuration

{
  "type": "date",
  "label": "Date of Birth",
  "placeholder": "Select your birth date",
  "dateType": "popover",
  "schema": { "required": true }
}

Props

PropTypeDefaultDescription
type'date'-Field type (required)
dateType'simple' | 'popover''popover'Inline calendar (simple) or popover button
labelstring-Label text
placeholderstring'Pick a date'Popover button text when no date is selected
helperTextstring-Help text below field
valueDate-Default date
columnsPartial<Record<Breakpoint, number>>-Grid columns by breakpoint
disabledboolean | function | ConditionGroupfalseDisable the field
hiddenboolean | function | ConditionGroup | ResponsivefalseHide the field

Declared but not yet implemented: showTime?: boolean and presets?: Array<{ label: string; value: number }> exist on the field’s type (and have builder methods .showTime() / .datePresets()), but the renderer currently ignores them — setting them has no effect.

Display Format & Localization

In popover mode, the selected date is shown using the date-fns 'PPP' format — English by default (e.g. June 11th, 2026). The default placeholder is 'Pick a date', overridable with the placeholder prop or per-locale via the i18n overlay. The date display locale is configurable globally with setDateLocale:

import { setDateLocale } from '@saastro/forms';
import { es } from 'date-fns/locale';

setDateLocale(es); // 'PPP' now renders "11 de junio de 2026"

Validation

Required Date

.addField('eventDate', (f) =>
  f.type('date')
    .label('Event Date')
    .required('Please select a date')
)

With Zod Constraints

import { z } from 'zod';

const oneYearFromNow = new Date();
oneYearFromNow.setFullYear(oneYearFromNow.getFullYear() + 1);

.addField('deadline', (f) =>
  f.type('date')
    .label('Deadline')
    .schema(z.date()
      .min(new Date(), 'Must be a future date')
      .max(oneYearFromNow, 'Must be within the next year')
    )
)

Styling

Custom Classes

.addField('birthdate', (f) =>
  f.type('date')
    .label('Date of Birth')
    .required()
    .classNames({
      wrapper: 'bg-muted/30 p-4 rounded-lg',
      input: 'border-2 border-primary/20',
      label: 'text-lg font-semibold',
      error: 'text-destructive text-sm',
    })
)

Responsive Layout

.addField('eventDate', (f) =>
  f.type('date')
    .label('Event Date')
    .columns({ default: 12, md: 6 })
)

JSON Styling

{
  "type": "date",
  "label": "Date of Birth",
  "wrapper_className": "bg-muted/30 p-4 rounded-lg",
  "input_className": "border-2 border-primary/20",
  "label_className": "text-lg font-semibold",
  "columns": { "default": 12, "md": 6 }
}

Accessibility

  • Built on react-day-picker
  • Full keyboard navigation
  • Screen reader support
  • ARIA labels for calendar navigation