# React Date Range Picker mit Presets und Bottom Sheet

> Ein React Date Range Picker für Reports und Buchungen: Presets, zwei Monate, Apply und Cancel am Desktop, ein Bottom Sheet am Handy und Wochen nach Locale.

- Autor: [Serban Rusu](https://wingo-ui.com/blog/authors/serban), Founder of Wingo UI
- Veröffentlicht: 13. Okt. 2026
- Kategorie: [Komponenten-Guides](https://wingo-ui.com/de/blog/category/components)
- Lesezeit: 9 Min.
- Kanonische URL: https://wingo-ui.com/de/blog/react-date-range-picker
- Englische Fassung: https://wingo-ui.com/blog/react-date-range-picker

## Kurz gesagt

Ein guter React Date Range Picker für Reports zeigt am Desktop Presets wie „Last 7 days“ neben zwei Monaten, hebt den Bereich schon beim Wählen des Endes als Vorschau hervor und ändert den Wert erst bei Apply, damit ein halb gewählter Zeitraum nie deine Abfrage erreicht. Auf dem Handy öffnet er ein Bottom Sheet mit einem Monat, Preset-Chips und Apply über dem Home-Indikator, und überall richtet sich der erste Wochentag nach der Locale. Der Date Range Picker von Wingo UI erledigt all das in einer Komponente, mit minDays, maxDays und gesperrten Tagen für Buchungen.

Ein React Date Range Picker steht über fast jedem Report, jeder Analytics-Seite und jedem Buchungsformular, und dabei schleichen sich leicht kleine Fehler ein. Typische Probleme: Der Report lädt nach dem ersten Klick neu, weil sich der Zeitraum geändert hat. Es gibt keine Presets, also klickt jeder durch die Monate bis zum letzten Quartal. Das Popover ist auf dem Handy ein gequetschtes Raster aus zwei Monaten, und für jemanden in Berlin beginnt die Woche am Sonntag. Dieser Guide zeigt, was ein Picker für Reports braucht, welche Props zählen und wie der [Date Range Picker](https://wingo-ui.com/components/date-range-picker) von Wingo UI Desktop, Handys und Locales behandelt.

## Was sollte ein React Date Range Picker für Reports können?

Fünf Dinge, grob danach sortiert, wie oft ihr Fehlen auffällt:

1. **Presets zuerst.** Die meisten Zeiträume in Reports sind relativ: die letzten 7 Tage, dieser Monat, das letzte Quartal. Eine Liste neben dem Kalender macht aus einem Dutzend Klicks einen.
2. **Zwei Monate am Desktop.** Zeiträume gehen oft über eine Monatsgrenze, und zwei Monate nebeneinander zeigen beide Enden, ohne zu blättern.
3. **Ein Entwurf, dann Apply.** Der erste Klick setzt den Start, der Bereich folgt dem Zeiger als Vorschau, und der zweite Klick setzt das Ende. Nichts landet in deinem State, bevor jemand Apply drückt. Cancel, Escape oder ein Klick daneben verwerfen den Entwurf.
4. **Ein Layout fürs Handy.** Ein Monat in voller Breite in einem Bottom Sheet, mit Apply in Reichweite des Daumens.
5. **Wochen und Labels nach Locale.** Der erste Wochentag, die Monatsnamen und der Text des Zeitraums folgen der Locale deiner Nutzer.

So schneiden die üblichen Optionen ab, Stand Oktober 2026:

| Option | Presets | Apply / Cancel | Layout fürs Handy |
|---|---|---|---|
| shadcn/ui [Range-Picker-Beispiel](https://ui.shadcn.com/docs/components/date-picker) | nicht gezeigt | nicht gezeigt | nicht gezeigt |
| [react-date-range](https://github.com/hypeserver/react-date-range) 2.0.1 | ja (`staticRanges`) | nicht erwähnt | auf der TODO-Liste |
| [MUI X](https://mui.com/x/introduction/licensing/) Date Range Picker | ja | ja | ja |
| Date Range Picker von Wingo UI | ja | ja | Bottom Sheet |

Das Beispiel von shadcn/ui ist kostenlos und MIT-lizenziert, und du setzt es selbst aus Popover und Calendar zusammen. Auf der [Calendar-Seite](https://ui.shadcn.com/docs/components/calendar) gibt es zwar ein Presets-Beispiel, aber es wählt einzelne Tage (Today, Tomorrow, In a week). Presets für Zeiträume schreibst du also weiterhin selbst. Laut README wird react-date-range nicht mehr gepflegt, das GitHub-Repository ist archiviert, und das letzte npm-Release stammt von April 2024. Die Lizenzseite von MUI ordnet den Date Range Picker dem kostenpflichtigen Pro-Plan zu. Der Picker von Wingo UI ist eine Pro-Komponente, deren Quellcode in dein Projekt kopiert wird.

Wenn du schon MUI nutzt, ist dessen Range Picker eine solide Wahl. Wenn du shadcn/ui nutzt und nur am Desktop einen einfachen Zeitraum brauchst, ist das Beispiel für den shadcn date range picker kostenlos und klein. Der Rest dieses Guides dreht sich um den Report-Fall.

## Wie sieht der Date Range Picker von Wingo UI aus?

Wer nach „react date range picker tailwind“ sucht, sollte Pakete mit eigenem Stylesheet meiden: react-date-range verlangt, dass du ein Haupt-Stylesheet und ein Theme-Stylesheet importierst und sie dann überschreibst. Der Picker von Wingo UI ist mit Klassen aus Tailwind CSS v4 gestylt, auf denselben semantischen Tokens wie der Rest deiner App, und folgt deshalb deinem Theme im Light und Dark Mode. Die Demo unten ist ein Umsatzreport. Öffne das Feld Period, probier ein Preset, wähl dann zwei Tage von Hand und schau, wie die Zusammenfassung unter dem Kalender die Tage zählt, bevor du Apply drückst.

> Live-Demo (Date Range Picker): Öffne das Feld Period, wähl Last 30 days oder zwei Tage von Hand und drück Apply: Die Summen ändern sich erst bei Apply. Drückst du stattdessen Escape, ändert sich nichts. Probier sie unter [Date Range Picker](https://wingo-ui.com/components/date-range-picker) aus und installier sie mit `npx wingo-ui@latest add date-range-picker`.

Für einen Report reichen ein Wert, ein Setter und `max` auf heute, damit niemand einen Zeitraum in der Zukunft wählt:

```tsx
"use client";

import { useState } from "react";
import { DateRangePicker, type DateRange } from "@/components/ui/date-range-picker";

export function SalesReportFilter() {
  const [range, setRange] = useState<DateRange>({ from: null, to: null });

  return (
    <DateRangePicker
      label="Zeitraum"
      locale="en-US"
      placeholder="Zeitraum wählen"
      max={new Date()}
      value={range}
      onValueChange={setRange}
    />
  );
}
```

`DateRange` ist `{ from: Date | null; to: Date | null }`. `onValueChange` feuert bei Apply, beim Leeren oder, mit `confirm={false}`, sobald beide Enden gewählt sind. Es feuert nie mit einem halb gewählten Entwurf, du kannst deine Abfrage also direkt darin starten.

## Wie baust du einen React Date Range Picker mit Presets?

`presets` ist standardmäßig `true` und zeigt Last 7 days, Last 30 days, This month, Last month, This quarter, Last quarter und This year. Für einen React Date Range Picker mit Presets nach deinen eigenen Regeln übergibst du eine Liste. Eingebaute Schlüssel und eigene Zeiträume lassen sich frei mischen, und ein eigener `value` darf eine Funktion sein. Dann wird er beim Öffnen des Panels berechnet statt beim Laden der Seite:

```tsx
import { addDays, nextSaturday, startOfDay } from "date-fns";
import { DateRangePicker } from "@/components/ui/date-range-picker";

export function CampaignPeriod() {
  return (
    <DateRangePicker
      label="Kampagne"
      locale="en-US"
      presets={[
        "last7Days",
        "lastMonth",
        "yearToDate",
        {
          label: "Nächste 30 Tage",
          value: () => {
            const today = startOfDay(new Date());
            return { from: today, to: addDays(today, 29) };
          },
        },
        {
          label: "Dieses Wochenende",
          value: () => {
            const saturday = nextSaturday(startOfDay(new Date()));
            return { from: saturday, to: addDays(saturday, 1) };
          },
        },
      ]}
      presetLabels={{ yearToDate: "Seit Jahresbeginn" }}
    />
  );
}
```

Die übrigen eingebauten Schlüssel sind `today`, `yesterday`, `last90Days`, `thisWeek`, `lastWeek`, `thisMonth`, `thisQuarter` und `lastYear`. Presets, die über `min` oder `max` hinausgehen, werden passend gekürzt. Presets, die ganz außerhalb liegen oder länger als `maxDays` sind, werden deaktiviert statt ausgeblendet, damit die Liste nie springt.

## Welche Props zählen für Buchungen und lange Zeiträume?

Reports und Buchungen brauchen unterschiedliche Regeln. Dieselbe Komponente deckt beide ab:

- **`minDays` und `maxDays`** zählen beide Enden mit. Ein Mindestaufenthalt von zwei Nächten ist `minDays={3}`. Enden, die die Regel verletzen würden, sind beim Wählen des Endes nicht verfügbar, und Presets, die länger als `maxDays` sind, werden deaktiviert.
- **`disabled`** nimmt `true`, um das ganze Feld zu sperren, oder einen Matcher für Tage: ein `Date`, eine Liste, `{ from, to }`, `{ before }`, `{ after }`, `{ dayOfWeek: [0, 6] }` oder eine Funktion. Gebuchte Nächte sind eine Liste von Daten.
- **`excludeDisabled`** (Standard `true`) verhindert, dass ein Zeitraum über einen gesperrten Tag reicht. Genau das braucht eine Buchung.
- **`labels.nights`** stellt die Zusammenfassung von Tagen auf Nächte um. Aus einem Aufenthalt über 4 Tage werden so 3 Nächte.
- **`numberOfMonths`** ist standardmäßig 2 und kann 1 oder 3 sein. Auf dem Handy ist es immer einer.

```tsx
"use client";

import { useState } from "react";
import { addDays, startOfDay } from "date-fns";
import { DateRangePicker, type DateRange } from "@/components/ui/date-range-picker";

const today = startOfDay(new Date());
const booked = [addDays(today, 10), addDays(today, 11), addDays(today, 16)];
const nights = (n: number) => `${n} ${n === 1 ? "Nacht" : "Nächte"}`;

export function StayPicker() {
  const [stay, setStay] = useState<DateRange>({ from: null, to: null });

  return (
    <DateRangePicker
      label="Anreise bis Abreise"
      locale="en-US"
      placeholder="Wähl deine Nächte"
      min={today}
      disabled={booked}
      minDays={3}
      presets={false}
      numberOfMonths={1}
      description="Mindestens 2 Nächte. Durchgestrichene Nächte sind gebucht."
      labels={{ nights }}
      calendarProps={{ strikeDisabled: true }}
      value={stay}
      onValueChange={setStay}
    />
  );
}
```

## Wie funktioniert der Date Range Picker auf dem Handy?

Beim Thema Date Range Picker mobile scheitern Layouts meist an derselben Stelle: Ein Popover mit zwei Monaten läuft auf einem 390px breiten Screen über oder schrumpft die Tageszellen unter eine bequeme Tippgröße, und es öffnet sich weit weg vom Daumen. Unter 768px öffnet das Feld von Wingo UI stattdessen ein Bottom Sheet. Das Sheet zeigt die Presets als Reihe von Chips, zwei Zellen für Start und Ende (das Ende, das du gerade wählst, ist umrandet), einen Monat, den du per Wischen wechselst, und Apply und Cancel fixiert über dem Home-Indikator. Wer das Sheet nach unten zieht, bricht ab, genau wie Escape am Desktop. In der Standardgröße `md` ist das Feld auf Touchscreens 44px hoch.

`responsive` (Standard `true`) steuert den Wechsel, und `sheetTitle` benennt das Sheet. Brauchst du überall ein Popover, etwa in einem Admin-Tool nur für den Desktop, setz `responsive={false}`. Denselben Wechsel von Popover zu Sheet für jeden Dialog zeigt unser [Tutorial zum responsiven shadcn-Dialog](https://wingo-ui.com/de/blog/shadcn-responsive-dialog-on-mobile), und einen kompletten Report-Screen rund um den Picker findest du im [Next.js-Admin-Dashboard-Tutorial](https://wingo-ui.com/de/blog/nextjs-admin-dashboard-tutorial).

## Wie funktionieren Wochen und Labels nach Locale?

`locale` bestimmt Monats- und Wochentagsnamen, Ziffern, den Text im Feld und den ersten Wochentag. Lässt du `weekStartsOn` auf dem Standard `-1`, folgt die Woche der Locale: Sonntag für `en-US`, Montag für `en-GB` oder `ro-RO`. Die Komponente liest die Wocheninfo aus `Intl.Locale`, wo der Browser sie anbietet, und fällt sonst auf eine eingebaute Tabelle zurück. Mit `weekStartsOn={1}` erzwingst du Montag, egal bei welcher Locale.

Der Text des Zeitraums nutzt `formatRange` von `Intl.DateTimeFormat`. Der 1. bis 30. Oktober erscheint in `en-US` also als „Oct 1 – 30, 2026“, ohne dass du einen Formatter schreibst. Die Teile änderst du mit `displayFormat`, zum Beispiel `{ day: "numeric", month: "short" }` für ein Buchungs-Widget, das nie über einen Jahreswechsel geht.

Jeder Text im Panel ist ebenfalls ein Prop: `labels.apply`, `labels.cancel`, `labels.clear`, `labels.start`, `labels.end` und `labels.duration` für die Zusammenfassung.

Der zugrunde liegende [Calendar](https://wingo-ui.com/components/calendar) funktioniert auch allein mit `mode="range"`, wenn du das Raster direkt auf der Seite statt in einem Feld willst. In dieser Demo ist die Locale `en-GB`, die Woche beginnt also am Montag.

> Live-Demo (Calendar): Wähl im eingebetteten Calendar im Range-Modus einen Start und ein Ende. Mit der Locale en-GB beginnt jede Woche am Montag. Probier sie unter [Calendar](https://wingo-ui.com/components/calendar) aus und installier sie mit `npx wingo-ui@latest add calendar`.

## Lässt sich der Date Range Picker per Tastatur bedienen?

Ja. Enter, Leertaste oder ArrowDown auf dem Feld öffnen das Panel und setzen den Fokus in den Kalender. Die Pfeiltasten bewegen dich um einen Tag oder eine Woche, Home und End springen an Anfang und Ende der Woche, PageUp und PageDown wechseln den Monat (mit Shift das Jahr), und Enter wählt erst den Start und dann das Ende. Mit Tab erreichst du Cancel und Apply, mit Shift+Tab die Presets, zwischen denen du mit den Pfeiltasten wechselst. Ctrl+Enter, auf dem Mac Cmd+Enter, übernimmt von überall im Panel, und Escape bricht ab und gibt den Fokus ans Feld zurück.

Mit `label`, `description` und `error` bekommt das Feld denselben Aufbau wie jedes andere Eingabefeld von Wingo UI: ein verknüpftes Label, `aria-describedby` für den Hilfetext und `aria-invalid` mit einer animierten Fehlermeldung.

## Welche Stolpersteine tauchen in Produktion auf?

- **Zeitzonen in Formularen.** `date.toISOString()` rechnet in UTC um. Mitternacht am 13. Oktober in Berlin wird so zu `2026-10-12T22:00:00.000Z`, und dein Server liest den falschen Tag. Sende lokale Daten. Mit `name="period"` rendert der Picker die versteckten Felder `period.from` und `period.to` als lokale ISO-Daten wie `2026-10-13`.
- **Inklusive Enden.** „Last 7 days“ endet heute und schließt heute ein. Fragst du eine Datenbank mit einer Timestamp-Spalte ab, vergleich mit dem Beginn des Tages nach `to`, sonst verlierst du alles, was am letzten Tag nach Mitternacht passiert ist.
- **Presets, die beim Import berechnet werden.** Ein `new Date()` auf Modulebene veraltet in einem Tab, der über Nacht offen bleibt. Nutze für eigene Presets einen Funktionswert.
- **Eine Abfrage bei jedem Klick.** Ohne Apply-Schritt startet schon die Wahl des Starts deinen Report mit einem offenen Zeitraum. Lass `confirm` bei allem an, was langsamer ist als ein Filter im Client.

Hier die Formularversion, bei der eine Server Action die versteckten Felder liest. Sie prüft das Format beider Daten, denn eine Anfrage kann dein Formular umgehen, und behält sie als Kalenderdaten, damit die Zeitzone des Servers sie nie verschiebt:

```tsx
import { DateRangePicker } from "@/components/ui/date-range-picker";
import { Button } from "@/components/ui/button";

const ISO_DATE = /^\d{4}-\d{2}-\d{2}$/;

async function exportOrders(formData: FormData) {
  "use server";
  const from = String(formData.get("period.from") ?? "");
  const to = String(formData.get("period.to") ?? "");
  if (!ISO_DATE.test(from) || !ISO_DATE.test(to)) return;
  // führ deine Abfrage mit exklusivem Ende aus, z. B. in Postgres:
  // where created_at >= $1::date and created_at < $2::date + 1
  console.log("export orders", { from, to });
}

export function ExportForm() {
  return (
    <form action={exportOrders} className="flex flex-col gap-4">
      <DateRangePicker label="Zeitraum" name="period" locale="en-US" max={new Date()} />
      <Button type="submit">CSV exportieren</Button>
    </form>
  );
}
```

## Wann ist ein einfacher Date Picker die bessere Wahl?

Wenn es um einen einzigen Tag geht: ein Fälligkeitsdatum, einen Geburtstag, ein Lieferfenster. Mit dem [Date Picker](https://wingo-ui.com/components/date-picker) tippt man ein Datum im Format der eigenen Locale oder wählt es im Kalender, mit Presets, Grenzen und optionaler Uhrzeit, und auch er öffnet auf dem Handy ein Sheet. Den Range Picker nimmst du nur, wenn beide Enden zählen. Geht es um eine Uhrzeit, hilft der Guide zum [React Time Picker](https://wingo-ui.com/de/blog/react-time-picker), und für Abrechnungszeiträume oder Berichte nach Monat der Guide zum [React Month Picker](https://wingo-ui.com/de/blog/react-month-picker).

Mehr Deep Dives zu einzelnen Komponenten findest du in den [Komponenten-Guides](https://wingo-ui.com/de/blog/category/components). Wie die Datumsfelder zum Rest eines Formulars passen, zeigt unser Guide zu [React-Formularkomponenten](https://wingo-ui.com/de/blog/react-form-components-guide).

## Wie installierst du ihn?

Führ `npx wingo-ui@latest add date-range-picker` aus. Der Befehl kopiert den Picker mit dem Calendar, dem Drawer für das Sheet auf dem Handy, dem Field und den Datums-Helfern, die er importiert, und installiert die nötigen npm-Pakete wie `date-fns`, `motion` und `@radix-ui/react-popover`. Der Date Range Picker gehört zu [Wingo UI Pro](https://wingo-ui.com/de/pricing), melde dich also zuerst mit `npx wingo-ui@latest login` an.

## Komponenten in diesem Artikel

- [Date Range Picker](https://wingo-ui.com/components/date-range-picker) (Pro): A React date range picker for reports and bookings: two months with presets on desktop, a one-month bottom sheet on phones. Installation: `npx wingo-ui@latest add date-range-picker`
- [Calendar](https://wingo-ui.com/components/calendar) (Pro): A React calendar to pick one day, several days or a range, with disabled days, event dots, several months and a quick month / year jump. Installation: `npx wingo-ui@latest add calendar`
- [Date Picker](https://wingo-ui.com/components/date-picker) (Pro): A React date picker: type a date in your locale or pick it from a calendar, with presets, limits, optional time and a phone sheet. Installation: `npx wingo-ui@latest add date-picker`

## Häufige Fragen

### Wie füge ich einem React Date Range Picker Presets hinzu?

Zeig neben dem Kalender eine Liste von Zeiträumen und füll beide Enden, sobald jemand einen Eintrag wählt. Im Date Range Picker von Wingo UI ist presets standardmäßig true (Last 7 days, Last 30 days, This month, Last month, This quarter, Last quarter, This year), oder du übergibst eine Liste, die eingebaute Schlüssel wie "yearToDate" mit eigenen { label, value }-Einträgen mischt.

### Hat shadcn/ui einen Date Range Picker?

Auf der Date-Picker-Seite gibt es ein Range-Picker-Beispiel, das aus Popover und Calendar zusammengesetzt ist; die Seite sagt selbst, dass es keine DatePicker-Root-Komponente gibt. Stand Oktober 2026 zeigt die Seite keine Presets, keinen Schritt mit Apply und Cancel und keinen Drawer fürs Handy, das baust du also selbst.

### Soll ein Date Range Picker sofort übernehmen oder einen Apply-Button haben?

Nimm Apply, wenn jede Änderung eine teure Abfrage auslöst, etwa einen Report oder einen Export. Dann lassen sich beide Enden und ein Preset wählen, ohne drei Ladevorgänge auszulösen. Bei einem günstigen clientseitigen Filter übernimmst du sofort; in Wingo UI ist das confirm={false}.

### Wie sollte ein Date Range Picker auf dem Handy funktionieren?

Öffne ein Bottom Sheet statt eines Popovers, zeig einen Monat in voller Breite mit großen Tageszellen und pack die Presets in eine scrollbare Reihe von Chips. Zeig Zellen für Start und Ende, damit klar ist, welches Ende gerade gewählt wird, und fixier Apply und Cancel über dem Home-Indikator.

### Wie sende ich den Zeitraum in einem Formular ohne Zeitzonenfehler?

Sende lokale Kalenderdaten wie 2026-10-13, nie toISOString(), denn das rechnet in UTC um und kann den Tag verschieben. Mit name="period" rendert der Picker von Wingo UI die versteckten Felder period.from und period.to mit lokalen ISO-Daten.

---

Quelle: https://wingo-ui.com/de/blog/react-date-range-picker
