tetra-ui Logotetra ui

Native Date Select

A native date and time selection input built on Expo UI DatePicker and DateTimePicker, with optional form-styled input and platform-aware presentation.

Native Date Select wraps Expo UI's SwiftUI DatePicker on iOS and Jetpack Compose DateTimePicker on Android.

native-date-select

Installation

npx shadcn@latest add @tetra-ui/native-date-select

Usage

Modes

Set mode on the root to choose what the picker selects: date (default), time, or datetime. Always include NativeDateSelectContent.

import {
  NativeDateSelect,
  NativeDateSelectContent,
} from "@/components/ui/native-date-select";
const [date, setDate] = useState(new Date());

<NativeDateSelect mode="date" value={date} onValueChange={setDate}>
  <NativeDateSelectContent />
</NativeDateSelect>

<NativeDateSelect mode="time" value={date} onValueChange={setDate}>
  <NativeDateSelectContent />
</NativeDateSelect>

<NativeDateSelect mode="datetime" value={date} onValueChange={setDate}>
  <NativeDateSelectContent />
</NativeDateSelect>

Use Content alone to render the picker inline. On iOS, set variant to compact, graphical, or wheel (maps to SwiftUI datePickerStyle). On Android, inline has no combined datetime UI — use the form-styled Input API for mode="datetime" (sequential date then time dialogs).

Form-styled date select

Compose NativeDateSelectTrigger, NativeDateSelectInput, and NativeDateSelectContent like Select. The Trigger opens the picker; Input is display-only; Content owns the sheet/dialog. Content has no props — the sheet title follows the Input placeholder.

Set variant on the input (or root) to choose the iOS presentation:

  • wheel / default (iOS) — Trigger opens a bottom sheet with a wheel DatePicker
  • compact (iOS) — non-pressable input; only the native compact DatePicker is interactive (Trigger optional)
  • Android — Trigger mounts a Material dialog (DatePickerDialog / TimePickerDialog). For datetime, date then time dialogs run sequentially. variant maps to picker/input style on the dialog.

Pass InputAddon children to Input for adornments. On iOS wheel, optionally compose NativeDateSelectSheetFooter and NativeDateSelectSheetConfirm inside Content to require an explicit confirm before committing (dismiss cancels).

import { Button } from "@/components/ui/button";
import {
  NativeDateSelect,
  NativeDateSelectContent,
  NativeDateSelectInput,
  NativeDateSelectSheetConfirm,
  NativeDateSelectSheetFooter,
  NativeDateSelectTrigger,
} from "@/components/ui/native-date-select";
<NativeDateSelect mode="date" value={date} onValueChange={setDate}>
  <NativeDateSelectTrigger asChild>
    <NativeDateSelectInput placeholder="Pick a date" />
  </NativeDateSelectTrigger>
  <NativeDateSelectContent>
    <NativeDateSelectSheetFooter>
      <NativeDateSelectSheetConfirm asChild>
        <Button>Confirm</Button>
      </NativeDateSelectSheetConfirm>
    </NativeDateSelectSheetFooter>
  </NativeDateSelectContent>
</NativeDateSelect>
<NativeDateSelect mode="date" value={date} onValueChange={setDate}>
  <NativeDateSelectInput variant="compact" placeholder="Pick a date" />
  <NativeDateSelectContent />
</NativeDateSelect>

Without a sheet footer, iOS wheel commits the value as it changes.

Use formatValue to customize the displayed label (defaults to Intl.DateTimeFormat by mode):

<NativeDateSelectInput
  formatValue={(d) => d.toLocaleDateString()}
  placeholder="Pick a date"
/>

Custom trigger

Use any pressable as the Trigger child via asChild:

import { Button, ButtonText } from "@/components/ui/button";
import {
  NativeDateSelect,
  NativeDateSelectContent,
  NativeDateSelectTrigger,
} from "@/components/ui/native-date-select";
<NativeDateSelect mode="date" value={date} onValueChange={setDate}>
  <NativeDateSelectTrigger asChild>
    <Button size="sm" variant="secondary">
      <ButtonText>{label ?? "Pick a date"}</ButtonText>
    </Button>
  </NativeDateSelectTrigger>
  <NativeDateSelectContent />
</NativeDateSelect>

Bounds

Constrain selectable dates with minimumDate and maximumDate. On iOS these map to SwiftUI DatePicker range; on Android they map to Compose selectableDates. This limits which dates can be chosen — it is not a dual start/end value.

<NativeDateSelect
  mode="date"
  value={date}
  onValueChange={setDate}
  minimumDate={new Date(2024, 0, 1)}
  maximumDate={new Date(2024, 11, 31)}
>
  <NativeDateSelectTrigger asChild>
    <NativeDateSelectInput placeholder="Pick a date" />
  </NativeDateSelectTrigger>
  <NativeDateSelectContent />
</NativeDateSelect>

Range selection

Expo UI pickers still select a single date. For a start–end range value, compose two pickers and cross-constrain with minimumDate / maximumDate:

const [start, setStart] = useState<Date>();
const [end, setEnd] = useState<Date>();

<NativeDateSelect
  mode="date"
  value={start}
  onValueChange={setStart}
  maximumDate={end}
>
  <NativeDateSelectTrigger asChild>
    <NativeDateSelectInput placeholder="Start date" />
  </NativeDateSelectTrigger>
  <NativeDateSelectContent />
</NativeDateSelect>

<NativeDateSelect
  mode="date"
  value={end}
  onValueChange={setEnd}
  minimumDate={start}
>
  <NativeDateSelectTrigger asChild>
    <NativeDateSelectInput placeholder="End date" />
  </NativeDateSelectTrigger>
  <NativeDateSelectContent />
</NativeDateSelect>

Controlled

Pass value and onValueChange for controlled selection. Optionally control open and onOpenChange for the sheet/dialog:

const [date, setDate] = useState(new Date());
const [open, setOpen] = useState(false);

<NativeDateSelect
  value={date}
  onValueChange={setDate}
  open={open}
  onOpenChange={setOpen}
>
  <NativeDateSelectTrigger asChild>
    <NativeDateSelectInput placeholder="Pick a date" />
  </NativeDateSelectTrigger>
  <NativeDateSelectContent />
</NativeDateSelect>

Disabled

<NativeDateSelect disabled value={date} onValueChange={setDate}>
  <NativeDateSelectTrigger asChild>
    <NativeDateSelectInput placeholder="Pick a date" />
  </NativeDateSelectTrigger>
  <NativeDateSelectContent />
</NativeDateSelect>

API Reference

Built on Expo UI DatePicker (iOS) / DateTimePicker and Material dialogs (Android). Always include NativeDateSelectContent. Content-only renders an inline picker; with Trigger/Input, uses a form-styled input and sheet/dialog.

NativeDateSelect

Root value/open state for the date/time picker.

Prop

Type

NativeDateSelectTrigger

Opens the sheet/dialog. Extends React Native Pressable props. Must be used within NativeDateSelect.

Prop

Type

NativeDateSelectInput

Display-only form-styled input. Compose inside NativeDateSelectTrigger for wheel/Android. iOS compact embeds the native compact DatePicker and does not need a Trigger.

Prop

Type

NativeDateSelectContent

Always required. Owns presentation: inline picker, iOS wheel bottom sheet, or Android Material dialog. Hosts optional sheet footer. No props — sheet title follows the Input placeholder.

NativeDateSelectSheetFooter

Accepts BottomSheetFooter props. When present on iOS, picker changes are drafted until confirm; dismiss cancels.

NativeDateSelectSheetConfirm

Extends React Native Pressable props. Commits the draft date and closes the sheet. Must be used within NativeDateSelect.

Prop

Type

Changelog

2026-08-04 - Trigger and Content composition

Add NativeDateSelectTrigger and NativeDateSelectContent. NativeDateSelectInput is display-only; open the picker via Trigger (or use any pressable with asChild). Sheet footer moves into Content.

2026-08-05 - Mandatory Content and variant

NativeDateSelectContent is required for all usage. Renamed display to variant. Content takes no props (sheet title follows Input placeholder).

On this page