DatePicker 日期选择器
@lark-ui/core
November 2026

已选择:无

import { useState } from "react";
import { DatePicker } from "@lark-ui/core";

/**
 * 选择单个日期。
 */
export function DatePickerSingleDemo() {
  const [date, setDate] = useState<Date | undefined>();

  return (
    <div className="flex flex-col gap-4">
      <DatePicker
        className="p-3"
        mode="single"
        selected={date}
        onChange={(d) => {
          if (d) {
            setDate(d);
          }
        }}
      />
      <p className="text-sm text-lark-subtle">
        已选择:{date ? date.toLocaleDateString() : "无"}
      </p>
    </div>
  );
}

安装

统一导出

import { DatePicker, type DateRange } from "@lark-ui/core";

按需导出

import {
  DatePicker,
  type DateRange,
} from "@lark-ui/core/components/date-picker";

用法

日期选择器(DatePicker)支持三种选择模式:single、multiple 和 range。

import { useState } from "react";
import { DatePicker } from "@lark-ui/core";

export default function Example() {
  const [date, setDate] = useState<Date>();

  return <DatePicker mode="single" selected={date} onChange={setDate} />;
}

示例

单选日期

选择单个日期。这是日期选择器最常见的用法。

November 2026

已选择:无

import { useState } from "react";
import { DatePicker } from "@lark-ui/core";

/**
 * 选择单个日期。
 */
export function DatePickerSingleDemo() {
  const [date, setDate] = useState<Date | undefined>();

  return (
    <div className="flex flex-col gap-4">
      <DatePicker
        className="p-3"
        mode="single"
        selected={date}
        onChange={(d) => {
          if (d) {
            setDate(d);
          }
        }}
      />
      <p className="text-sm text-lark-subtle">
        已选择:{date ? date.toLocaleDateString() : "无"}
      </p>
    </div>
  );
}

多选日期

选择多个独立日期。使用 max 限制最多可选择的数量。

November 2026

已选择:0 个日期

import { useState } from "react";
import { DatePicker } from "@lark-ui/core";

/**
 * 选择多个日期,最多可选 5 个。
 */
export function DatePickerMultipleDemo() {
  const [dates, setDates] = useState<Date[] | undefined>();

  return (
    <div className="flex flex-col gap-4">
      <DatePicker
        className="p-3"
        mode="multiple"
        selected={dates}
        onChange={setDates}
        max={5}
      />
      <p className="text-sm text-lark-subtle">
        已选择:{dates?.length ?? 0} 个日期
      </p>
    </div>
  );
}

日期范围选择

选择一段连续的日期范围。搭配 numberOfMonths={2} 可获得并排视图,效果更好。多月视图会隐藏各月份之外的日期,避免面板之间出现重复日期。设置 showOutsideDays 可以显示这些日期。

November 2026
December 2026

范围:无

import { useState } from "react";
import { DatePicker, DateRange } from "@lark-ui/core";

/**
 * 选择日期范围,同时显示两个月。
 */
export function DatePickerRangeDemo() {
  const [range, setRange] = useState<DateRange | undefined>();

  return (
    <div className="flex flex-col gap-4">
      <DatePicker
        className="p-3"
        mode="range"
        selected={range}
        onChange={setRange}
        numberOfMonths={2}
      />
      <p className="text-sm text-lark-subtle">
        范围:
        {range?.from
          ? `${range.from.toLocaleDateString()} - ${range.to?.toLocaleDateString() ?? "..."}`
          : "无"}
      </p>
    </div>
  );
}

带最小/最大值限制的范围选择

使用 min 和 max 属性限制范围长度(以天/晚为单位)。

November 2026
import { useState } from "react";
import { DatePicker, DateRange } from "@lark-ui/core";

/**
 * 日期范围选择,最短 3 晚、最长 7 晚。
 */
export function DatePickerRangeMinMaxDemo() {
  const [range, setRange] = useState<DateRange | undefined>();

  return (
    <div className="flex flex-col gap-4">
      <DatePicker
        className="p-3"
        mode="range"
        selected={range}
        onChange={setRange}
        min={3}
        max={7}
        footer={<span className="text-xs text-lark-subtle">选择 3-7 晚</span>}
      />
    </div>
  );
}

月份和年份下拉选择

设置 captionLayout="dropdown" 可直接跳转到指定月份和年份。使用 startMonth 和 endMonth 限定年份范围。

November 2026
import { useState } from "react";
import { DatePicker } from "@lark-ui/core";

/**
 * 通过月份和年份下拉框直接跳转到指定日期。
 */
export function DatePickerDropdownCaptionDemo() {
  const [date, setDate] = useState<Date | undefined>();

  return (
    <DatePicker
      className="p-3"
      mode="single"
      selected={date}
      onChange={setDate}
      captionLayout="dropdown"
      startMonth={new Date(2020, 0)}
      endMonth={new Date(2030, 11)}
    />
  );
}

搭配气泡卡片使用

与 Popover 组件组合,可以创建一个下拉式日期选择器。

import { useState } from "react";
import { DatePicker, Popover, Button } from "@lark-ui/core";
import { CalendarDotsIcon } from "@phosphor-icons/react";

/**
 * 与 Popover 组合的日期选择器,呈现下拉式交互。
 */
export function DatePickerPopoverDemo() {
  const [date, setDate] = useState<Date | undefined>();

  return (
    <Popover>
      <Popover.Trigger
        render={<Button variant="outline" icon={CalendarDotsIcon} />}
      >
        {date ? date.toLocaleDateString() : "选择日期"}
      </Popover.Trigger>
      <Popover.Content className="p-3">
        <DatePicker mode="single" selected={date} onChange={setDate} />
      </Popover.Content>
    </Popover>
  );
}

搭配气泡卡片选择日期范围

在气泡卡片中展示两个月的日期范围选择器。

import { useState } from "react";
import { DatePicker, Popover, Button, DateRange } from "@lark-ui/core";
import { CalendarDotsIcon } from "@phosphor-icons/react";

/**
 * 与 Popover 组合的日期范围选择器,呈现下拉式交互。
 */
export function DatePickerRangePopoverDemo() {
  const [range, setRange] = useState<DateRange | undefined>();

  const formatRange = () => {
    if (!range?.from) return "选择日期范围";
    if (!range.to) return range.from.toLocaleDateString();
    return `${range.from.toLocaleDateString()} – ${range.to.toLocaleDateString()}`;
  };

  return (
    <Popover>
      <Popover.Trigger
        render={<Button variant="outline" icon={CalendarDotsIcon} />}
      >
        {formatRange()}
      </Popover.Trigger>
      <Popover.Content className="p-3">
        <DatePicker
          mode="range"
          selected={range}
          onChange={setRange}
          numberOfMonths={2}
        />
      </Popover.Content>
    </Popover>
  );
}

带预设的日期范围

将日期选择器与预设选项结合,便于快速选择。

import { useState } from "react";
import { DatePicker, Popover, Button, DateRange } from "@lark-ui/core";
import { CalendarDotsIcon } from "@phosphor-icons/react";

/**
 * 带预设选项(位于 Popover 中)的日期范围选择器。
 */
export function DatePickerRangeWithPresetsDemo() {
  const [range, setRange] = useState<DateRange | undefined>();
  const [month, setMonth] = useState<Date>(new Date());

  const today = new Date();

  const presets = [
    {
      label: "今天",
      range: { from: today, to: today },
    },
    {
      label: "最近 7 天",
      range: {
        from: new Date(today.getTime() - 6 * 24 * 60 * 60 * 1000),
        to: today,
      },
    },
    {
      label: "最近 30 天",
      range: {
        from: new Date(today.getTime() - 29 * 24 * 60 * 60 * 1000),
        to: today,
      },
    },
    {
      label: "最近 90 天",
      range: {
        from: new Date(today.getTime() - 89 * 24 * 60 * 60 * 1000),
        to: today,
      },
    },
    {
      label: "本月",
      range: {
        from: new Date(today.getFullYear(), today.getMonth(), 1),
        to: new Date(today.getFullYear(), today.getMonth() + 1, 0),
      },
    },
    {
      label: "上个月",
      range: {
        from: new Date(today.getFullYear(), today.getMonth() - 1, 1),
        to: new Date(today.getFullYear(), today.getMonth(), 0),
      },
    },
  ];

  const handlePresetClick = (preset: { range: DateRange }) => {
    setRange(preset.range);
    // Navigate calendar to show the start of the range
    if (preset.range.from) {
      setMonth(preset.range.from);
    }
  };

  const isPresetActive = (preset: { range: DateRange }) => {
    if (!range?.from || !range?.to || !preset.range.from || !preset.range.to)
      return false;
    // Compare dates only (ignore time)
    const sameFrom =
      range.from.toDateString() === preset.range.from.toDateString();
    const sameTo = range.to.toDateString() === preset.range.to.toDateString();
    return sameFrom && sameTo;
  };

  const formatRange = () => {
    if (!range?.from) return "选择日期范围";
    if (!range.to) return range.from.toLocaleDateString();
    return `${range.from.toLocaleDateString()} – ${range.to.toLocaleDateString()}`;
  };

  return (
    <Popover>
      <Popover.Trigger
        render={<Button variant="outline" icon={CalendarDotsIcon} />}
      >
        {formatRange()}
      </Popover.Trigger>
      <Popover.Content className="p-0">
        <div className="flex">
          <div className="flex flex-col gap-1 border-r border-lark-hairline p-2 text-sm">
            {presets.map((preset) => {
              const isActive = isPresetActive(preset);
              return (
                <button
                  key={preset.label}
                  type="button"
                  onClick={() => handlePresetClick(preset)}
                  className={`rounded-md px-3 py-1.5 text-left whitespace-nowrap ${
                    isActive
                      ? "bg-lark-contrast text-lark-inverse"
                      : "text-lark-subtle hover:bg-lark-control"
                  }`}
                >
                  {preset.label}
                </button>
              );
            })}
          </div>
          <div className="p-3">
            <DatePicker
              mode="range"
              selected={range}
              onChange={setRange}
              month={month}
              onMonthChange={setMonth}
              numberOfMonths={2}
            />
          </div>
        </div>
      </Popover.Content>
    </Popover>
  );
}

禁用日期与用量限制

使用 disabled 属性让某些日期不可选择,使用 footer 显示用量信息。

November 2026
import { useState } from "react";
import { DatePicker } from "@lark-ui/core";

/**
 * 含禁用日期的日期选择器,页脚展示使用次数限制。
 */
export function DatePickerDisabledWithFooterDemo() {
  const [dates, setDates] = useState<Date[] | undefined>();
  const today = new Date();

  // Example: some dates are already used/unavailable
  const unavailableDates = [
    new Date(today.getFullYear(), today.getMonth(), 5),
    new Date(today.getFullYear(), today.getMonth(), 12),
    new Date(today.getFullYear(), today.getMonth(), 18),
    new Date(today.getFullYear(), today.getMonth(), 25),
  ];

  const selectedCount = dates?.length ?? 0;
  const maxDays = 5;

  return (
    <DatePicker
      className="p-3"
      mode="multiple"
      selected={dates}
      onChange={setDates}
      max={maxDays}
      disabled={unavailableDates}
      fixedWeeks
      footer={
        <p className="w-full pt-2 text-xs text-lark-subtle">
          已选择 {selectedCount}/{maxDays} 天。灰色日期不可用。
        </p>
      }
    />
  );
}

完整的气泡卡片示例

下面是一个完整示例,演示如何将 DatePicker 与 Popover 组合使用:

import { useState } from "react";
import { DatePicker, Popover, Button } from "@lark-ui/core";
import { CalendarDotsIcon } from "@phosphor-icons/react";

export function DatePickerDropdown() {
  const [date, setDate] = useState<Date>();

  return (
    <Popover>
      <Popover.Trigger
        render={<Button variant="outline" icon={CalendarDotsIcon} />}
      >
        {date ? date.toLocaleDateString() : "Pick a date"}
      </Popover.Trigger>
      <Popover.Content className="p-3">
        <DatePicker
          mode="single"
          selected={date}
          onChange={(d) => setDate(d)}
        />
      </Popover.Content>
    </Popover>
  );
}

API 参考

DatePicker 会将所有属性透传给

react-day-picker

。主要属性包括:

  • mode — "single" | "multiple" | "range" — 选择模式(必填)

  • selected — 当前选中的日期
  • onChange — 选择变化时的回调
  • numberOfMonths — 展示的月份数量
  • disabled — 不可选择的日期
  • min / max — 最小/最大选择限制
  • footer — 渲染在日历下方的内容
  • locale — 用于国际化的 date-fns locale
  • className — 额外的 CSS 类名

完整的 API 请参阅

react-day-picker 文档

。

与 react-day-picker 的差异

为了与其他 Lark 表单组件保持一致,DatePicker 使用 onChange 而不是 react-day-picker 的 onSelect。完整的类型推断能力得以保留——TypeScript 会根据 mode 属性正确收窄回调签名。