diff --git a/web/src/components/ui/DatePicker.tsx b/web/src/components/ui/DatePicker.tsx
new file mode 100644
index 00000000..44993228
--- /dev/null
+++ b/web/src/components/ui/DatePicker.tsx
@@ -0,0 +1,100 @@
+// DatePicker — the house date field: a themed trigger + the portaled Calendar
+// popover, replacing the native (and its browser-chrome
+// calendar) everywhere in the dashboard. Value is a "yyyy-MM-dd" string (the
+// same shape the native input used) so it's a drop-in for existing filters.
+
+import React from "react";
+import { format, parse, isValid } from "date-fns";
+import { CalendarIcon, XIcon } from "lucide-react";
+
+import { cn } from "@/lib/utils";
+import Calendar from "@/components/app/Calendar";
+
+// Parse "yyyy-MM-dd" as a LOCAL date (avoids the UTC shift new Date("yyyy-MM-dd")
+// introduces, which can land the picker a day off in negative-offset timezones).
+function parseISODate(v: string): Date | null {
+ if (!v) return null;
+ const d = parse(v, "yyyy-MM-dd", new Date());
+ return isValid(d) ? d : null;
+}
+
+export function DatePicker({
+ value,
+ onChange,
+ placeholder = "Any date",
+ className,
+ clearable = true,
+ disabled = false,
+ display = "MMM d, yyyy",
+}: {
+ /** "yyyy-MM-dd", or "" when unset. */
+ value: string;
+ onChange: (value: string) => void;
+ placeholder?: string;
+ className?: string;
+ clearable?: boolean;
+ disabled?: boolean;
+ /** date-fns format for the trigger label. */
+ display?: string;
+}) {
+ const [open, setOpen] = React.useState(false);
+ const wrapRef = React.useRef(null);
+ const selected = parseISODate(value);
+
+ // Close on outside click. BUBBLE phase (not capture): the Calendar panel is
+ // portaled to and stops mousedown bubbling, so its own clicks (month
+ // nav, day select) never reach here and don't dismiss it mid-interaction.
+ React.useEffect(() => {
+ if (!open) return;
+ const onDown = (e: MouseEvent | TouchEvent) => {
+ if (wrapRef.current && !wrapRef.current.contains(e.target as Node)) setOpen(false);
+ };
+ document.addEventListener("mousedown", onDown);
+ document.addEventListener("touchstart", onDown);
+ return () => {
+ document.removeEventListener("mousedown", onDown);
+ document.removeEventListener("touchstart", onDown);
+ };
+ }, [open]);
+
+ return (
+