CriteriaDates

PreviousNext

A Datepicker wrapper for booking search flows, with an optional price-status legend, section card chrome, and a child slot for reinsurance messages.

Select your dates

Price legend

Best price

Last availabilities

Not available

Nov 2026

Dec 2026

Jan 2027

Feb 2027

Mar 2027

Apr 2027

May 2027

Jun 2027

Jul 2027

Aug 2027

Sep 2027

Oct 2027

New! Flexible booking now available.

'use client';
import { useState } from 'react';
import type { DateRange } from 'react-day-picker';

import { DeviceProvider } from '@/ui/contexts/devices/Device';
import { CriteriaDates, CriteriaDatesReinsurance } from '@/ui/criteria/CriteriaDates';

export function CriteriaDatesDemo() {
  const [selected, setSelected] = useState<DateRange | undefined>();

  const labels = {
    seeNextMonth: 'Next month',
    seePreviousMonth: 'Previous month',
    bestPrice: 'Best price',
    lastAvailabilities: 'Last availabilities',
    notAvailable: 'Not available',
    priceLegend: 'Price legend',
  };

  return (
    <DeviceProvider device="all">
      <div className="w-[340px] space-y-24">
        <CriteriaDates
          labels={labels}
          showPriceLegend
          selected={selected}
          onSelect={setSelected}
          title="Select your dates"
        >
          <CriteriaDatesReinsurance
            labels={{ disclaimer: 'New! Flexible booking now available.' }}
          />
        </CriteriaDates>
        {selected?.from && (
          <p className="text-b4">
            From: {selected.from.toLocaleDateString()}
            {selected.to ? ` — To: ${selected.to.toLocaleDateString()}` : ''}
          </p>
        )}
      </div>
    </DeviceProvider>
  );
}

Installation

npx shadcn@latest add https://develop.trident-ui.pro.clubmed/r/criteria-dates.json

Dependencies

CriteriaDates wraps Datepicker internally, which reads the current breakpoint via useDevice. Place DeviceProvider above it in the tree:

import { DeviceProvider } from '@/ui/contexts/devices/Device';
 
<DeviceProvider device="all">
  <CriteriaDates ... />
</DeviceProvider>

Install the peer dependencies used by Datepicker:

npm install react-day-picker date-fns

See the Datepicker documentation for the full DeviceProvider setup guide.

Usage

Basic range selection

CriteriaDates is controlled the same way as Datepicker — you own the DateRange state and pass it back via onSelect. The labels object must always contain seeNextMonth and seePreviousMonth for the accessible navigation button labels.

'use client';
 
import { useState } from 'react';
import type { DateRange } from 'react-day-picker';
import { DeviceProvider } from '@/ui/contexts/devices/Device';
import { CriteriaDates } from '@/ui/criteria/CriteriaDates';
 
export function Component() {
  const [selected, setSelected] = useState<DateRange | undefined>();
 
  return (
    <DeviceProvider device="all">
      <CriteriaDates
        fromMonth={new Date()}
        labels={{
          seeNextMonth: 'Next month',
          seePreviousMonth: 'Previous month',
        }}
        selected={selected}
        onSelect={setSelected}
        title="Select your dates"
      />
    </DeviceProvider>
  );
}

With price legend

Pass showPriceLegend and include the extra legend keys in labels to render a colour-coded legend above the calendar. The type system enforces that the extra keys must be present when the legend is enabled — TypeScript will raise an error if showPriceLegend is true but the price keys are missing from labels.

Select your dates

Price legend

Best price

Last availabilities

Not available

Nov 2026

Dec 2026

Jan 2027

Feb 2027

Mar 2027

Apr 2027

May 2027

Jun 2027

Jul 2027

Aug 2027

Sep 2027

Oct 2027

New! Flexible booking now available.

'use client';
import { useState } from 'react';
import type { DateRange } from 'react-day-picker';

import { DeviceProvider } from '@/ui/contexts/devices/Device';
import { CriteriaDates, CriteriaDatesReinsurance } from '@/ui/criteria/CriteriaDates';

export function CriteriaDatesDemo() {
  const [selected, setSelected] = useState<DateRange | undefined>();

  const labels = {
    seeNextMonth: 'Next month',
    seePreviousMonth: 'Previous month',
    bestPrice: 'Best price',
    lastAvailabilities: 'Last availabilities',
    notAvailable: 'Not available',
    priceLegend: 'Price legend',
  };

  return (
    <DeviceProvider device="all">
      <div className="w-[340px] space-y-24">
        <CriteriaDates
          labels={labels}
          showPriceLegend
          selected={selected}
          onSelect={setSelected}
          title="Select your dates"
        >
          <CriteriaDatesReinsurance
            labels={{ disclaimer: 'New! Flexible booking now available.' }}
          />
        </CriteriaDates>
        {selected?.from && (
          <p className="text-b4">
            From: {selected.from.toLocaleDateString()}
            {selected.to ? ` — To: ${selected.to.toLocaleDateString()}` : ''}
          </p>
        )}
      </div>
    </DeviceProvider>
  );
}

With reinsurance message

Place <CriteriaDatesReinsurance> as a child of <CriteriaDates> to render a disclaimer below the calendar. The block renders nothing when labels.disclaimer is an empty string, so you can conditionally suppress it by toggling the string value.

import { CriteriaDates, CriteriaDatesReinsurance } from '@/ui/criteria/CriteriaDates';
 
<CriteriaDates
  fromMonth={new Date()}
  labels={{ seeNextMonth: 'Next month', seePreviousMonth: 'Previous month' }}
  selected={selected}
  onSelect={setSelected}
>
  <CriteriaDatesReinsurance
    labels={{ disclaimer: 'New! Flexible booking now available on all resorts.' }}
  />
</CriteriaDates>

With date annotations and disabled days

All Datepicker props are forwarded unchanged, including dates for per-day price and status annotations and disabledDays for blocking unavailable dates. When showPriceLegend is enabled, statusLabels are derived automatically from the legend text — screen readers will announce the status of each annotated day without any extra configuration.

<CriteriaDates
  fromMonth={new Date()}
  labels={{
    seeNextMonth: 'Next month',
    seePreviousMonth: 'Previous month',
    bestPrice: 'Best price',
    lastAvailabilities: 'Last availabilities',
    notAvailable: 'Not available',
    priceLegend: 'Price legend',
  }}
  showPriceLegend
  selected={selected}
  onSelect={setSelected}
  disabledDays={{ before: new Date() }}
  dates={[
    { id: '2025-08-10', price: '890 €',   status: 'green'   },
    { id: '2025-08-15', price: '1 450 €', status: 'orange'  },
    { id: '2025-08-20', price: '720 €',   status: 'outline' },
  ]}
/>

API Reference

CriteriaDates

PropTypeDefaultDescription
labelsCriteriaDatesLabels | CriteriaDatesLabelsWithPriceLegend—Required. Text labels for navigation buttons and, when showPriceLegend is true, the legend items. See CriteriaDatesLabels and CriteriaDatesLabelsWithPriceLegend.
showPriceLegendbooleanfalseWhen true, renders the colour-coded price legend above the calendar. Requires priceLegend, bestPrice, lastAvailabilities, and notAvailable to be present in labels.
titlestringundefinedOptional heading displayed at the top of the section card.
classNamestringundefinedAdditional CSS class on the section card wrapper.
childrenReactNodeundefinedSlot rendered below the calendar. Intended for CriteriaDatesReinsurance.
...datepickerPropsDatepickerProps—All remaining props are forwarded to the inner Datepicker — selected, onSelect, fromMonth, defaultMonth, maxNumberOfMonths, dates, disabledDays, locale, formatDurationsLabel.

CriteriaDatesLabels

Base labels required for all configurations.

FieldTypeDescription
seeNextMonthstringAccessible label for the next-month navigation button. Supports {currentMonth} and {nextMonth} token interpolation.
seePreviousMonthstringAccessible label for the previous-month navigation button. Supports {previousMonth} and {currentMonth} token interpolation.
availableSeasonstring?Optional label for the season-availability legend item (outline square). Omit to hide the swatch.

CriteriaDatesLabelsWithPriceLegend

Extends CriteriaDatesLabels with the following fields. Must be provided when showPriceLegend is true.

FieldTypeDescription
priceLegendstringTitle of the legend region; also its aria-label.
bestPricestringLabel for the green (best price) swatch.
lastAvailabilitiesstringLabel for the orange (high-demand) swatch.
notAvailablestringLabel for the diagonal-strikethrough (unavailable) swatch.
availableSeasonstring?Inherited from CriteriaDatesLabels.

CriteriaDatesReinsurance

A companion component rendered as a child of CriteriaDates.

PropTypeDefaultDescription
labels.disclaimerstring—Required. The disclaimer text. The component renders nothing when this string is empty.
classNamestringundefinedAdditional CSS class on the paragraph element.

Price legend

When showPriceLegend is enabled a legend is rendered above the calendar with colour-coded swatches that mirror the Datepicker day statuses:

SwatchDay statusMeaning
Green pill (bg-green)greenBest price of the period
Orange rounded square (bg-orange)orangeHigh-demand / last available dates
Strikethrough square (diagonal-line)Disabled dayDate not available
Outline square (white + black border)outlineSeason availability (optional)

The statusLabels passed to the inner Datepicker are derived automatically from the legend labels — no extra configuration needed for screen-reader announcements.

Notes

Labels union type

The labels prop is a union of CriteriaDatesLabels and CriteriaDatesLabelsWithPriceLegend. TypeScript raises a type error if you pass showPriceLegend={true} without the legend keys. Provide the full object to keep the type narrow, even if you conditionally hide the legend at runtime.

All Datepicker props are forwarded

CriteriaDates spreads any prop it does not recognise directly onto the inner Datepicker. labels.seeNextMonth and labels.seePreviousMonth are extracted and passed to Datepicker's own labels prop automatically — do not duplicate them.

Controlled-only

Like the underlying Datepicker, CriteriaDates holds no selection state. Always pass both selected and onSelect, otherwise clicks have no visible effect.

Client component

CriteriaDates uses React hooks internally. Add 'use client' at the top of any file that renders it in a Next.js App Router project.

Accessibility

  • The price legend region uses role="region" with aria-labelledby pointing to its title element, so screen readers announce the landmark when entering it.
  • Legend items are grouped in role="list" with role="listitem" on each entry.
  • Navigation button labels support month-name token interpolation, producing a fully spoken sentence (e.g. "See August 2025 and September 2025").
  • When showPriceLegend is enabled, statusLabels are derived from the legend text and injected as visually-hidden <span class="sr-only"> elements inside annotated day buttons, so each day's status is announced by screen readers.
  • The reinsurance disclaimer is a plain <p> element; no additional ARIA attributes are required.