Repository: streamlit/streamlit
Pull request: Allow `None` as value for `st.date_input`
## Describe your changes

This PR adds support for setting `None` as the value in `st.date_input`. Using `value=None` will render an empty date input that returns `None` as long as the user hasn't specified a date:

<img width="723" alt="image" src="https://github.com/streamlit/streamlit/assets/2852129/1cf90c9a-8ca3-4ed0-b4d0-ef7df4e12614">

If a user has specified a date, the user can clear this value again to reset the input to `None`:

<img width="723" alt="image" src="https://github.com/streamlit/streamlit/assets/2852129/47d34a1b-4da9-4c8f-b103-e4941059845d">

- Spec: https://www.notion.so/snowflake-corp/Spec-v2-b00de8d56f99464693e7ddfdea0674e9

## GitHub Issue Link (if applicable)

- Closes #6719

## Testing Plan

- Unit Tests: ✅
- E2E Tests: ✅

---

**Contribution License Agreement**

By submitting this pull request you agree that all contributions to this project are made under the Apache 2.0 license.


Problem statement:
Data Widgets to start with no initial value
Can all data widgets have no initial selected value?

1. Can a date widget have no default date value on first start / load? It is only after the user selects a date, can it contain a date value. To get around this I have to use a text_input widget instead, and add date validation logic to check if the date is valid in terms of syntax (in addition to other other conditions such as end dates to be greater than start dates...)
2. Can radio buttons start with no value? It is only after a use selects one of the options does the widget have a selected value
etc... To get around this I use a dummy 1st option to default as no selection.
3. ...

Use case: data from widgets can be stored back into the database only if the user explicitly selects a value, not otherwise. 

There is no way for me to otherwise tell if today's date, in a date widget, was explicitly selected by the end user or is a result of the default widget value.

Hints from issue discussion:
This seems related to #4645 (the idea of making widgets required so as to conclusively get a value from the user). Certainly the select-type widgets can do this (in effect), but other widgets don't have simple ways to accomplish this. For things like number input or color picker where the default choice might indeed be the user's choice, I've had to rely on a button to get confirmation.
This is currently in work here: https://github.com/streamlit/streamlit/pull/6596. It will allow setting the initial value to `None`, which will result in an empty state.
Thanks @LukasMasuch! (No love for color_picker yet?) 😄 
@MathCatsAnd not yet 😬 Adding an empty state to color_picker is a bit more complicated 
Thanks both, for your prompt responses :-)

Commit message: Merge remote-tracking branch 'remote/develop' into feature/allow-none-for-date-input

Patch to review:
diff --git a/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-disabled_no_date[dark_theme-chromium].png b/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-disabled_no_date[dark_theme-chromium].png
new file mode 100644
index 000000000000..4a0aead2039f
Binary files /dev/null and b/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-disabled_no_date[dark_theme-chromium].png differ
diff --git a/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-disabled_no_date[dark_theme-firefox].png b/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-disabled_no_date[dark_theme-firefox].png
new file mode 100644
index 000000000000..66baf7aefa96
Binary files /dev/null and b/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-disabled_no_date[dark_theme-firefox].png differ
diff --git a/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-disabled_no_date[dark_theme-webkit].png b/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-disabled_no_date[dark_theme-webkit].png
new file mode 100644
index 000000000000..bea198fc0e4b
Binary files /dev/null and b/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-disabled_no_date[dark_theme-webkit].png differ
diff --git a/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-disabled_no_date[light_theme-chromium].png b/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-disabled_no_date[light_theme-chromium].png
new file mode 100644
index 000000000000..f3c0d1450ea2
Binary files /dev/null and b/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-disabled_no_date[light_theme-chromium].png differ
diff --git a/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-disabled_no_date[light_theme-firefox].png b/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-disabled_no_date[light_theme-firefox].png
new file mode 100644
index 000000000000..ac6635c5bac6
Binary files /dev/null and b/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-disabled_no_date[light_theme-firefox].png differ
diff --git a/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-disabled_no_date[light_theme-webkit].png b/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-disabled_no_date[light_theme-webkit].png
new file mode 100644
index 000000000000..3c2074a3c510
Binary files /dev/null and b/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-disabled_no_date[light_theme-webkit].png differ
diff --git a/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-empty_value[dark_theme-chromium].png b/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-empty_value[dark_theme-chromium].png
new file mode 100644
index 000000000000..57f20abd337e
Binary files /dev/null and b/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-empty_value[dark_theme-chromium].png differ
diff --git a/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-empty_value[dark_theme-firefox].png b/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-empty_value[dark_theme-firefox].png
new file mode 100644
index 000000000000..7b3e77b35940
Binary files /dev/null and b/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-empty_value[dark_theme-firefox].png differ
diff --git a/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-empty_value[dark_theme-webkit].png b/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-empty_value[dark_theme-webkit].png
new file mode 100644
index 000000000000..255f131541ff
Binary files /dev/null and b/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-empty_value[dark_theme-webkit].png differ
diff --git a/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-empty_value[light_theme-chromium].png b/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-empty_value[light_theme-chromium].png
new file mode 100644
index 000000000000..e4a5583bc28a
Binary files /dev/null and b/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-empty_value[light_theme-chromium].png differ
diff --git a/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-empty_value[light_theme-firefox].png b/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-empty_value[light_theme-firefox].png
new file mode 100644
index 000000000000..8b3ef230366c
Binary files /dev/null and b/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-empty_value[light_theme-firefox].png differ
diff --git a/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-empty_value[light_theme-webkit].png b/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-empty_value[light_theme-webkit].png
new file mode 100644
index 000000000000..eacf2b72afdc
Binary files /dev/null and b/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-empty_value[light_theme-webkit].png differ
diff --git a/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-label_collapsed[dark_theme-chromium].png b/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-label_collapsed[dark_theme-chromium].png
new file mode 100644
index 000000000000..c2b46571ce9f
Binary files /dev/null and b/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-label_collapsed[dark_theme-chromium].png differ
diff --git a/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-label_collapsed[dark_theme-firefox].png b/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-label_collapsed[dark_theme-firefox].png
new file mode 100644
index 000000000000..43aa34732339
Binary files /dev/null and b/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-label_collapsed[dark_theme-firefox].png differ
diff --git a/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-label_collapsed[dark_theme-webkit].png b/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-label_collapsed[dark_theme-webkit].png
new file mode 100644
index 000000000000..1f14dab9a46f
Binary files /dev/null and b/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-label_collapsed[dark_theme-webkit].png differ
diff --git a/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-label_collapsed[light_theme-chromium].png b/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-label_collapsed[light_theme-chromium].png
new file mode 100644
index 000000000000..9718908d8065
Binary files /dev/null and b/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-label_collapsed[light_theme-chromium].png differ
diff --git a/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-label_collapsed[light_theme-firefox].png b/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-label_collapsed[light_theme-firefox].png
new file mode 100644
index 000000000000..672f85c5e02a
Binary files /dev/null and b/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-label_collapsed[light_theme-firefox].png differ
diff --git a/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-label_collapsed[light_theme-webkit].png b/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-label_collapsed[light_theme-webkit].png
new file mode 100644
index 000000000000..98f87cc7e0d6
Binary files /dev/null and b/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-label_collapsed[light_theme-webkit].png differ
diff --git a/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-label_hidden[dark_theme-chromium].png b/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-label_hidden[dark_theme-chromium].png
new file mode 100644
index 000000000000..f3893b16d038
Binary files /dev/null and b/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-label_hidden[dark_theme-chromium].png differ
diff --git a/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-label_hidden[dark_theme-firefox].png b/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-label_hidden[dark_theme-firefox].png
new file mode 100644
index 000000000000..cc1230d12a00
Binary files /dev/null and b/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-label_hidden[dark_theme-firefox].png differ
diff --git a/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-label_hidden[dark_theme-webkit].png b/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-label_hidden[dark_theme-webkit].png
new file mode 100644
index 000000000000..6324e6ff4e75
Binary files /dev/null and b/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-label_hidden[dark_theme-webkit].png differ
diff --git a/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-label_hidden[light_theme-chromium].png b/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-label_hidden[light_theme-chromium].png
new file mode 100644
index 000000000000..df11712da378
Binary files /dev/null and b/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-label_hidden[light_theme-chromium].png differ
diff --git a/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-label_hidden[light_theme-firefox].png b/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-label_hidden[light_theme-firefox].png
new file mode 100644
index 000000000000..4119171eaeaa
Binary files /dev/null and b/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-label_hidden[light_theme-firefox].png differ
diff --git a/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-label_hidden[light_theme-webkit].png b/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-label_hidden[light_theme-webkit].png
new file mode 100644
index 000000000000..37d918bb4b4f
Binary files /dev/null and b/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-label_hidden[light_theme-webkit].png differ
diff --git a/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-range_no_date[dark_theme-chromium].png b/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-range_no_date[dark_theme-chromium].png
new file mode 100644
index 000000000000..58066404c824
Binary files /dev/null and b/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-range_no_date[dark_theme-chromium].png differ
diff --git a/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-range_no_date[dark_theme-firefox].png b/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-range_no_date[dark_theme-firefox].png
new file mode 100644
index 000000000000..f7a737cc54c0
Binary files /dev/null and b/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-range_no_date[dark_theme-firefox].png differ
diff --git a/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-range_no_date[dark_theme-webkit].png b/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-range_no_date[dark_theme-webkit].png
new file mode 100644
index 000000000000..8df87060bb94
Binary files /dev/null and b/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-range_no_date[dark_theme-webkit].png differ
diff --git a/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-range_no_date[light_theme-chromium].png b/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-range_no_date[light_theme-chromium].png
new file mode 100644
index 000000000000..16a85dc1204f
Binary files /dev/null and b/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-range_no_date[light_theme-chromium].png differ
diff --git a/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-range_no_date[light_theme-firefox].png b/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-range_no_date[light_theme-firefox].png
new file mode 100644
index 000000000000..acf16c5427d8
Binary files /dev/null and b/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-range_no_date[light_theme-firefox].png differ
diff --git a/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-range_no_date[light_theme-webkit].png b/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-range_no_date[light_theme-webkit].png
new file mode 100644
index 000000000000..3122ddc8152c
Binary files /dev/null and b/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-range_no_date[light_theme-webkit].png differ
diff --git a/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-range_no_date_format[dark_theme-chromium].png b/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-range_no_date_format[dark_theme-chromium].png
new file mode 100644
index 000000000000..6380b24b3061
Binary files /dev/null and b/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-range_no_date_format[dark_theme-chromium].png differ
diff --git a/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-range_no_date_format[dark_theme-firefox].png b/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-range_no_date_format[dark_theme-firefox].png
new file mode 100644
index 000000000000..8e2d0b3c1823
Binary files /dev/null and b/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-range_no_date_format[dark_theme-firefox].png differ
diff --git a/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-range_no_date_format[dark_theme-webkit].png b/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-range_no_date_format[dark_theme-webkit].png
new file mode 100644
index 000000000000..818fc5de6909
Binary files /dev/null and b/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-range_no_date_format[dark_theme-webkit].png differ
diff --git a/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-range_no_date_format[light_theme-chromium].png b/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-range_no_date_format[light_theme-chromium].png
new file mode 100644
index 000000000000..7ba4ac237760
Binary files /dev/null and b/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-range_no_date_format[light_theme-chromium].png differ
diff --git a/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-range_no_date_format[light_theme-firefox].png b/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-range_no_date_format[light_theme-firefox].png
new file mode 100644
index 000000000000..557345771cb4
Binary files /dev/null and b/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-range_no_date_format[light_theme-firefox].png differ
diff --git a/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-range_no_date_format[light_theme-webkit].png b/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-range_no_date_format[light_theme-webkit].png
new file mode 100644
index 000000000000..e10ece6b5044
Binary files /dev/null and b/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-range_no_date_format[light_theme-webkit].png differ
diff --git a/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-range_one_date[dark_theme-chromium].png b/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-range_one_date[dark_theme-chromium].png
new file mode 100644
index 000000000000..784002af1aed
Binary files /dev/null and b/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-range_one_date[dark_theme-chromium].png differ
diff --git a/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-range_one_date[dark_theme-firefox].png b/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-range_one_date[dark_theme-firefox].png
new file mode 100644
index 000000000000..16a38bc9f68c
Binary files /dev/null and b/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-range_one_date[dark_theme-firefox].png differ
diff --git a/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-range_one_date[dark_theme-webkit].png b/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-range_one_date[dark_theme-webkit].png
new file mode 100644
index 000000000000..d3a00377a9be
Binary files /dev/null and b/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-range_one_date[dark_theme-webkit].png differ
diff --git a/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-range_one_date[light_theme-chromium].png b/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-range_one_date[light_theme-chromium].png
new file mode 100644
index 000000000000..c95c48706425
Binary files /dev/null and b/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-range_one_date[light_theme-chromium].png differ
diff --git a/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-range_one_date[light_theme-firefox].png b/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-range_one_date[light_theme-firefox].png
new file mode 100644
index 000000000000..b876b8a0b950
Binary files /dev/null and b/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-range_one_date[light_theme-firefox].png differ
diff --git a/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-range_one_date[light_theme-webkit].png b/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-range_one_date[light_theme-webkit].png
new file mode 100644
index 000000000000..ba5e377a3f6a
Binary files /dev/null and b/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-range_one_date[light_theme-webkit].png differ
diff --git a/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-range_two_dates[dark_theme-chromium].png b/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-range_two_dates[dark_theme-chromium].png
new file mode 100644
index 000000000000..69ccb414b712
Binary files /dev/null and b/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-range_two_dates[dark_theme-chromium].png differ
diff --git a/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-range_two_dates[dark_theme-firefox].png b/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-range_two_dates[dark_theme-firefox].png
new file mode 100644
index 000000000000..3d265cbe8613
Binary files /dev/null and b/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-range_two_dates[dark_theme-firefox].png differ
diff --git a/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-range_two_dates[dark_theme-webkit].png b/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-range_two_dates[dark_theme-webkit].png
new file mode 100644
index 000000000000..3e95ccdf42aa
Binary files /dev/null and b/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-range_two_dates[dark_theme-webkit].png differ
diff --git a/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-range_two_dates[light_theme-chromium].png b/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-range_two_dates[light_theme-chromium].png
new file mode 100644
index 000000000000..f63975ea542a
Binary files /dev/null and b/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-range_two_dates[light_theme-chromium].png differ
diff --git a/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-range_two_dates[light_theme-firefox].png b/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-range_two_dates[light_theme-firefox].png
new file mode 100644
index 000000000000..fd7a7b46c19f
Binary files /dev/null and b/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-range_two_dates[light_theme-firefox].png differ
diff --git a/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-range_two_dates[light_theme-webkit].png b/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-range_two_dates[light_theme-webkit].png
new file mode 100644
index 000000000000..7589469fd95e
Binary files /dev/null and b/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-range_two_dates[light_theme-webkit].png differ
diff --git a/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-range_two_dates_calendar[dark_theme-chromium].png b/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-range_two_dates_calendar[dark_theme-chromium].png
new file mode 100644
index 000000000000..30975ea92e4c
Binary files /dev/null and b/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-range_two_dates_calendar[dark_theme-chromium].png differ
diff --git a/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-range_two_dates_calendar[dark_theme-firefox].png b/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-range_two_dates_calendar[dark_theme-firefox].png
new file mode 100644
index 000000000000..cfdea98db01f
Binary files /dev/null and b/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-range_two_dates_calendar[dark_theme-firefox].png differ
diff --git a/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-range_two_dates_calendar[dark_theme-webkit].png b/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-range_two_dates_calendar[dark_theme-webkit].png
new file mode 100644
index 000000000000..424fc54a7395
Binary files /dev/null and b/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-range_two_dates_calendar[dark_theme-webkit].png differ
diff --git a/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-range_two_dates_calendar[light_theme-chromium].png b/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-range_two_dates_calendar[light_theme-chromium].png
new file mode 100644
index 000000000000..60f6160f0748
Binary files /dev/null and b/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-range_two_dates_calendar[light_theme-chromium].png differ
diff --git a/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-range_two_dates_calendar[light_theme-firefox].png b/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-range_two_dates_calendar[light_theme-firefox].png
new file mode 100644
index 000000000000..4b9c1be9d8f0
Binary files /dev/null and b/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-range_two_dates_calendar[light_theme-firefox].png differ
diff --git a/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-range_two_dates_calendar[light_theme-webkit].png b/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-range_two_dates_calendar[light_theme-webkit].png
new file mode 100644
index 000000000000..884dade37ac8
Binary files /dev/null and b/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-range_two_dates_calendar[light_theme-webkit].png differ
diff --git a/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-range_two_dates_format[dark_theme-chromium].png b/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-range_two_dates_format[dark_theme-chromium].png
new file mode 100644
index 000000000000..de7f4f9220a4
Binary files /dev/null and b/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-range_two_dates_format[dark_theme-chromium].png differ
diff --git a/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-range_two_dates_format[dark_theme-firefox].png b/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-range_two_dates_format[dark_theme-firefox].png
new file mode 100644
index 000000000000..6ab4d31849b8
Binary files /dev/null and b/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-range_two_dates_format[dark_theme-firefox].png differ
diff --git a/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-range_two_dates_format[dark_theme-webkit].png b/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-range_two_dates_format[dark_theme-webkit].png
new file mode 100644
index 000000000000..eb27d76b1c57
Binary files /dev/null and b/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-range_two_dates_format[dark_theme-webkit].png differ
diff --git a/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-range_two_dates_format[light_theme-chromium].png b/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-range_two_dates_format[light_theme-chromium].png
new file mode 100644
index 000000000000..24b010ad601a
Binary files /dev/null and b/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-range_two_dates_format[light_theme-chromium].png differ
diff --git a/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-range_two_dates_format[light_theme-firefox].png b/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-range_two_dates_format[light_theme-firefox].png
new file mode 100644
index 000000000000..2639e5af6a92
Binary files /dev/null and b/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-range_two_dates_format[light_theme-firefox].png differ
diff --git a/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-range_two_dates_format[light_theme-webkit].png b/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-range_two_dates_format[light_theme-webkit].png
new file mode 100644
index 000000000000..9f5629bd3f76
Binary files /dev/null and b/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-range_two_dates_format[light_theme-webkit].png differ
diff --git a/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-single_date[dark_theme-chromium].png b/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-single_date[dark_theme-chromium].png
new file mode 100644
index 000000000000..8989964bce3c
Binary files /dev/null and b/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-single_date[dark_theme-chromium].png differ
diff --git a/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-single_date[dark_theme-firefox].png b/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-single_date[dark_theme-firefox].png
new file mode 100644
index 000000000000..142b9e79a188
Binary files /dev/null and b/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-single_date[dark_theme-firefox].png differ
diff --git a/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-single_date[dark_theme-webkit].png b/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-single_date[dark_theme-webkit].png
new file mode 100644
index 000000000000..825bc4b4bd12
Binary files /dev/null and b/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-single_date[dark_theme-webkit].png differ
diff --git a/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-single_date[light_theme-chromium].png b/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-single_date[light_theme-chromium].png
new file mode 100644
index 000000000000..d7eae02f45de
Binary files /dev/null and b/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-single_date[light_theme-chromium].png differ
diff --git a/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-single_date[light_theme-firefox].png b/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-single_date[light_theme-firefox].png
new file mode 100644
index 000000000000..fa7381a7071a
Binary files /dev/null and b/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-single_date[light_theme-firefox].png differ
diff --git a/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-single_date[light_theme-webkit].png b/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-single_date[light_theme-webkit].png
new file mode 100644
index 000000000000..87fd52527eaa
Binary files /dev/null and b/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-single_date[light_theme-webkit].png differ
diff --git a/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-single_date_calendar[dark_theme-chromium].png b/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-single_date_calendar[dark_theme-chromium].png
new file mode 100644
index 000000000000..ed1b7769b17c
Binary files /dev/null and b/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-single_date_calendar[dark_theme-chromium].png differ
diff --git a/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-single_date_calendar[dark_theme-firefox].png b/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-single_date_calendar[dark_theme-firefox].png
new file mode 100644
index 000000000000..f8f4b4c552dd
Binary files /dev/null and b/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-single_date_calendar[dark_theme-firefox].png differ
diff --git a/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-single_date_calendar[dark_theme-webkit].png b/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-single_date_calendar[dark_theme-webkit].png
new file mode 100644
index 000000000000..606e67080fe2
Binary files /dev/null and b/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-single_date_calendar[dark_theme-webkit].png differ
diff --git a/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-single_date_calendar[light_theme-chromium].png b/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-single_date_calendar[light_theme-chromium].png
new file mode 100644
index 000000000000..8fbae3a8cb7f
Binary files /dev/null and b/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-single_date_calendar[light_theme-chromium].png differ
diff --git a/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-single_date_calendar[light_theme-firefox].png b/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-single_date_calendar[light_theme-firefox].png
new file mode 100644
index 000000000000..0be28a65d2a0
Binary files /dev/null and b/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-single_date_calendar[light_theme-firefox].png differ
diff --git a/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-single_date_calendar[light_theme-webkit].png b/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-single_date_calendar[light_theme-webkit].png
new file mode 100644
index 000000000000..38c73bdef69f
Binary files /dev/null and b/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-single_date_calendar[light_theme-webkit].png differ
diff --git a/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-single_date_callback[dark_theme-chromium].png b/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-single_date_callback[dark_theme-chromium].png
new file mode 100644
index 000000000000..f6205e37951a
Binary files /dev/null and b/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-single_date_callback[dark_theme-chromium].png differ
diff --git a/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-single_date_callback[dark_theme-firefox].png b/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-single_date_callback[dark_theme-firefox].png
new file mode 100644
index 000000000000..bb225ab197fe
Binary files /dev/null and b/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-single_date_callback[dark_theme-firefox].png differ
diff --git a/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-single_date_callback[dark_theme-webkit].png b/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-single_date_callback[dark_theme-webkit].png
new file mode 100644
index 000000000000..cbbc9f478c0c
Binary files /dev/null and b/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-single_date_callback[dark_theme-webkit].png differ
diff --git a/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-single_date_callback[light_theme-chromium].png b/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-single_date_callback[light_theme-chromium].png
new file mode 100644
index 000000000000..9d8be905ede8
Binary files /dev/null and b/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-single_date_callback[light_theme-chromium].png differ
diff --git a/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-single_date_callback[light_theme-firefox].png b/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-single_date_callback[light_theme-firefox].png
new file mode 100644
index 000000000000..e63ca3134b2c
Binary files /dev/null and b/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-single_date_callback[light_theme-firefox].png differ
diff --git a/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-single_date_callback[light_theme-webkit].png b/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-single_date_callback[light_theme-webkit].png
new file mode 100644
index 000000000000..adf6cc3f81d5
Binary files /dev/null and b/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-single_date_callback[light_theme-webkit].png differ
diff --git a/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-single_date_format[dark_theme-chromium].png b/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-single_date_format[dark_theme-chromium].png
new file mode 100644
index 000000000000..b19f2bb1d183
Binary files /dev/null and b/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-single_date_format[dark_theme-chromium].png differ
diff --git a/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-single_date_format[dark_theme-firefox].png b/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-single_date_format[dark_theme-firefox].png
new file mode 100644
index 000000000000..2c5149eb7931
Binary files /dev/null and b/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-single_date_format[dark_theme-firefox].png differ
diff --git a/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-single_date_format[dark_theme-webkit].png b/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-single_date_format[dark_theme-webkit].png
new file mode 100644
index 000000000000..49710d48856c
Binary files /dev/null and b/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-single_date_format[dark_theme-webkit].png differ
diff --git a/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-single_date_format[light_theme-chromium].png b/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-single_date_format[light_theme-chromium].png
new file mode 100644
index 000000000000..6ceb19d92cc2
Binary files /dev/null and b/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-single_date_format[light_theme-chromium].png differ
diff --git a/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-single_date_format[light_theme-firefox].png b/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-single_date_format[light_theme-firefox].png
new file mode 100644
index 000000000000..94c6e609d379
Binary files /dev/null and b/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-single_date_format[light_theme-firefox].png differ
diff --git a/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-single_date_format[light_theme-webkit].png b/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-single_date_format[light_theme-webkit].png
new file mode 100644
index 000000000000..726de9ecbe54
Binary files /dev/null and b/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-single_date_format[light_theme-webkit].png differ
diff --git a/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-single_datetime[dark_theme-chromium].png b/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-single_datetime[dark_theme-chromium].png
new file mode 100644
index 000000000000..23ddc92e2531
Binary files /dev/null and b/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-single_datetime[dark_theme-chromium].png differ
diff --git a/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-single_datetime[dark_theme-firefox].png b/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-single_datetime[dark_theme-firefox].png
new file mode 100644
index 000000000000..2a24d28e5b90
Binary files /dev/null and b/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-single_datetime[dark_theme-firefox].png differ
diff --git a/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-single_datetime[dark_theme-webkit].png b/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-single_datetime[dark_theme-webkit].png
new file mode 100644
index 000000000000..0d74f459102f
Binary files /dev/null and b/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-single_datetime[dark_theme-webkit].png differ
diff --git a/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-single_datetime[light_theme-chromium].png b/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-single_datetime[light_theme-chromium].png
new file mode 100644
index 000000000000..18604234786b
Binary files /dev/null and b/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-single_datetime[light_theme-chromium].png differ
diff --git a/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-single_datetime[light_theme-firefox].png b/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-single_datetime[light_theme-firefox].png
new file mode 100644
index 000000000000..6dc14c508c2b
Binary files /dev/null and b/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-single_datetime[light_theme-firefox].png differ
diff --git a/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-single_datetime[light_theme-webkit].png b/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-single_datetime[light_theme-webkit].png
new file mode 100644
index 000000000000..3c600dd9a6b6
Binary files /dev/null and b/e2e_playwright/__snapshots__/linux/st_date_input_test/date_input-single_datetime[light_theme-webkit].png differ
diff --git a/e2e_playwright/__snapshots__/linux/st_date_input_test/st_date_input-clearable_input[chromium].png b/e2e_playwright/__snapshots__/linux/st_date_input_test/st_date_input-clearable_input[chromium].png
new file mode 100644
index 000000000000..4d561acd86b5
Binary files /dev/null and b/e2e_playwright/__snapshots__/linux/st_date_input_test/st_date_input-clearable_input[chromium].png differ
diff --git a/e2e_playwright/__snapshots__/linux/st_date_input_test/st_date_input-clearable_input[firefox].png b/e2e_playwright/__snapshots__/linux/st_date_input_test/st_date_input-clearable_input[firefox].png
new file mode 100644
index 000000000000..dc972f3c1e35
Binary files /dev/null and b/e2e_playwright/__snapshots__/linux/st_date_input_test/st_date_input-clearable_input[firefox].png differ
diff --git a/e2e_playwright/__snapshots__/linux/st_date_input_test/st_date_input-clearable_input[webkit].png b/e2e_playwright/__snapshots__/linux/st_date_input_test/st_date_input-clearable_input[webkit].png
new file mode 100644
index 000000000000..bd6a96ae7e05
Binary files /dev/null and b/e2e_playwright/__snapshots__/linux/st_date_input_test/st_date_input-clearable_input[webkit].png differ
diff --git a/e2e_playwright/conftest.py b/e2e_playwright/conftest.py
index 2868897c4035..e672e51c7bc0 100644
--- a/e2e_playwright/conftest.py
+++ b/e2e_playwright/conftest.py
@@ -396,7 +396,7 @@ def compare(
             # Update this in updates folder:
             snapshot_updates_file_path.parent.mkdir(parents=True, exist_ok=True)
             snapshot_updates_file_path.write_bytes(img_bytes)
-            pytest.fail(f"Snapshot matching failed: {ex}")
+            pytest.fail(f"Snapshot matching for {snapshot_file_name} failed: {ex}")
         max_diff_pixels = int(image_threshold * img_a.size[0] * img_a.size[1])
 
         if mismatch < max_diff_pixels:
diff --git a/e2e_playwright/st_date_input.py b/e2e_playwright/st_date_input.py
new file mode 100644
index 000000000000..76a034873072
--- /dev/null
+++ b/e2e_playwright/st_date_input.py
@@ -0,0 +1,79 @@
+# Copyright (c) Streamlit Inc. (2018-2022) Snowflake Inc. (2022)
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from datetime import date, datetime
+
+import streamlit as st
+from streamlit import runtime
+
+d1 = st.date_input("Single date", date(1970, 1, 1), min_value=date(1970, 1, 1))
+st.write("Value 1:", d1)
+
+d2 = st.date_input("Single datetime", datetime(2019, 7, 6, 21, 15), help="Help text")
+st.write("Value 2:", d2)
+
+d3 = st.date_input("Range, no date", [])
+st.write("Value 3:", d3)
+
+d4 = st.date_input("Range, one date", [date(2019, 7, 6)])
+st.write("Value 4:", d4)
+
+d5 = st.date_input("Range, two dates", [date(2019, 7, 6), date(2019, 7, 8)])
+st.write("Value 5:", d5)
+
+d6 = st.date_input("Disabled, no date", [], disabled=True)
+st.write("Value 6:", d6)
+
+d7 = st.date_input(
+    "Label hidden", datetime(2019, 7, 6, 21, 15), label_visibility="hidden"
+)
+st.write("Value 7:", d7)
+
+d8 = st.date_input(
+    "Label collapsed", datetime(2019, 7, 6, 21, 15), label_visibility="collapsed"
+)
+st.write("Value 8:", d8)
+
+d9 = st.date_input("Single date with format", date(1970, 1, 1), format="MM-DD-YYYY")
+st.write("Value 9:", d9)
+
+d10 = st.date_input(
+    "Range, two dates with format",
+    [date(2019, 7, 6), date(2019, 7, 8)],
+    format="MM/DD/YYYY",
+)
+st.write("Value 10:", d10)
+
+d11 = st.date_input("Range, no date with format", [], format="DD.MM.YYYY")
+st.write("Value 11:", d11)
+
+
+if runtime.exists():
+
+    def on_change():
+        st.session_state.date_input_changed = True
+        st.text("Date input changed callback")
+
+    st.date_input(
+        "Single date with callback",
+        date(1970, 1, 1),
+        min_value=date(1970, 1, 1),
+        key="date_input12",
+        on_change=on_change,
+    )
+    st.write("Value 12:", st.session_state.date_input12)
+    st.write("Date Input Changed:", "date_input_changed" in st.session_state)
+
+d13 = st.date_input("Empty value", value=None)
+st.write("Value 13:", d13)
diff --git a/e2e_playwright/st_date_input_test.py b/e2e_playwright/st_date_input_test.py
new file mode 100644
index 000000000000..507a1e9acc4e
--- /dev/null
+++ b/e2e_playwright/st_date_input_test.py
@@ -0,0 +1,285 @@
+# Copyright (c) Streamlit Inc. (2018-2022) Snowflake Inc. (2022)
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from playwright.sync_api import Page, expect
+
+from e2e_playwright.conftest import ImageCompareFunction
+
+
+def test_date_input_rendering(themed_app: Page, assert_snapshot: ImageCompareFunction):
+    """Test that st.date_input renders correctly via screenshots matching."""
+    date_time_widgets = themed_app.get_by_test_id("stDateInput")
+    expect(date_time_widgets).to_have_count(13)
+
+    assert_snapshot(date_time_widgets.nth(0), name="date_input-single_date")
+    assert_snapshot(date_time_widgets.nth(1), name="date_input-single_datetime")
+    assert_snapshot(date_time_widgets.nth(2), name="date_input-range_no_date")
+    assert_snapshot(date_time_widgets.nth(3), name="date_input-range_one_date")
+    assert_snapshot(date_time_widgets.nth(4), name="date_input-range_two_dates")
+    assert_snapshot(date_time_widgets.nth(5), name="date_input-disabled_no_date")
+    assert_snapshot(date_time_widgets.nth(6), name="date_input-label_hidden")
+    assert_snapshot(date_time_widgets.nth(7), name="date_input-label_collapsed")
+    assert_snapshot(date_time_widgets.nth(8), name="date_input-single_date_format")
+    assert_snapshot(date_time_widgets.nth(9), name="date_input-range_two_dates_format")
+    assert_snapshot(date_time_widgets.nth(10), name="date_input-range_no_date_format")
+    assert_snapshot(date_time_widgets.nth(11), name="date_input-single_date_callback")
+    assert_snapshot(date_time_widgets.nth(12), name="date_input-empty_value")
+
+
+def test_date_input_has_correct_initial_values(app: Page):
+    """Test that st.date_input has the correct initial values."""
+    markdown_elements = app.get_by_test_id("stMarkdown")
+    expect(markdown_elements).to_have_count(14)
+
+    expected = [
+        "Value 1: 1970-01-01",
+        "Value 2: 2019-07-06",
+        "Value 3: ()",
+        "Value 4: (datetime.date(2019, 7, 6),)",
+        "Value 5: (datetime.date(2019, 7, 6), datetime.date(2019, 7, 8))",
+        "Value 6: ()",
+        "Value 7: 2019-07-06",
+        "Value 8: 2019-07-06",
+        "Value 9: 1970-01-01",
+        "Value 10: (datetime.date(2019, 7, 6), datetime.date(2019, 7, 8))",
+        "Value 11: ()",
+        "Value 12: 1970-01-01",
+        "Date Input Changed: False",
+        "Value 13: None",
+    ]
+
+    for markdown_element, expected_text in zip(markdown_elements.all(), expected):
+        expect(markdown_element).to_have_text(expected_text, use_inner_text=True)
+
+
+def test_handles_date_selection(app: Page):
+    """Test that selection of a date on the calendar works as expected."""
+    app.get_by_test_id("stDateInput").first.click()
+
+    # Select '1970/01/02':
+    app.locator(
+        '[data-baseweb="calendar"] [aria-label^="Choose Friday, January 2nd 1970."]'
+    ).first.click()
+
+    expect(app.get_by_test_id("stMarkdown").first).to_have_text(
+        "Value 1: 1970-01-02", use_inner_text=True
+    )
+
+
+def test_handle_value_changes(app: Page):
+    """Test that st.date_input has the correct value after typing in a date."""
+
+    first_date_input_field = app.locator(".stDateInput input").first
+    first_date_input_field.type("1970/01/02")
+    first_date_input_field.blur()
+
+    expect(app.get_by_test_id("stMarkdown").first).to_have_text(
+        "Value 1: 1970-01-02", use_inner_text=True
+    )
+
+
+def test_empty_date_input_behaves_correctly(
+    app: Page, assert_snapshot: ImageCompareFunction
+):
+    """Test that st.date_input behaves correctly when empty."""
+    # Enter 10 in the first empty input:
+    empty_number_input = app.locator(".stDateInput input").nth(12)
+    empty_number_input.type("1970/01/02")
+    empty_number_input.press("Enter")
+
+    expect(app.get_by_test_id("stMarkdown").nth(13)).to_have_text(
+        "Value 13: 1970-01-02", use_inner_text=True
+    )
+
+    # Click outside to remove focus:
+    app.get_by_test_id("stMarkdown").nth(13).click()
+
+    # Screenshot match clearable input:
+    assert_snapshot(
+        app.get_by_test_id("stDateInput").nth(12), name="st_date_input-clearable_input"
+    )
+
+    # Press escape to clear value:
+    empty_number_input = app.locator(".stDateInput input").nth(12)
+    empty_number_input.focus()
+    empty_number_input.press("Escape")
+    # Click outside to enter value:
+    app.get_by_test_id("stMarkdown").nth(13).click()
+
+    # Should be empty again:
+    expect(app.get_by_test_id("stMarkdown").nth(13)).to_have_text(
+        "Value 13: None", use_inner_text=True
+    )
+
+
+def test_handles_range_end_date_changes(app: Page):
+    """Test that it correctly handles changes to the end date of a range."""
+    app.get_by_test_id("stDateInput").nth(3).click()
+
+    # Select '2019/07/10'
+    app.locator(
+        '[data-baseweb="calendar"] [aria-label^="Choose Wednesday, July 10th 2019."]'
+    ).first.click()
+
+    expect(app.get_by_test_id("stMarkdown").nth(3)).to_have_text(
+        "Value 4: (datetime.date(2019, 7, 6), datetime.date(2019, 7, 10))",
+        use_inner_text=True,
+    )
+
+
+def test_handles_range_start_end_date_changes(app: Page):
+    """Test that it correctly handles changes to the start and end date of a range."""
+    app.get_by_test_id("stDateInput").nth(4).click()
+
+    # Select start date: '2019/07/10'
+    app.locator(
+        '[data-baseweb="calendar"] [aria-label^="Choose Wednesday, July 10th 2019."]'
+    ).first.click()
+
+    expect(app.get_by_test_id("stMarkdown").nth(4)).to_have_text(
+        "Value 5: (datetime.date(2019, 7, 10),)",
+        use_inner_text=True,
+    )
+
+    # Select end date: '2019/07/12'
+    app.locator(
+        '[data-baseweb="calendar"] [aria-label^="Choose Friday, July 12th 2019."]'
+    ).first.click()
+
+    expect(app.get_by_test_id("stMarkdown").nth(4)).to_have_text(
+        "Value 5: (datetime.date(2019, 7, 10), datetime.date(2019, 7, 12))",
+        use_inner_text=True,
+    )
+
+
+def test_calls_callback_on_change(app: Page):
+    """Test that it correctly calls the callback on change."""
+    app.get_by_test_id("stDateInput").nth(11).click()
+
+    # Select '1970/01/02'
+    app.locator(
+        '[data-baseweb="calendar"] [aria-label^="Choose Friday, January 2nd 1970."]'
+    ).first.click()
+
+    expect(app.get_by_test_id("stMarkdown").nth(11)).to_have_text(
+        "Value 12: 1970-01-02",
+        use_inner_text=True,
+    )
+    expect(app.get_by_test_id("stMarkdown").nth(12)).to_have_text(
+        "Date Input Changed: True",
+        use_inner_text=True,
+    )
+
+    # Change different date input to trigger delta path change
+    first_date_input_field = app.locator(".stDateInput input").first
+    first_date_input_field.type("1971/01/03")
+    first_date_input_field.blur()
+
+    expect(app.get_by_test_id("stMarkdown").first).to_have_text(
+        "Value 1: 1971-01-03", use_inner_text=True
+    )
+
+    # Test if value is still correct after delta path change
+    expect(app.get_by_test_id("stMarkdown").nth(11)).to_have_text(
+        "Value 12: 1970-01-02",
+        use_inner_text=True,
+    )
+
+
+def test_single_date_calendar_picker_rendering(
+    themed_app: Page, assert_snapshot: ImageCompareFunction
+):
+    """Test that the single value calendar picker renders correctly via screenshots matching."""
+    themed_app.get_by_test_id("stDateInput").first.click()
+    assert_snapshot(
+        themed_app.locator('[data-baseweb="calendar"]').first,
+        name="date_input-single_date_calendar",
+    )
+
+
+def test_range_date_calendar_picker_rendering(
+    themed_app: Page, assert_snapshot: ImageCompareFunction
+):
+    """Test that the range calendar picker renders correctly via screenshots matching."""
+    themed_app.get_by_test_id("stDateInput").nth(4).click()
+    assert_snapshot(
+        themed_app.locator('[data-baseweb="calendar"]').first,
+        name="date_input-range_two_dates_calendar",
+    )
+
+
+def test_resets_to_default_single_value_if_calendar_closed_empty(app: Page):
+    """Test that single value is reset to default if calendar closed empty."""
+    app.get_by_test_id("stDateInput").first.click()
+
+    # Select '1970/01/02'
+    app.locator(
+        '[data-baseweb="calendar"] [aria-label^="Choose Friday, January 2nd 1970."]'
+    ).first.click()
+
+    expect(app.get_by_test_id("stMarkdown").first).to_have_text(
+        "Value 1: 1970-01-02", use_inner_text=True
+    )
+
+    # Close calendar without selecting a date
+    date_input_field = app.locator(".stDateInput input").first
+    date_input_field.focus()
+    date_input_field.clear()
+
+    # Click outside of the calendar to submit value
+    app.get_by_test_id("stMarkdown").first.click(delay=100)
+
+    # Value should be reset to default
+    expect(app.get_by_test_id("stMarkdown").first).to_have_text(
+        "Value 1: 1970-01-01", use_inner_text=True
+    )
+
+
+def test_range_is_empty_if_calendar_closed_empty(app: Page):
+    """Test that range value is empty of calendar closed empty."""
+    app.get_by_test_id("stDateInput").nth(4).click()
+
+    # Select start date: '2019/07/10'
+    app.locator(
+        '[data-baseweb="calendar"] [aria-label^="Choose Wednesday, July 10th 2019."]'
+    ).first.click()
+
+    expect(app.get_by_test_id("stMarkdown").nth(4)).to_have_text(
+        "Value 5: (datetime.date(2019, 7, 10),)",
+        use_inner_text=True,
+    )
+
+    # Select end date: '2019/07/12'
+    app.locator(
+        '[data-baseweb="calendar"] [aria-label^="Choose Friday, July 12th 2019."]'
+    ).first.click()
+
+    expect(app.get_by_test_id("stMarkdown").nth(4)).to_have_text(
+        "Value 5: (datetime.date(2019, 7, 10), datetime.date(2019, 7, 12))",
+        use_inner_text=True,
+    )
+
+    # Close calendar without selecting a date
+    date_input_field = app.locator(".stDateInput input").nth(4)
+    date_input_field.focus()
+    date_input_field.clear()
+
+    # Click outside of the calendar to submit value
+    app.get_by_test_id("stMarkdown").nth(4).click()
+
+    # Range should be empty
+    expect(app.get_by_test_id("stMarkdown").nth(4)).to_have_text(
+        "Value 5: ()",
+        use_inner_text=True,
+    )
diff --git a/frontend/lib/src/components/elements/Markdown/Markdown.tsx b/frontend/lib/src/components/elements/Markdown/Markdown.tsx
index c0a932c4f327..00bab744812e 100644
--- a/frontend/lib/src/components/elements/Markdown/Markdown.tsx
+++ b/frontend/lib/src/components/elements/Markdown/Markdown.tsx
@@ -37,7 +37,7 @@ export default function Markdown({
 }: MarkdownProps): ReactElement {
   const styleProp = { width }
   return (
-    <div className="stMarkdown" style={styleProp}>
+    <div className="stMarkdown" style={styleProp} data-testid="stMarkdown">
       {element.help ? (
         <StyledLabelHelpWrapper>
           <StreamlitMarkdown
diff --git a/frontend/lib/src/components/widgets/DateInput/DateInput.tsx b/frontend/lib/src/components/widgets/DateInput/DateInput.tsx
index 40d217939393..a3737eb8752b 100644
--- a/frontend/lib/src/components/widgets/DateInput/DateInput.tsx
+++ b/frontend/lib/src/components/widgets/DateInput/DateInput.tsx
@@ -66,6 +66,9 @@ function stringsToDates(strings: string[]): Date[] {
 
 /** Convert an array of dates to an array of strings. */
 function datesToStrings(dates: Date[]): string[] {
+  if (!dates) {
+    return []
+  }
   return dates.map((value: Date) => moment(value as Date).format(DATE_FORMAT))
 }
 
@@ -85,7 +88,9 @@ class DateInput extends React.PureComponent<Props, State> {
       this.props.element
     )
     const stringArray =
-      storedValue !== undefined ? storedValue : this.props.element.default
+      storedValue !== undefined
+        ? storedValue
+        : this.props.element.default || []
     return stringsToDates(stringArray)
   }
 
@@ -214,6 +219,7 @@ class DateInput extends React.PureComponent<Props, State> {
     const style = { width }
     const minDate = moment(element.min, DATE_FORMAT).toDate()
     const maxDate = this.getMaxDate()
+    const clearable = element.default.length === 0 && !disabled
 
     // We need to extract the mask and format (date-fns notation) from the provided format string
     // The user configured date format is based on the momentJS notation and is only allowed to contain
@@ -235,7 +241,7 @@ class DateInput extends React.PureComponent<Props, State> {
     )
 
     return (
-      <div className="stDateInput" style={style}>
+      <div className="stDateInput" style={style} data-testid="stDateInput">
         <WidgetLabel
           label={element.label}
           disabled={disabled}
@@ -368,7 +374,26 @@ class DateInput extends React.PureComponent<Props, State> {
                       borderBottomWidth: "1px",
                     },
                   },
-
+                  ClearIcon: {
+                    props: {
+                      overrides: {
+                        Svg: {
+                          style: {
+                            color: theme.colors.darkGray,
+                            // Since the close icon is an SVG, and we can't control its viewbox nor its attributes,
+                            // Let's use a scale transform effect to make it bigger.
+                            // The width property only enlarges its bounding box, so it's easier to click.
+                            transform: "scale(1.41)",
+                            width: theme.spacing.twoXL,
+                            marginRight: "-8px",
+                            ":hover": {
+                              fill: theme.colors.bodyText,
+                            },
+                          },
+                        },
+                      },
+                    },
+                  },
                   Input: {
                     style: {
                       // Baseweb requires long-hand props, short-hand leads to weird bugs & warnings.
@@ -386,6 +411,7 @@ class DateInput extends React.PureComponent<Props, State> {
           minDate={minDate}
           maxDate={maxDate}
           range={isRange}
+          clearable={clearable}
         />
       </div>
     )
diff --git a/lib/streamlit/elements/widgets/number_input.py b/lib/streamlit/elements/widgets/number_input.py
index 56ba71b10de5..5a39f20a9479 100644
--- a/lib/streamlit/elements/widgets/number_input.py
+++ b/lib/streamlit/elements/widgets/number_input.py
@@ -169,8 +169,8 @@ def number_input(
             If None, there will be no maximum.
         value : int, float, or None
             The value of this widget when it first renders. If ``None``, the widget
-            will be initialized empty and returns ``None`` as long as the user didn't
-            provide an input. Defaults to min_value, or 0.0 if min_value is None.
+            will initialize empty and return ``None`` until the user provides input.
+            Defaults to min_value, or 0.0 if min_value is None.
         step : int, float, or None
             The stepping interval.
             Defaults to 1 if the value is an int, 0.01 otherwise.
diff --git a/lib/streamlit/elements/widgets/time_widgets.py b/lib/streamlit/elements/widgets/time_widgets.py
index 3d7b75589f7d..cb4cca563db7 100644
--- a/lib/streamlit/elements/widgets/time_widgets.py
+++ b/lib/streamlit/elements/widgets/time_widgets.py
@@ -34,6 +34,7 @@
 from streamlit.runtime.metrics_util import gather_metrics
 from streamlit.runtime.scriptrunner import ScriptRunContext, get_script_run_ctx
 from streamlit.runtime.state import (
+    DefaultValue,
     WidgetArgs,
     WidgetCallback,
     WidgetKwargs,
@@ -43,12 +44,16 @@
 from streamlit.type_util import Key, LabelVisibility, maybe_raise_label_warnings, to_key
 
 if TYPE_CHECKING:
+    from builtins import ellipsis
+
     from streamlit.delta_generator import DeltaGenerator
 
 TimeValue: TypeAlias = Union[time, datetime, None]
 SingleDateValue: TypeAlias = Union[date, datetime, None]
 DateValue: TypeAlias = Union[SingleDateValue, Sequence[SingleDateValue]]
-DateWidgetReturn: TypeAlias = Union[date, Tuple[()], Tuple[date], Tuple[date, date]]
+DateWidgetReturn: TypeAlias = Union[
+    date, Tuple[()], Tuple[date], Tuple[date, date], None
+]
 
 DEFAULT_STEP_MINUTES = 15
 ALLOWED_DATE_FORMATS = re.compile(
@@ -56,10 +61,12 @@
 )
 
 
-def _parse_date_value(value: DateValue) -> Tuple[List[date], bool]:
+def _parse_date_value(value: DateValue | ellipsis) -> Tuple[List[date] | None, bool]:
     parsed_dates: List[date]
     range_value: bool = False
     if value is None:
+        return None, range_value
+    if value is DefaultValue:
         # Set value default.
         parsed_dates = [datetime.now().date()]
     elif isinstance(value, datetime):
@@ -85,7 +92,7 @@ def _parse_date_value(value: DateValue) -> Tuple[List[date], bool]:
 
 def _parse_min_date(
     min_value: SingleDateValue,
-    parsed_dates: Sequence[date],
+    parsed_dates: Sequence[date] | None,
 ) -> date:
     parsed_min_date: date
     if isinstance(min_value, datetime):
@@ -106,7 +113,7 @@ def _parse_min_date(
 
 def _parse_max_date(
     max_value: SingleDateValue,
-    parsed_dates: Sequence[date],
+    parsed_dates: Sequence[date] | None,
 ) -> date:
     parsed_max_date: date
     if isinstance(max_value, datetime):
@@ -127,7 +134,7 @@ def _parse_max_date(
 
 @dataclass(frozen=True)
 class _DateInputValues:
-    value: Sequence[date]
+    value: Sequence[date] | None
     is_range: bool
     max: date
     min: date
@@ -135,7 +142,7 @@ class _DateInputValues:
     @classmethod
     def from_raw_values(
         cls,
-        value: DateValue,
+        value: DateValue | ellipsis,
         min_value: SingleDateValue,
         max_value: SingleDateValue,
     ) -> "_DateInputValues":
@@ -198,7 +205,7 @@ def deserialize(
         ui_value: Any,
         widget_id: str = "",
     ) -> DateWidgetReturn:
-        return_value: Sequence[date]
+        return_value: Sequence[date] | None
         if ui_value is not None:
             return_value = tuple(
                 datetime.strptime(v, "%Y/%m/%d").date() for v in ui_value
@@ -206,11 +213,17 @@ def deserialize(
         else:
             return_value = self.value.value
 
+        if return_value is None or len(return_value) == 0:
+            return () if self.value.is_range else None
+
         if not self.value.is_range:
             return return_value[0]
         return cast(DateWidgetReturn, tuple(return_value))
 
     def serialize(self, v: DateWidgetReturn) -> List[str]:
+        if v is None:
+            return []
+
         to_serialize = list(v) if isinstance(v, (list, tuple)) else [v]
         return [date.strftime(v, "%Y/%m/%d") for v in to_serialize]
 
@@ -417,14 +430,14 @@ def _time_input(
     def date_input(
         self,
         label: str,
-        value: DateValue = None,
+        value: DateValue | ellipsis = ...,
         min_value: SingleDateValue = None,
         max_value: SingleDateValue = None,
-        key: Optional[Key] = None,
-        help: Optional[str] = None,
-        on_change: Optional[WidgetCallback] = None,
-        args: Optional[WidgetArgs] = None,
-        kwargs: Optional[WidgetKwargs] = None,
+        key: Key | None = None,
+        help: str | None = None,
+        on_change: WidgetCallback | None = None,
+        args: WidgetArgs | None = None,
+        kwargs: WidgetKwargs | None = None,
         *,  # keyword-only arguments:
         format: str = "YYYY/MM/DD",
         disabled: bool = False,
@@ -463,7 +476,9 @@ def date_input(
         value : datetime.date or datetime.datetime or list/tuple of datetime.date or datetime.datetime or None
             The value of this widget when it first renders. If a list/tuple with
             0 to 2 date/datetime values is provided, the datepicker will allow
-            users to provide a range. Defaults to today as a single-date picker.
+            users to provide a range. If ``None``, will initialize empty and
+            return ``None`` until the user provides input. Defaults to today as
+            a single-date picker.
         min_value : datetime.date or datetime.datetime
             The minimum selectable date. If value is a date, defaults to value - 10 years.
             If value is the interval [start, end], defaults to start - 10 years.
@@ -499,8 +514,9 @@ def date_input(
 
         Returns
         -------
-        datetime.date or a tuple with 0-2 dates
-            The current value of the date input widget.
+        datetime.date or a tuple with 0-2 dates or None
+            The current value of the date input widget or ``None`` if no date has been
+            selected.
 
         Examples
         --------
@@ -535,6 +551,16 @@ def date_input(
            https://doc-date-input1.streamlit.app/
            height: 380px
 
+        >>> import datetime
+        >>> import streamlit as st
+        >>>
+        >>> d = st.date_input("When's your birthday", value=None)
+        >>> st.write('Your birthday is:', d)
+
+        .. output::
+           https://doc-date-input-empty.streamlit.app/
+           height: 380px
+
         """
         ctx = get_script_run_ctx()
         return self._date_input(
@@ -556,19 +582,19 @@ def date_input(
     def _date_input(
         self,
         label: str,
-        value: DateValue = None,
+        value: DateValue | ellipsis = ...,
         min_value: SingleDateValue = None,
         max_value: SingleDateValue = None,
-        key: Optional[Key] = None,
-        help: Optional[str] = None,
-        on_change: Optional[WidgetCallback] = None,
-        args: Optional[WidgetArgs] = None,
-        kwargs: Optional[WidgetKwargs] = None,
+        key: Key | None = None,
+        help: str | None = None,
+        on_change: WidgetCallback | None = None,
+        args: WidgetArgs | None = None,
+        kwargs: WidgetKwargs | None = None,
         *,  # keyword-only arguments:
         format: str = "YYYY/MM/DD",
         disabled: bool = False,
         label_visibility: LabelVisibility = "visible",
-        ctx: Optional[ScriptRunContext] = None,
+        ctx: ScriptRunContext | None = None,
     ) -> DateWidgetReturn:
         key = to_key(key)
         check_callback_rules(self.dg, on_change)
@@ -576,19 +602,21 @@ def _date_input(
 
         maybe_raise_label_warnings(label, label_visibility)
 
-        def parse_date_deterministic(v: SingleDateValue) -> str | None:
-            if v is None:
-                return None
-            elif isinstance(v, datetime):
+        def parse_date_deterministic(v: SingleDateValue | ellipsis) -> str | None:
+            if isinstance(v, datetime):
                 return date.strftime(v.date(), "%Y/%m/%d")
             elif isinstance(v, date):
                 return date.strftime(v, "%Y/%m/%d")
+            return None
 
         parsed_min_date = parse_date_deterministic(min_value)
         parsed_max_date = parse_date_deterministic(max_value)
 
-        if isinstance(value, datetime) or isinstance(value, date) or value is None:
-            parsed: str | None | List[str | None] = parse_date_deterministic(value)
+        parsed: str | None | List[str | None]
+        if value is DefaultValue or value is None:
+            parsed = None
+        elif isinstance(value, (datetime, date)):
+            parsed = parse_date_deterministic(value)
         else:
             parsed = [parse_date_deterministic(v) for v in value]
 
@@ -630,9 +658,12 @@ def parse_date_deterministic(v: SingleDateValue) -> str | None:
         )
         date_input_proto.format = format
         date_input_proto.label = label
-        date_input_proto.default[:] = [
-            date.strftime(v, "%Y/%m/%d") for v in parsed_values.value
-        ]
+        if parsed_values.value is None:
+            date_input_proto.default[:] = []
+        else:
+            date_input_proto.default[:] = [
+                date.strftime(v, "%Y/%m/%d") for v in parsed_values.value
+            ]
         date_input_proto.min = date.strftime(parsed_values.min, "%Y/%m/%d")
         date_input_proto.max = date.strftime(parsed_values.max, "%Y/%m/%d")
         date_input_proto.form_id = current_form_id(self.dg)
diff --git a/lib/streamlit/testing/element_tree.py b/lib/streamlit/testing/element_tree.py
index 7e5787435524..d5483f89f87a 100644
--- a/lib/streamlit/testing/element_tree.py
+++ b/lib/streamlit/testing/element_tree.py
@@ -292,12 +292,12 @@ def pick(self, v: str) -> ColorPicker:
 
 
 SingleDateValue: TypeAlias = Union[date, datetime]
-DateValue: TypeAlias = Union[SingleDateValue, Sequence[SingleDateValue]]
+DateValue: TypeAlias = Union[SingleDateValue, Sequence[SingleDateValue], None]
 
 
 @dataclass(repr=False)
 class DateInput(Widget):
-    _value: DateValue | None
+    _value: DateValue | None | InitialValue
     proto: DateInputProto
     min: date
     max: date
@@ -306,7 +306,7 @@ class DateInput(Widget):
     def __init__(self, proto: DateInputProto, root: ElementTree):
         self.proto = proto
         self.root = root
-        self._value = None
+        self._value = InitialValue()
 
         self.type = "date_input"
         self.id = proto.id
@@ -333,9 +333,9 @@ def widget_state(self) -> WidgetState:
 
     @property
     def value(self) -> DateWidgetReturn:
-        if self._value is not None:
+        if not isinstance(self._value, InitialValue):
             parsed, _ = _parse_date_value(self._value)
-            return tuple(parsed)  # type: ignore
+            return tuple(parsed) if parsed is not None else None  # type: ignore
         else:
             state = self.root.session_state
             assert state
diff --git a/lib/tests/streamlit/elements/date_input_test.py b/lib/tests/streamlit/elements/date_input_test.py
index 7be7d3aff97b..6b791fd97cfd 100644
--- a/lib/tests/streamlit/elements/date_input_test.py
+++ b/lib/tests/streamlit/elements/date_input_test.py
@@ -22,6 +22,7 @@
 import streamlit as st
 from streamlit.errors import StreamlitAPIException
 from streamlit.proto.LabelVisibilityMessage_pb2 import LabelVisibilityMessage
+from streamlit.testing.script_interactions import InteractiveScriptTests
 from tests.delta_generator_test_case import DeltaGeneratorTestCase
 
 
@@ -50,6 +51,16 @@ def test_just_disabled(self):
         c = self.get_delta_from_queue().new_element.date_input
         self.assertEqual(c.disabled, True)
 
+    def test_none_value(self):
+        """Test that it can be called with None as value."""
+        st.date_input("the label", value=None)
+
+        c = self.get_delta_from_queue().new_element.date_input
+        self.assertEqual(c.label, "the label")
+        # If a proto property is null is not determined by this value,
+        # but by the check via the HasField method:
+        self.assertEqual(c.default, [])
+
     @parameterized.expand(
         [
             (date(1970, 1, 1), ["1970/01/01"]),
@@ -254,3 +265,28 @@ def test_invalid_date_format_values(self, format: str):
         with self.assertRaises(StreamlitAPIException) as ex:
             st.date_input("the label", format=format)
         self.assertTrue(str(ex.exception).startswith("The provided format"))
+
+
+class DateInputInteractiveTest(InteractiveScriptTests):
+    def test_date_input_interaction(self):
+        """Test interactions with an empty date_input widget."""
+        script = self.script_from_string(
+            """
+        import streamlit as st
+
+        st.date_input("the label", value=None)
+        """
+        )
+        sr = script.run()
+        date_input = sr.date_input[0]
+        assert date_input.value is None
+
+        # Set the value to 10
+        sr2 = date_input.set_value(date(2012, 1, 3)).run()
+        date_input = sr2.date_input[0]
+        assert date_input.value == date(2012, 1, 3)
+
+        # # Clear the value
+        sr3 = date_input.set_value(None).run()
+        date_input = sr3.date_input[0]
+        assert date_input.value is None
diff --git a/lib/tests/streamlit/elements/element_utils_test.py b/lib/tests/streamlit/elements/element_utils_test.py
index 31a5a7082203..fbf9576325f2 100644
--- a/lib/tests/streamlit/elements/element_utils_test.py
+++ b/lib/tests/streamlit/elements/element_utils_test.py
@@ -20,6 +20,7 @@
 import pytest
 
 from streamlit import config
+from streamlit.elements import utils
 from streamlit.elements.utils import check_callback_rules, check_session_state_rules
 from streamlit.errors import StreamlitAPIException
 
@@ -107,6 +108,8 @@ def test_check_session_state_rules_prints_warning(
         mock_session_state = MagicMock()
         mock_session_state.is_new_state_value.return_value = True
         patched_get_session_state.return_value = mock_session_state
+        # Reset globale flag:
+        utils._shown_default_value_warning = False
 
         check_session_state_rules(5, key="the key")
 


Code review comments:
diff_hunk: @@ -254,3 +265,28 @@ def test_invalid_date_format_values(self, format: str):
         with self.assertRaises(StreamlitAPIException) as ex:
             st.date_input("the label", format=format)
         self.assertTrue(str(ex.exception).startswith("The provided format"))
+
+
+class DateInputInteractiveTest(InteractiveScriptTests):
+    def test_date_input_interaction(self):
+        """Test interactions with an empty date_input widget."""
+        script = self.script_from_string(
+            """
+        import streamlit as st
+
+        st.date_input("the label", value=None)
+        """
+        )
+        sr = script.run()
+        date_input = sr.date_input[0]
+        assert date_input.value is None
+
+        # Set the value to 10
path: lib/tests/streamlit/elements/date_input_test.py
text: @user1:
Nit: Set it to 10?

@author:
Whoops, this is from number input copy/paste...