> Markdown version of [Date Picker](https://vaadin.com/docs/next/components/date-picker). Section index: [llms.txt](https://vaadin.com/docs/next/components/llms.txt)

# Date Picker

Date Picker is an input field that allows the user to enter a date by typing or by selecting from a calendar overlay.

Try clicking the calendar icon. A calendar overlay appears, showing the current month. Click on a date to select it.

**Lit** — `date-picker-basic.ts`

```html
<vaadin-date-picker label="Start date"></vaadin-date-picker>
```

**Flow** — `DatePickerBasic.java`

```java
DatePicker datePicker = new DatePicker("Start date");
add(datePicker);
```

**React** — `date-picker-basic.tsx`

```tsx
<DatePicker label="Start date" />
```

The date can be entered directly using the keyboard in the format of the current locale or through the date picker overlay. The overlay opens when the field is clicked or any input is entered when the field is focused.

## <a id="validation"></a>Validation

Date Picker provides a validation mechanism based on constraints. Constraints allow you to define criteria that the date must meet to be considered valid. Validation occurs typically when the user initiates a date change, for example by selecting a date from the overlay or through text input followed by `Enter`. If the date is invalid, the field is highlighted in red, and an error message appears underneath the input.

Below is a list of supported constraints with more detailed information:

Bad Input

Bad input refers to any input that cannot be parsed into a value of the field type. When an unparsable value is entered, the field resets the value to empty and becomes invalid. This constraint is non-configurable and enabled by default.

Required

Required fields are marked with an indicator next to the label, and become invalid if their value is first entered and then cleared.

An instruction text at the top of the form explaining the required indicator is recommended. The indicator itself can be customized with the `--vaadin-input-field-required-indicator` style property.

Min & Max Values

The valid input range of Date Picker can be restricted by defining `min` and `max` values. Dates before the `min` and after the `max` are disabled in the overlay. Helper text can be used to inform the user about the accepted range.

Disabled Weekdays (since undefined)

Whole weekdays can be excluded from selection, for example to restrict a booking to business days. Dates that fall on a disabled weekday are shown as disabled in the overlay, and typing one into the field makes it invalid.

Disabled Dates (since undefined)

Individual dates can also be excluded from selection, for example public holidays. Like disabled weekdays, they’re shown as disabled in the overlay, and typing one into the field makes it invalid.

This is meant for a small set of dates that’s known in advance. When the set is large, changes while the application runs, or when particular dates should also be styled, use the date metadata callback instead. See [Configuring Individual Dates](#configuring-individual-dates).

The following example demonstrates how to specify these constraints and provide error messages:

**Lit** — `date-picker-validation.ts`

```html
private closedDates = [3, 4].map((days) =>
  formatISO(addDays(new Date(), days), { representation: 'date' })
);

private isOfficeClosed = (isoDate: string) =>
  isWeekend(parseISO(isoDate)) || this.closedDates.includes(isoDate);

private isDateDisabled = ({ year, month, day }: DatePickerDate) =>
  this.isOfficeClosed(formatISO(new Date(year, month, day), { representation: 'date' }));

protected override render() {
  return html`
    <vaadin-date-picker
      label="Appointment date"
      helper-text="Must be a business day within 60 days from today"
      required
      .min="${formatISO(this.minDate, { representation: 'date' })}"
      .max="${formatISO(this.maxDate, { representation: 'date' })}"
      .isDateDisabled="${this.isDateDisabled}"
      .errorMessage="${this.errorMessage}"
      @validated="${(event: DatePickerValidatedEvent) => {
        const field = event.target as DatePicker;
        if (!field.value && (field.inputElement as HTMLInputElement).value) {
          this.errorMessage = 'Invalid date format';
        } else if (!field.value) {
          this.errorMessage = 'Field is required';
        } else if (field.value < field.min!) {
          this.errorMessage = 'Too early, choose another date';
        } else if (field.value > field.max!) {
          this.errorMessage = 'Too late, choose another date';
        } else if (this.isOfficeClosed(field.value)) {
          this.errorMessage = 'The office is closed, choose another date';
        } else {
          this.errorMessage = '';
        }
      }}"
    ></vaadin-date-picker>
  `;
}
```

**Flow** — `DatePickerValidation.java`

```java
LocalDate now = LocalDate.now(ZoneId.systemDefault());

datePicker.setRequiredIndicatorVisible(true);
datePicker.setMin(now);
datePicker.setMax(now.plusDays(60));
datePicker.setDisabledWeekdays(
        EnumSet.of(DayOfWeek.SATURDAY, DayOfWeek.SUNDAY));
datePicker.setDisabledDates(List.of(now.plusDays(3), now.plusDays(4)));
datePicker.setHelperText(
        "Must be a business day within 60 days from today");

datePicker.setI18n(new DatePickerI18n()
        .setBadInputErrorMessage("Invalid date format")
        .setRequiredErrorMessage("Field is required")
        .setMinErrorMessage("Too early, choose another date")
        .setMaxErrorMessage("Too late, choose another date")
        .setDisabledDateErrorMessage(
                "The office is closed, choose another date"));
```

**React** — `date-picker-validation.tsx`

```tsx
const closedDates = [3, 4].map((days) =>
  formatISO(addDays(new Date(), days), { representation: 'date' })
);

function isOfficeClosed(isoDate: string): boolean {
  return isWeekend(parseISO(isoDate)) || closedDates.includes(isoDate);
}

const isDateDisabled = ({ year, month, day }: DatePickerDate) =>
  isOfficeClosed(formatISO(new Date(year, month, day), { representation: 'date' }));
    <DatePicker
      label="Appointment date"
      helperText="Must be a business day within 60 days from today"
      required
      min={formatISO(minDate.value, { representation: 'date' })}
      max={formatISO(maxDate.value, { representation: 'date' })}
      isDateDisabled={isDateDisabled}
      errorMessage={errorMessage.value}
      onValidated={(event) => {
        const field = event.target as DatePickerElement;
        if (!field.value && (field.inputElement as HTMLInputElement).value) {
          errorMessage.value = 'Invalid date format';
        } else if (!field.value) {
          errorMessage.value = 'Field is required';
        } else if (field.value < field.min!) {
          errorMessage.value = 'Too early, choose another date';
        } else if (field.value > field.max!) {
          errorMessage.value = 'Too late, choose another date';
        } else if (isOfficeClosed(field.value)) {
          errorMessage.value = 'The office is closed, choose another date';
        } else {
          errorMessage.value = '';
        }
      }}
    />
```

It’s important to ensure an appropriate error message is configured for each constraint violation to provide users with clear feedback.

### <a id="custom-validation"></a>Custom Validation

For more complex cases where constraint validation isn’t enough, Flow and Hilla offer the Binder API that allows you to define custom validation rules. This is useful, for example, when the rule depends on the value of another field. The following example limits the options to Monday through Friday — which the [Disabled Weekdays](#disabled-weekdays) constraint can also express. Try selecting a date that’s on a Sunday or on a Saturday to see a custom validation message.

**Lit** — `date-picker-custom-validation.ts`

```typescript
private binder = new Binder(this, AppointmentModel);

protected override firstUpdated() {
  this.binder.for(this.binder.model.startDate).addValidator({
    message: 'Select a weekday',
    validate: (startDate: string) => {
      const date = new Date(startDate);
      const isWeekday = date.getDay() >= 1 && date.getDay() <= 5;
      return isWeekday;
    },
  });
}

protected override render() {
  return html`
    <vaadin-date-picker
      label="Meeting date"
      helper-text="Mondays – Fridays only"
      ${field(this.binder.model.startDate)}
    ></vaadin-date-picker>
  `;
}
```

**Flow** — `DatePickerCustomValidation.java`

```java
DatePicker datePicker = new DatePicker("Meeting date");
datePicker.setHelperText("Mondays – Fridays only");
datePicker.setI18n(new DatePickerI18n()
        .setBadInputErrorMessage("Invalid date format"));

Binder<Appointment> binder = new Binder<>(Appointment.class);
binder.forField(datePicker).withValidator(localDate -> {
    int dayOfWeek = localDate.getDayOfWeek().getValue();
    boolean validWeekDay = dayOfWeek >= 1 && dayOfWeek <= 5;
    return validWeekDay;
}, "Select a weekday").bind(Appointment::getStartDate,
        Appointment::setStartDate);
```

**React** — `date-picker-custom-validation.tsx`

```tsx
const { model, field } = useForm(AppointmentModel);
const dateField = useFormPart(model.startDate);

useEffect(() => {
  dateField.addValidator({
    message: 'Select a weekday',
    validate: (startDate: string) => {
      const date = new Date(startDate);
      const isWeekday = date.getDay() >= 1 && date.getDay() <= 5;
      return isWeekday;
    },
  });
}, []);

return (
  <DatePicker
    label="Meeting date"
    helperText="Mondays – Fridays only"
    {...field(model.startDate)}
  />
);
```

Binder can also be used to organize data binding and validation for multiple fields, creating forms. You can learn more about Binder from the corresponding [Flow](https://vaadin.com/docs/next/flow/binding-data/components-binder-validation.md) and [Hilla](https://vaadin.com/docs/next/hilla/guides/forms/binder-validation.md) articles.

## <a id="configuring-individual-dates"></a>Configuring Individual Dates (since V25.3)

Individual dates in the calendar overlay can be given metadata: whether the date can be selected, and custom CSS part names for styling it. The metadata comes from a callback that Date Picker invokes for the dates it’s about to render, so the data can be fetched from a backend and can change while the application runs.

Use this when the set of special dates is large, isn’t known in advance, or when particular dates should stand out visually. For a small, fixed set of dates, the simpler [Disabled Dates](#disabled-dates) and [Disabled Weekdays](#disabled-weekdays) constraints are enough, and a continuous range is best expressed with [Min & Max Values](#min-and-max-value).

In the following example, an appointment field marks fully booked dates as unselectable, and highlights the dates that have only a few slots left. Try opening the overlay and navigating between months.

**Lit** — `date-picker-date-metadata.ts`

```typescript
// In a real application, these would query a booking service.
private isFullyBooked = (date: Date) => date.getDate() % 7 === 3;

private isAlmostFull = (date: Date) => date.getDate() % 5 === 0;

private getMetadata = (date: Date): DatePickerDateMetadata | undefined => {
  const isoDate = formatISO(date, { representation: 'date' });
  if (this.isFullyBooked(date)) {
    return { date: isoDate, disabled: true };
  }
  if (this.isAlmostFull(date)) {
    return { date: isoDate, part: 'limited' };
  }
  return undefined;
};

private dateMetadataProvider: DatePickerDateMetadataProvider = ({ start, end }) =>
  eachDayOfInterval({ start: parseISO(start), end: parseISO(end) })
    .map(this.getMetadata)
    .filter((metadata) => metadata !== undefined);

protected override render() {
  return html`
    <vaadin-date-picker
      label="Appointment date"
      helper-text="Highlighted dates are almost full"
      .dateMetadataProvider="${this.dateMetadataProvider}"
    ></vaadin-date-picker>
  `;
}
```

**Flow** — `DatePickerDateMetadata.java`

```java
datePicker.setHelperText("Highlighted dates are almost full");

    DateMetadataProvider provider = DateMetadataProvider.perDate(date -> {
        if (isFullyBooked(date)) {
            return new DateMetadata(date, true);
        }
        if (isAlmostFull(date)) {
            return new DateMetadata(date, "limited");
        }
        return null;
    });
    datePicker.setDateMetadataProvider(provider);

    datePicker.setI18n(new DatePickerI18n()
            .setDisabledDateErrorMessage("That date is fully booked"));

// In a real application, these would query a booking service.
private static boolean isFullyBooked(LocalDate date) {
    return date.getDayOfMonth() % 7 == 3;
}

private static boolean isAlmostFull(LocalDate date) {
    return date.getDayOfMonth() % 5 == 0;
}
```

**React** — `date-picker-date-metadata.tsx`

```tsx
// In a real application, these would query a booking service.
const isFullyBooked = (date: Date) => date.getDate() % 7 === 3;
const isAlmostFull = (date: Date) => date.getDate() % 5 === 0;

function getMetadata(date: Date): DatePickerDateMetadata | undefined {
  const isoDate = formatISO(date, { representation: 'date' });
  if (isFullyBooked(date)) {
    return { date: isoDate, disabled: true };
  }
  if (isAlmostFull(date)) {
    return { date: isoDate, part: 'limited' };
  }
  return undefined;
}

const dateMetadataProvider: DatePickerDateMetadataProvider = ({ start, end }) =>
  eachDayOfInterval({ start: parseISO(start), end: parseISO(end) })
    .map(getMetadata)
    .filter((metadata) => metadata !== undefined);

function Example() {
  return (
    <DatePicker
      label="Appointment date"
      helperText="Highlighted dates are almost full"
      dateMetadataProvider={dateMetadataProvider}
    />
  );
}
```

`date-picker-date-metadata.css`

```css
/* Add this to your global CSS, for example in: */
/* src/main/resources/META-INF/resources/styles.css */

vaadin-month-calendar::part(limited) {
  background: rgba(255, 201, 102, 0.35);
}

vaadin-month-calendar::part(limited):hover {
  background: rgba(255, 201, 102, 0.6);
}
```

### <a id="disabling-individual-dates"></a>Disabling Individual Dates

A date whose metadata marks it as disabled can’t be selected: it’s shown as disabled in the overlay, and typing it into the field makes the field invalid. The same error message is used as for the [Disabled Dates](#disabled-dates) and [Disabled Weekdays](#disabled-weekdays) constraints.

The callback is invoked during server-side validation as well, with the single date being validated, so a disabled date can’t be committed even if the browser was never told about it.

### <a id="custom-part-names"></a>Custom Part Names

Metadata can add one or more CSS part names to a date, which a style sheet can then target with the `::part()` selector. Part names affect only the appearance of a date; they never change whether it can be selected. This is useful for providing additional information without disabling a date.

## <a id="week-numbers"></a>Week Numbers

<!-- vale Vaadin.Abbr = NO -->

Week numbers ([ISO-8601](https://www.iso.org/iso-8601-date-and-time-format.html)) can be enabled in the calendar overlay. This works only when the first day of the week is set to Monday.

<!-- vale Vaadin.Abbr = YES -->

**Lit** — `date-picker-week-numbers.ts`

```typescript
protected override firstUpdated() {
  this.datePicker.i18n = {
    ...this.datePicker.i18n,
    firstDayOfWeek: 1,
  };
}

protected override render() {
  return html`
    <vaadin-date-picker label="Vacation start date" show-week-numbers></vaadin-date-picker>
  `;
}
```

**Flow** — `DatePickerWeekNumbers.java`

```java
datePicker.setWeekNumbersVisible(true);
datePicker
        .setI18n(new DatePicker.DatePickerI18n().setFirstDayOfWeek(1));
```

**React** — `date-picker-week-numbers.tsx`

```tsx
const datePickerRef = useRef<DatePickerElement>(null);

useEffect(() => {
  if (datePickerRef.current) {
    datePickerRef.current.i18n = {
      ...datePickerRef.current.i18n,
      firstDayOfWeek: 1,
    };
  }
}, [datePickerRef.current]);

return <DatePicker label="Vacation start date" showWeekNumbers ref={datePickerRef} />;
```

## <a id="initial-position"></a>Initial Position

Date Picker’s initial position parameter defines which date is focused in the calendar overlay when the overlay is opened. The default, initial position is the selected or current date.

Use this feature to minimize the need for unnecessary navigation or scrolling when the user’s input is expected to be within a certain time. In the following example, click the Date Picker to open the calendar overlay and notice that the highlighted, focused date is in the future (i.e., the last day of the year), instead of the current date.

**Lit** — `date-picker-initial-position.ts`

```html
<vaadin-date-picker
  label="Q4 deadline"
  .initialPosition="${formatISO(this.lastDayOfTheYear, { representation: 'date' })}"
></vaadin-date-picker>
```

**Flow** — `DatePickerInitialPosition.java`

```java
LocalDate lastDayOfYear = LocalDate.now(ZoneId.systemDefault())
        .with(TemporalAdjusters.lastDayOfYear());

datePicker.setInitialPosition(lastDayOfYear);
```

**React** — `date-picker-initial-position.tsx`

```tsx
<DatePicker
  label="Q4 deadline"
  initialPosition={formatISO(lastDayOfTheYear.value, { representation: 'date' })}
/>
```

## <a id="auto-open"></a>Auto Open

The overlay automatically opens when the field is focused with a click or a tap, and when typing a value in the input. This can be prevented to have the overlay only open when the toggle button or the up/down arrow keys are pressed. This behavior isn’t affected, though, on touch devices.

**Lit** — `date-picker-auto-open.ts`

```html
<vaadin-date-picker label="Start date" auto-open-disabled></vaadin-date-picker>
```

**Flow** — `DatePickerAutoOpen.java`

```java
datePicker.setAutoOpen(false);
```

**React** — `date-picker-auto-open.tsx`

```tsx
<DatePicker label="Start date" autoOpenDisabled />
```

## <a id="i18n"></a>Internationalization (i18n)

Date Picker allows localizing text and labels, such as month names and button labels.

**Lit** — `date-picker-internationalization.ts`

```typescript
@query('vaadin-date-picker')
private datePicker!: DatePicker;

protected override firstUpdated() {
  this.datePicker.i18n = {
    ...this.datePicker.i18n,
    monthNames: [
      'Januar',
      'Februar',
      'März',
      'April',
      'Mai',
      'Juni',
      'Juli',
      'August',
      'September',
      'Oktober',
      'November',
      'Dezember',
    ],
    weekdays: ['Sonntag', 'Montag', 'Dienstag', 'Mittwoch', 'Donnerstag', 'Freitag', 'Samstag'],
    weekdaysShort: ['So', 'Mo', 'Di', 'Mi', 'Do', 'Fr', 'Sa'],
    today: 'Heute',
    cancel: 'Abbrechen',
  };
}

protected override render() {
  return html`<vaadin-date-picker label="Sitzungsdatum"></vaadin-date-picker>`;
}
```

**Flow** — `DatePickerInternationalization.java`

```java
DatePicker.DatePickerI18n germanI18n = new DatePicker.DatePickerI18n();
germanI18n.setMonthNames(List.of("Januar", "Februar", "März", "April",
        "Mai", "Juni", "Juli", "August", "September", "Oktober",
        "November", "Dezember"));
germanI18n.setWeekdays(List.of("Sonntag", "Montag", "Dienstag",
        "Mittwoch", "Donnerstag", "Freitag", "Samstag"));
germanI18n.setWeekdaysShort(
        List.of("So", "Mo", "Di", "Mi", "Do", "Fr", "Sa"));
germanI18n.setToday("Heute");
germanI18n.setCancel("Abbrechen");

datePicker.setI18n(germanI18n);
```

**React** — `date-picker-internationalization.tsx`

```tsx
const datePickerRef = useRef<DatePickerElement>(null);

useEffect(() => {
  if (datePickerRef.current) {
    datePickerRef.current.i18n = {
      ...datePickerRef.current.i18n,
      monthNames: [
        'Januar',
        'Februar',
        'März',
        'April',
        'Mai',
        'Juni',
        'Juli',
        'August',
        'September',
        'Oktober',
        'November',
        'Dezember',
      ],
      weekdays: ['Sonntag', 'Montag', 'Dienstag', 'Mittwoch', 'Donnerstag', 'Freitag', 'Samstag'],
      weekdaysShort: ['So', 'Mo', 'Di', 'Mi', 'Do', 'Fr', 'Sa'],
      today: 'Heute',
      cancel: 'Abbrechen',
    };
  }
}, [datePickerRef.current]);

return <DatePicker label="Sitzungsdatum" ref={datePickerRef} />;
```

See also how to [configure a custom date format](https://vaadin.com/docs/next/components/date-picker/date-formats.md).

## <a id="basic-features"></a>Basic Features

The following features, common to most input field components, are supported:

Label

The label is used to identify the input field. It supports plain-text content. In the Lumo theme its length is limited to the width of the field (and truncated with ellipsis), while in the Aura theme labels wrap to multiple lines. [Helpers](#helper) and [Tooltips](#tooltip) can be used to provide additional information that doesn’t fit into the label.

Visible labels are strongly recommended for all input fields. In cases where the built-in label cannot be used, an external element can be associated as the field’s label through the `aria-labelledby` attribute (`setAriaLabelledBy` in Flow). Fields without any visible label should include an invisible label for assistive technologies with the `aria-label` attribute (`setAriaLabel` in Flow).

Helper

Helpers are used to provide additional information that the user may need to enter in the field, such as format requirements or explanations of the field’s purpose below the field.

A [style variant](https://vaadin.com/docs/next/components/date-picker/styling.md#style-variants) is available for rendering the helper above the field.

In addition to plain text, helpers can contain components and HTML elements. However, complex and interactive content is likely to have accessibility issues.

Placeholder

The placeholder is text that’s displayed when the field is empty. Its primary purpose is to provide a short input hint (e.g., the expected format) in situations where a [Helper](#helper) cannot be used.

Placeholders should not be used as a replacement for a visible label. They can be mistaken for a manually entered value. See [Label](#label) for alternatives to the built-in field label.

Tooltip

Tooltips are small text pop-ups displayed on hover, and on keyboard-focus. They can be used to provide additional information about a field. This can be useful in situations where an always visible [Helper](#helper) is not appropriate. Helpers are generally recommended in favor of tooltips, though, as they provide much better discoverability and mobile support. See the [Tooltip](https://vaadin.com/docs/next/components/tooltip.md) documentation for more information.

Clear Button

The clear button — which is displayed when the field is not empty — clears the field’s current value. Although the button itself is not keyboard focusable, the clear action can be taken with the `Esc` key, when the field has focus. The clear button can be especially useful in search and filter fields, where users often need to clear the value. They’re less useful, however, in regular forms.

Prefix

A prefix element — rendered at the start of the field — can be used to display units, icons, and similar visual cues to the field’s purpose or format.

Prefix elements aren’t automatically announced by screen readers as part of the field. Text content (e.g., in a `Span` or `Div`) that should be announced can be associated with the field (since V25.3) using the `accessible-description-ref` attribute (`setAriaDescribedBy` in Flow). Focusable elements (e.g., buttons) are announced separately when focused.

```html
<vaadin-date-picker accessible-description-ref="currency-prefix">
  <span slot="prefix" id="currency-prefix">$</span>
</vaadin-date-picker>
```

```java
Span prefix = new Span("$");
field.setPrefixComponent(prefix);
field.setAriaDescribedBy(prefix);
```

Associated elements are announced in addition to the field’s helper and error message.

External & Invisible Labels (ARIA)

Visible labels are strongly recommended for all input fields. In situations where the built-in label cannot be used, an external element can be associated as the field’s label through its element `id`. Fields without any visible label should be provided an invisible label for assistive technologies like screen readers.

```html
<!-- Associates external element as label: -->
<label id="external-label">This is the label</label>
<vaadin-date-picker accessible-name-ref="external-label">...

<!-- Invisible label for screen readers: -->
<vaadin-date-picker accessible-name="This is the label">...
```

```java
// Associates external element as label:
NativeLabel label = new NativeLabel("This is the label");
label.setId("external-label");
field.setAriaLabelledBy("external-label");

// Invisible label for screen readers:
field.setAriaLabel("This is the label");
```

**Lit** — `date-picker-basic-features.ts`

```typescript
<vaadin-date-picker
  label="Label"
  helper-text="Helper text"
  placeholder="Placeholder"
  clear-button-visible
>
  <vaadin-tooltip slot="tooltip" text="Tooltip text"></vaadin-tooltip>
  <vaadin-icon slot="prefix" icon="vaadin:vaadin-h"></vaadin-icon>
</vaadin-date-picker>
```

**Flow** — `DatePickerBasicFeatures.java`

```java
DatePicker field = new DatePicker();
field.setLabel("Label");
field.setHelperText("Helper text");
field.setPlaceholder("Placeholder");
field.setTooltipText("Tooltip text");
field.setClearButtonVisible(true);
field.setPrefixComponent(VaadinIcon.VAADIN_H.create());
```

**React** — `date-picker-basic-features.tsx`

```tsx
<DatePicker label="Label" helperText="Helper text" placeholder="Placeholder" clearButtonVisible>
  <Tooltip slot="tooltip" text="Tooltip text" />
  <Icon slot="prefix" icon="vaadin:vaadin-h" />
</DatePicker>
```

## <a id="read-only-disabled"></a>Read-Only & Disabled

Fields used to display values should be set to `read-only` mode to prevent editing. Read-only fields are focusable and visible to screen readers. They can display tooltips. Their values can be selected and copied.

Fields that are currently unavailable should be `disabled`. The reduced contrast of disabled fields makes them inappropriate for displaying information. They can’t be focused or display tooltips. They’re invisible to screen readers, and their values cannot be selected and copied.

Disabled fields can be useful in situations where they can become enabled based on some user action. Consider hiding fields entirely if there’s nothing the user can do to make them editable.

**Lit** — `date-picker-readonly-and-disabled.ts`

```typescript
<vaadin-date-picker readonly label="Read-only" value="2020-06-12"></vaadin-date-picker>

<vaadin-date-picker disabled label="Disabled"></vaadin-date-picker>
```

**Flow** — `DatePickerReadonlyAndDisabled.java`

```java
DatePicker readonlyField = new DatePicker();
readonlyField.setReadOnly(true);
readonlyField.setLabel("Read-only");
readonlyField.setValue(LocalDate.of(2020, 6, 12));

DatePicker disabledField = new DatePicker();
disabledField.setEnabled(false);
disabledField.setLabel("Disabled");
```

**React** — `date-picker-readonly-and-disabled.tsx`

```tsx
<DatePicker readonly label="Read-only" value="2020-06-12" />

<DatePicker disabled label="Disabled" />
```

## <a id="usage-patterns"></a>Usage Patterns

### <a id="date-range"></a>Date Range

You can create a date range picker using the Date Picker twice. Imagine the following example is for an airline ticket booking page. It’s asking the user for the date they want to depart and when they want to return. Try it: select a departure date, and then proceed to select a return date. Notice how dates prior to the departure date you chose are now disabled.

**Lit** — `date-picker-date-range.ts`

```html
<vaadin-horizontal-layout theme="spacing">
  <vaadin-date-picker
    label="Departure date"
    .max="${this.returnDate}"
    @value-changed="${(event: DatePickerValueChangedEvent) => {
      this.departureDate = event.detail.value;
    }}"
  ></vaadin-date-picker>
  <vaadin-date-picker
    label="Return date"
    .min="${this.departureDate}"
    @value-changed="${(event: DatePickerValueChangedEvent) => {
      this.returnDate = event.detail.value;
    }}"
  ></vaadin-date-picker>
</vaadin-horizontal-layout>
```

**Flow** — `DatePickerDateRange.java`

```java
DatePicker departureDate = new DatePicker("Departure date");
DatePicker returnDate = new DatePicker("Return date");
departureDate
        .addValueChangeListener(e -> returnDate.setMin(e.getValue()));
returnDate.addValueChangeListener(
        e -> departureDate.setMax(e.getValue()));
```

**React** — `date-picker-date-range.tsx`

```tsx
<HorizontalLayout theme="spacing">
  <DatePicker
    label="Departure date"
    max={returnDate.value}
    onValueChanged={(event) => {
      departureDate.value = event.detail.value;
    }}
  />

  <DatePicker
    label="Return date"
    min={departureDate.value}
    onValueChanged={(event) => {
      returnDate.value = event.detail.value;
    }}
  />
</HorizontalLayout>
```

To disable the days before the start date in the end date picker, you need to handle the selection in the start date picker and change the range in the end date picker.

## <a id="best-practices"></a>Best Practices

### <a id="picking-vs-typing"></a>Picking vs. Typing

The calendar overlay is useful when the user needs to choose a day that’s close to the current date or when information such as day of the week, week number, valid dates, and so on can aid in choosing the best option.

For far off dates (i.e., years ago or years from now) and for known dates (i.e., holidays and birthdays), typing the date in the input field can be faster and easier. Because of this, it’s important to verify that the user can enter dates according to their locale.

Instead of a Date Picker, you can use individual input fields (i.e., day, month, and year) to improve usability on small touch devices. In the following example, focus the year field and start to type a year. As you enter each digit, notice the list of choices narrows.

**Lit** — `date-picker-individual-input-fields.ts`

```typescript
@state()
selectedYear: number | undefined;

@state()
selectedMonth: string | undefined;

@state()
selectedDay: number | undefined;

@state()
selectableDays: number[] = [];

private handleYearChange(e: ComboBoxSelectedItemChangedEvent<number>) {
  this.selectedYear = e.detail.value!;
  this.selectedMonth = undefined;
  this.selectedDay = undefined;
  this.selectableDays = [];
}

private handleMonthChange(e: ComboBoxSelectedItemChangedEvent<string>) {
  this.selectedMonth = e.detail.value!;
  this.selectedDay = undefined;

  if (!this.selectedYear || !this.selectedMonth) {
    this.selectableDays = [];
    return;
  }

  const startOfMonth = new Date(this.selectedYear, this.months.indexOf(this.selectedMonth), 1);
  const lengthOfMonth = getDaysInMonth(startOfMonth);

  this.selectableDays = Array.from({ length: lengthOfMonth }, (_, k) => k + 1);
}

private handleDayChange(e: ComboBoxSelectedItemChangedEvent<number>) {
  this.selectedDay = e.detail.value!;
}

protected override render() {
  return html`
    <vaadin-horizontal-layout theme="spacing">
      <vaadin-combo-box
        label="Year"
        style="width: 7em;"
        .items="${this.years}"
        .selectedItem="${this.selectedYear}"
        @selected-item-changed="${this.handleYearChange}"
      ></vaadin-combo-box>
      <vaadin-combo-box
        label="Month"
        style="width: 10em;"
        .items="${this.months}"
        .selectedItem="${this.selectedMonth}"
        .disabled="${!this.selectedYear}"
        @selected-item-changed="${this.handleMonthChange}"
      ></vaadin-combo-box>
      <vaadin-combo-box
        label="Day"
        style="width: 6em;"
        .items="${this.selectableDays}"
        .selectedItem="${this.selectedDay}"
        .disabled="${!this.selectedYear || !this.selectedMonth}"
        @selected-item-changed="${this.handleDayChange}"
      ></vaadin-combo-box>
    </vaadin-horizontal-layout>
  `;
}
```

**Flow** — `DatePickerIndividualInputFields.java`

```java
public DatePickerIndividualInputFields() {
    LocalDate now = LocalDate.now(ZoneId.systemDefault());

    List<Integer> selectableYears = IntStream
            .range(now.getYear() - 99, now.getYear() + 1).boxed().toList();

    yearPicker = new ComboBox<>("Year", selectableYears);
    yearPicker.setWidth(7, Unit.EM);
    yearPicker.addValueChangeListener(e -> {
        updateMonthPicker();
        updateDayPicker();
    });

    monthPicker = new ComboBox<>("Month", Month.values());
    monthPicker.setItemLabelGenerator(
            m -> m.getDisplayName(TextStyle.FULL, Locale.getDefault()));
    monthPicker.setWidth(10, Unit.EM);
    monthPicker.addValueChangeListener(e -> {
        updateDayPicker();
    });
    monthPicker.setEnabled(false);

    dayPicker = new ComboBox<>("Day");
    dayPicker.setWidth(6, Unit.EM);
    dayPicker.setEnabled(false);

    add(new HorizontalLayout(yearPicker, monthPicker, dayPicker));
}

private void updateMonthPicker() {
    if (yearPicker.getValue() == null) {
        monthPicker.setValue(null);
        monthPicker.setEnabled(false);
        return;
    }

    monthPicker.setValue(null);
    monthPicker.setEnabled(true);
}

private void updateDayPicker() {
    if (yearPicker.getValue() == null || monthPicker.getValue() == null) {
        dayPicker.setValue(null);
        dayPicker.setEnabled(false);
        return;
    }

    dayPicker.setValue(null);
    dayPicker.setEnabled(true);

    LocalDate startOfMonth = LocalDate.of(yearPicker.getValue(),
            monthPicker.getValue(), 1);
    int lengthOfMonth = startOfMonth.lengthOfMonth();

    dayPicker.setItems(
            IntStream.range(1, lengthOfMonth + 1).boxed().toList());
}
```

**React** — `date-picker-individual-input-fields.tsx`

```tsx
<HorizontalLayout theme="spacing">
  <ComboBox
    label="Year"
    style={{ width: '7em' }}
    items={years.value}
    value={String(selectedYear.value)}
    onValueChanged={(e) => {
      selectedYear.value = parseInt(e.detail.value);
      selectedMonth.value = undefined;
      selectedDay.value = undefined;
    }}
  />

  <ComboBox
    label="Month"
    style={{ width: '10em' }}
    items={months.value}
    value={selectedMonth.value}
    disabled={!selectedYear.value}
    onValueChanged={(e) => {
      selectedMonth.value = e.detail.value;
      selectedDay.value = undefined;
    }}
  />

  <ComboBox
    label="Day"
    style={{ width: '6em' }}
    items={selectableDays.value}
    value={String(selectedDay.value)}
    disabled={!selectedYear.value || !selectedMonth.value}
    onValueChanged={(e) => {
      const day = Number.parseInt(e.detail.value);
      selectedDay.value = !Number.isNaN(day) ? day : undefined;
    }}
  />
</HorizontalLayout>
```

> **Note: Not Production-Ready**
>
> The previous example is only a prototype implementation to demonstrate the concept. It isn’t ready for production use.

### <a id="show-the-date-format"></a>Show the Date Format

Use a placeholder or helper to show how the input should be formatted. For example, "12/6/2020" represents different dates for Americans and Europeans. For most Americans, it’s a date in December. For many Europeans, it’s a date in June.

**Lit** — `date-picker-date-format-indicator.ts`

```html
<vaadin-date-picker
  label="Start date"
  placeholder="DD/MM/YYYY"
  helper-text="Format: DD/MM/YYYY"
></vaadin-date-picker>
```

**Flow** — `DatePickerDateFormatIndicator.java`

```java
datePicker.setPlaceholder("DD.MM.YYYY");
datePicker.setHelperText("Format: DD.MM.YYYY");
```

**React** — `date-picker-date-format-indicator.tsx`

```tsx
<DatePicker label="Start date" placeholder="DD/MM/YYYY" helperText="Format: DD/MM/YYYY" />
```

Helpers are preferable to placeholders, as they’re always visible. Fields with placeholders are also less noticeable than empty fields, so they are susceptible to being skipped. Use placeholders when space is limited, for example when Date Picker is used as a filter in a data grid header.

## <a id="related-components"></a>Related Components

| Component                                                                       | Usage Recommendation                                   |
| ------------------------------------------------------------------------------- | ------------------------------------------------------ |
| [Time Picker](https://vaadin.com/docs/next/components/time-picker.md)           | Input field for entering or selecting a specific time. |
| [Date Time Picker](https://vaadin.com/docs/next/components/date-time-picker.md) | Input field for selecting both a date and a time.      |

`26595CB7-1A81-4EE1-B94C-948E889C3027`
