diff --git a/frontend/src/metabase/components/form/FormWidget.jsx b/frontend/src/metabase/components/form/FormWidget.jsx
index de916c0accb7cf8f40c97ab8304ff3f78e5306f6..9ce32a2f4b3a382c014ad5591c32347abd0ca87c 100644
--- a/frontend/src/metabase/components/form/FormWidget.jsx
+++ b/frontend/src/metabase/components/form/FormWidget.jsx
@@ -5,6 +5,7 @@ import { PLUGIN_FORM_WIDGETS } from "metabase/plugins";
 
 import FormInfoWidget from "./widgets/FormInfoWidget";
 import FormInputWidget from "./widgets/FormInputWidget";
+import FormDateWidget from "./widgets/FormDateWidget";
 import FormEmailWidget from "./widgets/FormEmailWidget";
 import FormTextAreaWidget from "./widgets/FormTextAreaWidget";
 import FormPasswordWidget from "./widgets/FormPasswordWidget";
@@ -23,6 +24,7 @@ import FormTextFileWidget from "./widgets/FormTextFileWidget";
 const WIDGETS = {
   info: FormInfoWidget,
   input: FormInputWidget,
+  date: FormDateWidget,
   email: FormEmailWidget,
   text: FormTextAreaWidget,
   checkbox: FormCheckBoxWidget,
diff --git a/frontend/src/metabase/components/form/widgets/FormDateWidget/FormDateWidget.tsx b/frontend/src/metabase/components/form/widgets/FormDateWidget/FormDateWidget.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..f821e7991d1ce3865bb99dd8f28cff0a8d3c6a41
--- /dev/null
+++ b/frontend/src/metabase/components/form/widgets/FormDateWidget/FormDateWidget.tsx
@@ -0,0 +1,57 @@
+import React, { forwardRef, Ref, useCallback, useMemo } from "react";
+import moment, { Moment } from "moment";
+import DateInput from "metabase/core/components/DateInput";
+import { FormField } from "./types";
+
+const DATE_FORMAT = "YYYY-MM-DD";
+
+export interface FormDateWidgetProps {
+  field: FormField;
+  placeholder?: string;
+  readOnly?: boolean;
+  autoFocus?: boolean;
+  tabIndex?: number;
+}
+
+const FormDateWidget = forwardRef(function FormDateWidget(
+  { field, placeholder, readOnly, autoFocus, tabIndex }: FormDateWidgetProps,
+  ref: Ref<HTMLDivElement>,
+) {
+  const value = useMemo(() => {
+    return field.value ? moment(field.value, DATE_FORMAT) : undefined;
+  }, [field]);
+
+  const handleChange = useCallback(
+    (newValue?: Moment) => {
+      field.onChange?.(newValue?.format(DATE_FORMAT));
+    },
+    [field],
+  );
+
+  const handleFocus = useCallback(() => {
+    field.onFocus?.(field.value);
+  }, [field]);
+
+  const handleBlur = useCallback(() => {
+    field.onBlur?.(field.value);
+  }, [field]);
+
+  return (
+    <DateInput
+      ref={ref}
+      value={value}
+      placeholder={placeholder}
+      readOnly={readOnly}
+      autoFocus={autoFocus}
+      error={field.visited && !field.active && field.error != null}
+      fullWidth
+      tabIndex={tabIndex}
+      aria-labelledby={`${field.name}-label`}
+      onChange={handleChange}
+      onFocus={handleFocus}
+      onBlur={handleBlur}
+    />
+  );
+});
+
+export default FormDateWidget;
diff --git a/frontend/src/metabase/components/form/widgets/FormDateWidget/index.ts b/frontend/src/metabase/components/form/widgets/FormDateWidget/index.ts
new file mode 100644
index 0000000000000000000000000000000000000000..df005321703a871099a81157f815c47ca9ab6a82
--- /dev/null
+++ b/frontend/src/metabase/components/form/widgets/FormDateWidget/index.ts
@@ -0,0 +1 @@
+export { default } from "./FormDateWidget";
diff --git a/frontend/src/metabase/components/form/widgets/FormDateWidget/types.ts b/frontend/src/metabase/components/form/widgets/FormDateWidget/types.ts
new file mode 100644
index 0000000000000000000000000000000000000000..26db3ee8eae383ad843ecea3beaae1560b28d4e3
--- /dev/null
+++ b/frontend/src/metabase/components/form/widgets/FormDateWidget/types.ts
@@ -0,0 +1,10 @@
+export interface FormField {
+  name: string;
+  value?: string;
+  visited?: boolean;
+  active?: boolean;
+  error?: string;
+  onChange?: (value?: string) => void;
+  onFocus?: (value?: string) => void;
+  onBlur?: (value?: string) => void;
+}
diff --git a/frontend/src/metabase/components/form/widgets/FormInputWidget.jsx b/frontend/src/metabase/components/form/widgets/FormInputWidget.jsx
index 5371b9b2e594e15fa6a92cc99e76fd96554cfed4..f2a44a3ee4f22a215cc1cf80f47a8595ce9d51dd 100644
--- a/frontend/src/metabase/components/form/widgets/FormInputWidget.jsx
+++ b/frontend/src/metabase/components/form/widgets/FormInputWidget.jsx
@@ -1,7 +1,7 @@
 import React, { forwardRef } from "react";
 import PropTypes from "prop-types";
 import { formDomOnlyProps } from "metabase/lib/redux";
-import Input from "metabase/components/Input/Input";
+import Input from "metabase/core/components/Input";
 
 // Important: do NOT use this as an input of type="file"
 // For file inputs, See component FormTextFileWidget.tsx
diff --git a/frontend/src/metabase/core/components/DateInput/DateInput.stories.tsx b/frontend/src/metabase/core/components/DateInput/DateInput.stories.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..d8e16fac32214229a308b185dc9d071d3a4c76d8
--- /dev/null
+++ b/frontend/src/metabase/core/components/DateInput/DateInput.stories.tsx
@@ -0,0 +1,21 @@
+import React, { useState } from "react";
+import { Moment } from "moment";
+import { ComponentStory } from "@storybook/react";
+import DateInput from "./DateInput";
+
+export default {
+  title: "Core/DateInput",
+  component: DateInput,
+};
+
+const Template: ComponentStory<typeof DateInput> = args => {
+  const [value, setValue] = useState<Moment>();
+  return <DateInput {...args} value={value} onChange={setValue} />;
+};
+
+export const Default = Template.bind({});
+
+export const WithError = Template.bind({});
+WithError.args = {
+  error: true,
+};
diff --git a/frontend/src/metabase/core/components/DateInput/DateInput.styled.tsx b/frontend/src/metabase/core/components/DateInput/DateInput.styled.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..fd8e5c533bdd76bcc06fffdd013af3a3c015699b
--- /dev/null
+++ b/frontend/src/metabase/core/components/DateInput/DateInput.styled.tsx
@@ -0,0 +1,35 @@
+import styled from "@emotion/styled";
+import { color, darken } from "metabase/lib/colors";
+import IconButtonWrapper from "metabase/components/IconButtonWrapper";
+
+export interface InputRootProps {
+  readOnly?: boolean;
+  error?: boolean;
+  fullWidth?: boolean;
+}
+
+export const InputRoot = styled.div<InputRootProps>`
+  display: inline-flex;
+  align-items: center;
+  width: ${props => (props.fullWidth ? "100%" : "")};
+  border: 1px solid
+    ${props => (props.error ? color("error") : darken("border", 0.1))};
+  border-radius: 4px;
+  background-color: ${props =>
+    props.readOnly ? color("bg-light") : color("bg-white")};
+
+  &:focus-within {
+    border-color: ${color("brand")};
+    transition: border 300ms ease-in-out;
+  }
+`;
+
+export const InputIconButton = styled(IconButtonWrapper)`
+  margin: 0 0.75rem;
+`;
+
+export const CalendarFooter = styled.div`
+  display: flex;
+  justify-content: flex-end;
+  padding: 0.75rem;
+`;
diff --git a/frontend/src/metabase/core/components/DateInput/DateInput.tsx b/frontend/src/metabase/core/components/DateInput/DateInput.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..e49af36256e09a45f66b56441fffa140efb8796c
--- /dev/null
+++ b/frontend/src/metabase/core/components/DateInput/DateInput.tsx
@@ -0,0 +1,165 @@
+import React, {
+  ChangeEvent,
+  FocusEvent,
+  forwardRef,
+  InputHTMLAttributes,
+  Ref,
+  useCallback,
+  useMemo,
+  useState,
+} from "react";
+import moment, { Moment } from "moment";
+import { t } from "ttag";
+import Button from "metabase/core/components/Button";
+import Input from "metabase/core/components/Input";
+import Icon from "metabase/components/Icon";
+import Calendar from "metabase/components/Calendar";
+import Tooltip from "metabase/components/Tooltip";
+import TippyPopover from "metabase/components/Popover/TippyPopover";
+import { CalendarFooter, InputIconButton, InputRoot } from "./DateInput.styled";
+
+const INPUT_FORMAT = "MM/DD/YYYY";
+const CALENDAR_FORMAT = "YYYY-MM-DD";
+
+export type DateInputAttributes = Omit<
+  InputHTMLAttributes<HTMLDivElement>,
+  "value" | "onChange"
+>;
+
+export interface DateInputProps extends DateInputAttributes {
+  inputRef?: Ref<HTMLInputElement>;
+  value?: Moment;
+  error?: boolean;
+  fullWidth?: boolean;
+  onChange?: (value: Moment | undefined) => void;
+}
+
+const DateInput = forwardRef(function DateInput(
+  {
+    className,
+    style,
+    inputRef,
+    value,
+    placeholder,
+    readOnly,
+    disabled,
+    error,
+    fullWidth,
+    onFocus,
+    onBlur,
+    onChange,
+    ...props
+  }: DateInputProps,
+  ref: Ref<HTMLDivElement>,
+) {
+  const now = useMemo(() => moment(), []);
+  const nowText = useMemo(() => now.format(INPUT_FORMAT), [now]);
+  const valueText = useMemo(() => value?.format(INPUT_FORMAT) ?? "", [value]);
+  const [inputText, setInputText] = useState(valueText);
+  const [isOpened, setIsOpened] = useState(false);
+  const [isFocused, setIsFocused] = useState(false);
+
+  const handleInputFocus = useCallback(
+    (event: FocusEvent<HTMLInputElement>) => {
+      setIsFocused(true);
+      setInputText(valueText);
+      onFocus?.(event);
+    },
+    [valueText, onFocus],
+  );
+
+  const handleInputBlur = useCallback(
+    (event: FocusEvent<HTMLInputElement>) => {
+      setIsFocused(false);
+      onBlur?.(event);
+    },
+    [onBlur],
+  );
+
+  const handleInputChange = useCallback(
+    (event: ChangeEvent<HTMLInputElement>) => {
+      const newText = event.target.value;
+      const newValue = moment(newText, INPUT_FORMAT);
+      setInputText(newText);
+
+      if (newValue.isValid()) {
+        onChange?.(newValue);
+      } else {
+        onChange?.(undefined);
+      }
+    },
+    [onChange],
+  );
+
+  const handlePopoverOpen = useCallback(() => {
+    setIsOpened(true);
+  }, []);
+
+  const handlePopoverClose = useCallback(() => {
+    setIsOpened(false);
+  }, []);
+
+  const handleCalendarChange = useCallback(
+    (valueText: string) => {
+      const value = moment(valueText, CALENDAR_FORMAT);
+      onChange?.(value);
+    },
+    [onChange],
+  );
+
+  return (
+    <TippyPopover
+      trigger="manual"
+      placement="bottom-start"
+      visible={isOpened}
+      interactive
+      content={
+        <div>
+          <Calendar
+            selected={value}
+            initial={value ?? now}
+            onChange={handleCalendarChange}
+            isRangePicker={false}
+          />
+          <CalendarFooter>
+            <Button primary onClick={handlePopoverClose}>{t`Save`}</Button>
+          </CalendarFooter>
+        </div>
+      }
+      onHide={handlePopoverClose}
+    >
+      <InputRoot
+        ref={ref}
+        className={className}
+        style={style}
+        readOnly={readOnly}
+        error={error}
+        fullWidth={fullWidth}
+      >
+        <Input
+          {...props}
+          ref={inputRef}
+          value={isFocused ? inputText : valueText}
+          placeholder={nowText}
+          readOnly={readOnly}
+          disabled={disabled}
+          error={error}
+          fullWidth={fullWidth}
+          borderless
+          onFocus={handleInputFocus}
+          onBlur={handleInputBlur}
+          onChange={handleInputChange}
+        />
+        {!readOnly && !disabled && (
+          <Tooltip tooltip={t`Show calendar`}>
+            <InputIconButton tabIndex={-1} onClick={handlePopoverOpen}>
+              <Icon name="calendar" />
+            </InputIconButton>
+          </Tooltip>
+        )}
+      </InputRoot>
+    </TippyPopover>
+  );
+});
+
+export default DateInput;
diff --git a/frontend/src/metabase/core/components/DateInput/DateInput.unit.spec.tsx b/frontend/src/metabase/core/components/DateInput/DateInput.unit.spec.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..14570740a3e1179a468169c1e21f11b04b791520
--- /dev/null
+++ b/frontend/src/metabase/core/components/DateInput/DateInput.unit.spec.tsx
@@ -0,0 +1,45 @@
+import React, { useState } from "react";
+import { Moment } from "moment";
+import { render, screen } from "@testing-library/react";
+import userEvent from "@testing-library/user-event";
+import DateInput, { DateInputProps } from "./DateInput";
+
+const DateInputTest = (props: DateInputProps) => {
+  const [value, setValue] = useState<Moment>();
+  return <DateInput {...props} value={value} onChange={setValue} />;
+};
+
+describe("DateInput", () => {
+  beforeEach(() => {
+    jest.useFakeTimers();
+    jest.setSystemTime(new Date(2015, 0, 10));
+  });
+
+  afterEach(() => {
+    jest.useRealTimers();
+  });
+
+  it("should set a label", () => {
+    render(<DateInputTest aria-label="Date" />);
+
+    expect(screen.getByLabelText("Date")).toBeInTheDocument();
+  });
+
+  it("should set a placeholder", () => {
+    render(<DateInputTest />);
+
+    expect(screen.getByPlaceholderText("01/10/2015")).toBeInTheDocument();
+  });
+
+  it("should accept text input", () => {
+    const onChange = jest.fn();
+
+    render(<DateInputTest onChange={onChange} />);
+
+    userEvent.type(screen.getByRole("textbox"), "10/20/21");
+    expect(screen.getByDisplayValue("10/20/21")).toBeInTheDocument();
+
+    userEvent.tab();
+    expect(screen.getByDisplayValue("10/20/2021")).toBeInTheDocument();
+  });
+});
diff --git a/frontend/src/metabase/core/components/DateInput/index.ts b/frontend/src/metabase/core/components/DateInput/index.ts
new file mode 100644
index 0000000000000000000000000000000000000000..362d5a3898338e320011d95c2be7c515f58f4ef9
--- /dev/null
+++ b/frontend/src/metabase/core/components/DateInput/index.ts
@@ -0,0 +1 @@
+export { default } from "./DateInput";
diff --git a/frontend/src/metabase/components/Input/Input.styled.tsx b/frontend/src/metabase/core/components/Input/Input.styled.tsx
similarity index 89%
rename from frontend/src/metabase/components/Input/Input.styled.tsx
rename to frontend/src/metabase/core/components/Input/Input.styled.tsx
index 9babb46bbf0a7b932fe4fc97223f1be8bcf0098d..693ec9a725bee27fe29dbf1145f800b9ebade4c2 100644
--- a/frontend/src/metabase/components/Input/Input.styled.tsx
+++ b/frontend/src/metabase/core/components/Input/Input.styled.tsx
@@ -6,6 +6,7 @@ export interface InputProps {
   hasError?: boolean;
   hasTooltip?: boolean;
   fullWidth?: boolean;
+  borderless?: boolean;
 }
 
 export const InputRoot = styled.div<InputProps>`
@@ -20,10 +21,10 @@ export const InputField = styled.input<InputProps>`
   font-weight: 700;
   font-size: 1rem;
   color: ${color("text-dark")};
-  background-color: ${props => color(props.readOnly ? "bg-light" : "bg-white")};
   padding: 0.75rem;
   border: 1px solid ${darken("border", 0.1)};
   border-radius: 4px;
+  background-color: ${props => color(props.readOnly ? "bg-light" : "bg-white")};
   outline: none;
 
   &:focus {
@@ -48,6 +49,14 @@ export const InputField = styled.input<InputProps>`
     css`
       width: 100%;
     `}
+
+  ${props =>
+    props.borderless &&
+    css`
+      border: none;
+      border-radius: 0;
+      background-color: transparent;
+    `};
 `;
 
 export const InputIconContainer = styled.div`
diff --git a/frontend/src/metabase/components/Input/Input.tsx b/frontend/src/metabase/core/components/Input/Input.tsx
similarity index 51%
rename from frontend/src/metabase/components/Input/Input.tsx
rename to frontend/src/metabase/core/components/Input/Input.tsx
index 9b2717a6810d8ae877ad9b66dd7871f367741926..63b53e198c0e6ccd92736e6a2fa1eb4a175dc75b 100644
--- a/frontend/src/metabase/components/Input/Input.tsx
+++ b/frontend/src/metabase/core/components/Input/Input.tsx
@@ -1,43 +1,53 @@
-import React, { forwardRef, InputHTMLAttributes, ReactNode } from "react";
+import React, { forwardRef, InputHTMLAttributes, ReactNode, Ref } from "react";
 import Icon from "metabase/components/Icon";
 import Tooltip from "metabase/components/Tooltip";
 import { InputField, InputIconContainer, InputRoot } from "./Input.styled";
 
 export interface InputProps extends InputHTMLAttributes<HTMLInputElement> {
-  className?: string;
+  inputRef?: Ref<HTMLInputElement>;
   error?: boolean;
   fullWidth?: boolean;
+  borderless?: boolean;
   helperText?: ReactNode;
 }
 
-const Input = forwardRef<HTMLInputElement, InputProps>(function Input(
-  { className, error, fullWidth, helperText, ...rest }: InputProps,
-  ref,
+const Input = forwardRef(function Input(
+  {
+    className,
+    style,
+    inputRef,
+    error,
+    fullWidth,
+    borderless,
+    helperText,
+    ...rest
+  }: InputProps,
+  ref: Ref<HTMLDivElement>,
 ) {
   return (
-    <InputRoot className={className} fullWidth={fullWidth}>
+    <InputRoot
+      ref={ref}
+      className={className}
+      style={style}
+      fullWidth={fullWidth}
+    >
       <InputField
         {...rest}
+        ref={inputRef}
         hasError={error}
         hasTooltip={Boolean(helperText)}
         fullWidth={fullWidth}
-        ref={ref}
+        borderless={borderless}
       />
       {helperText && (
         <Tooltip tooltip={helperText} placement="right" offset={[0, 24]}>
-          <InputHelpContent />
+          <InputIconContainer>
+            <Icon name="info" />
+          </InputIconContainer>
         </Tooltip>
       )}
     </InputRoot>
   );
 });
 
-const InputHelpContent = forwardRef(function InputHelpContent(props, ref: any) {
-  return (
-    <InputIconContainer ref={ref}>
-      <Icon name="info" />
-    </InputIconContainer>
-  );
-});
-
 export default Input;
diff --git a/frontend/src/metabase/components/Input/index.ts b/frontend/src/metabase/core/components/Input/index.ts
similarity index 100%
rename from frontend/src/metabase/components/Input/index.ts
rename to frontend/src/metabase/core/components/Input/index.ts
diff --git a/frontend/src/metabase/entities/timeline-events/forms.js b/frontend/src/metabase/entities/timeline-events/forms.js
index bc902b6bb31d3d21ba13f0f51fa71997a33292e7..9712039f268528bb307dc71565b38ade9d7ad0da 100644
--- a/frontend/src/metabase/entities/timeline-events/forms.js
+++ b/frontend/src/metabase/entities/timeline-events/forms.js
@@ -13,7 +13,7 @@ const FORM_FIELDS = [
   {
     name: "timestamp",
     title: t`Date`,
-    placeholder: "2022-01-02",
+    type: "date",
     validate: validate.required(),
   },
   {