Repository: streamlit/streamlit

Issue:
st.selectbox - No selection per default 
### Problem

I believe it is currently not possible to use a selectbox that has nothing selected per default. This may be undesirable in some cases.

### Solution

Replace the `index`parameter with a `default` parameter just like the multiselect item does. Problem: This is a breaking change.

Possibly better: Instead of throwing an error when the provided index is not within the list, return `None` and show an empty selection in the UI instead of throwing an error. Problem: No explicit error is being raised in case the index out of bound in an unwanted scenario.

Possibly possibly better: An index of `-1` indicates an empty selection and `None` is returned. This might be a good balance since negative values are currently forbidden.

---

Community voting on feature requests enables the Streamlit team to understand which features are most important to our users.

**If you'd like the Streamlit team to prioritize this feature request, please use the 👍 (thumbs up emoji) reaction in response to the initial post.**



Discussion:
Here is a workaround:
Example: display a map only if a valid zoom level is selected:
```
map_zoom = st.selectbox('Zoom level', 
                        ['<select>', 12, 11, 10, 9, 8, 7],
                        0)  # default value = index 0
if map_zoom != '<select>':    
        st.map(data, zoom=map_zoom)
```
That's correct there's no actual way to have an empty selectbox, but for now you could do something like this using an empty string plus the `format_func` or even using just the empty string

```
import streamlit as st

selected = st.selectbox('Select one option:', ['', 'First one', 'Second one'], format_func=lambda x: 'Select an option' if x == '' else x)

if selected:
    st.success('Yay! 🎉')
else:
    st.warning('No option is selected')

```
A clean solution would probably need to have a dedicated default value that cannot be mistaken for any actual value ( `` could possibly be a valid option to select). I didn't think of the `format_func` to actually format this exceptional value and thus had to use a string, but it's a very good point.

For reference, you'll find below the wrapper I wrote in December to handle a very similar use case.

```python
DEFAULT = '< PICK A VALUE >'

def selectbox_with_default(text, values, default=DEFAULT, sidebar=False):
    func = st.sidebar.selectbox if sidebar else st.selectbox
    return func(text, np.insert(np.array(values, object), 0, default))

```

I'd then call it from the `app.py` code with
```python
res = selectbox_with_default('some title', some_values)
if res == DEFAULT:
    st.warning("Please fill all the fields !")
    raise StopException

```


I would find it very useful to be able to have an selection box without a default value selected!
declare the variable before the select box

res=some_values
res = selectbox_with_default('some title', some_values)

[Here](https://extras.streamlit.app/No-Default%20Selectbox) is a third-party solution which has this behavior. This is part of the [streamlit-extras](https://github.com/arnaudmiribel/streamlit-extras) package.

```python
from streamlit_extras.no_default_selectbox import selectbox

result = selectbox("Select an option", ["A", "B", "C"])
st.write("Result:", result)
```

This returns `None` until the user selects one of the options.
We have this on our roadmap. No concrete date yet but I'd love to get it out soon-ish. 
Any idea in case of Striemlit selectbox component how we can handle showing the section option from start instead show based on the option select in select box..
We're working on this right now. It will work by setting `index=None`. See a preview here:

https://github.com/streamlit/streamlit/assets/127552870/f25119e3-c16d-4246-85e9-a0321af8d974




@jrieke "We're working on this right now. It will work by setting `index=None`. See a preview here:" which version is this working in?
As I tried the same with version 1.24.0 as below but it's not working throws an error.
`
option = st.selectbox(
            'How would you like to be contacted?',
            ('Email', 'Home phone', 'Mobile phone'),index=None)

        st.write('You selected:', option)
`




We didn't shop this yet. Got interrupted due to some other high-priority projects (see the linked PR above). But we plan to pick it back up. 

Resolving patch:
diff --git a/e2e_playwright/st_selectbox.py b/e2e_playwright/st_selectbox.py
new file mode 100644
--- /dev/null
+++ b/e2e_playwright/st_selectbox.py
@@ -0,0 +1,92 @@
+# 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.
+
+import pandas as pd
+
+import streamlit as st
+from streamlit import runtime
+
+options = ("male", "female")
+i1 = st.selectbox("selectbox 1 (default)", options)
+st.write("value 1:", i1)
+
+i2 = st.selectbox(
+    "selectbox 2 (formatted options)", options, 1, format_func=lambda x: x.capitalize()
+)
+st.write("value 2:", i2)
+
+i3 = st.selectbox("selectbox 3 (no options)", [])
+st.write("value 3:", i3)
+
+more_options = [
+    "e2e/scripts/components_iframe.py",
+    "e2e/scripts/st_warning.py",
+    "This is a very very very long option label that should be truncated when it is showing in the dropdown menu.",
+    "e2e/scripts/st_container.py",
+    "e2e/scripts/st_dataframe_sort_column.py",
+    "e2e/scripts/app_hotkeys.py",
+    "e2e/scripts/st_info.py",
+    "e2e/scripts/st_echo.py",
+    "e2e/scripts/st_json.py",
+    "e2e/scripts/st_experimental_get_query_params.py",
+    "e2e/scripts/st_markdown.py",
+    "e2e/scripts/st_color_picker.py",
+    "e2e/scripts/st_expander.py",
+]
+i4 = st.selectbox("selectbox 4 (more options)", more_options, 0)
+st.write("value 4:", i4)
+
+i5 = st.selectbox("selectbox 5 (disabled)", options, disabled=True)
+st.write("value 5:", i5)
+
+i6 = st.selectbox("selectbox 6 (hidden label)", options, label_visibility="hidden")
+st.write("value 6:", i6)
+
+i7 = st.selectbox(
+    "selectbox 7 (collapsed label)", options, label_visibility="collapsed"
+)
+st.write("value 7:", i7)
+
+if runtime.exists():
+
+    def on_change():
+        st.session_state.selectbox_changed = True
+        st.text("Selectbox widget callback triggered")
+
+    st.selectbox(
+        "selectbox 8 (with callback, help)",
+        options,
+        1,
+        key="selectbox8",
+        on_change=on_change,
+        help="Help text",
+    )
+    st.write("value 8:", st.session_state.selectbox8)
+    st.write("selectbox changed:", "selectbox_changed" in st.session_state)
+
+i9 = st.selectbox("selectbox 9 (empty selection)", options, index=None)
+st.write("value 9:", i9)
+
+i10 = st.selectbox(
+    "selectbox 10 (empty, custom placeholder)",
+    options,
+    index=None,
+    placeholder="Select one of the options...",
+)
+st.write("value 10:", i10)
+
+i11 = st.selectbox(
+    "selectbox 11 (options from dataframe)", pd.DataFrame({"foo": list(options)})
+)
+st.write("value 11:", i11)
diff --git a/lib/streamlit/elements/widgets/selectbox.py b/lib/streamlit/elements/widgets/selectbox.py
--- a/lib/streamlit/elements/widgets/selectbox.py
+++ b/lib/streamlit/elements/widgets/selectbox.py
@@ -11,10 +11,11 @@
 # 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 __future__ import annotations
 
 from dataclasses import dataclass
 from textwrap import dedent
-from typing import TYPE_CHECKING, Any, Callable, Generic, Optional, Sequence, cast
+from typing import TYPE_CHECKING, Any, Callable, Generic, Sequence, cast
 
 from streamlit.elements.form import current_form_id
 from streamlit.elements.utils import (
@@ -51,21 +52,22 @@
 @dataclass
 class SelectboxSerde(Generic[T]):
     options: Sequence[T]
-    index: int
+    index: int | None
 
-    def serialize(self, v: object) -> int:
+    def serialize(self, v: object) -> int | None:
+        if v is None:
+            return None
         if len(self.options) == 0:
             return 0
         return index_(self.options, v)
 
     def deserialize(
         self,
-        ui_value: Optional[int],
+        ui_value: int | None,
         widget_id: str = "",
-    ) -> Optional[T]:
-        idx: int = ui_value if ui_value is not None else self.index
-
-        return self.options[idx] if len(self.options) > 0 else None
+    ) -> T | None:
+        idx = ui_value if ui_value is not None else self.index
+        return self.options[idx] if idx is not None and len(self.options) > 0 else None
 
 
 class SelectboxMixin:
@@ -74,18 +76,18 @@ def selectbox(
         self,
         label: str,
         options: OptionSequence[T],
-        index: int = 0,
+        index: int | None = 0,
         format_func: Callable[[Any], Any] = str,
-        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:
         placeholder: str = "Select...",
         disabled: bool = False,
         label_visibility: LabelVisibility = "visible",
-    ) -> Optional[T]:
+    ) -> T | None:
         r"""Display a select widget.
 
         Parameters
@@ -120,7 +122,9 @@ def selectbox(
             Labels for the select options. This will be cast to str internally
             by default. For pandas.DataFrame, the first column is selected.
         index : int
-            The index of the preselected option on first render.
+            The index of the preselected option on first render. If ``None``,
+            will initialize empty and return ``None`` until the user selects an option.
+            Defaults to 0 (the first option).
         format_func : function
             Function to modify the display of the labels. It receives the option
             as an argument and its output will be cast to str.
@@ -139,10 +143,6 @@ def selectbox(
             An optional dict of kwargs to pass to the callback.
         placeholder : str
             A string to display when no options are selected. Defaults to 'Select...'.
-
-            A selectbox can't be empty, so a placeholder only displays while a
-            user's cursor is in a selectbox after manually deleting the current
-            selection. A future update will allow selectboxes to be empty.
         disabled : bool
             An optional boolean, which disables the selectbox if set to True.
             The default is False. This argument can only be supplied by keyword.
@@ -155,7 +155,7 @@ def selectbox(
         Returns
         -------
         any
-            The selected option
+            The selected option or ``None`` if no option is selected.
 
         Example
         -------
@@ -171,6 +171,23 @@ def selectbox(
            https://doc-selectbox.streamlit.app/
            height: 320px
 
+        To initialize an empty selectbox, use ``None`` as the index value:
+
+        >>> import streamlit as st
+        >>>
+        >>> option = st.selectbox(
+        ...    "How would you like to be contacted?",
+        ...    ("Email", "Home phone", "Mobile phone"),
+        ...    index=None,
+        ...    placeholder="Select contact method...",
+        ... )
+        >>>
+        >>> st.write('You selected:', option)
+
+        .. output::
+           https://doc-selectbox-empty.streamlit.app/
+           height: 320px
+
         """
         ctx = get_script_run_ctx()
         return self._selectbox(
@@ -193,19 +210,19 @@ def _selectbox(
         self,
         label: str,
         options: OptionSequence[T],
-        index: int = 0,
+        index: int | None = 0,
         format_func: Callable[[Any], Any] = str,
-        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:
         placeholder: str = "Select...",
         disabled: bool = False,
         label_visibility: LabelVisibility = "visible",
-        ctx: Optional[ScriptRunContext] = None,
-    ) -> Optional[T]:
+        ctx: ScriptRunContext | None = None,
+    ) -> T | None:
         key = to_key(key)
         check_callback_rules(self.dg, on_change)
         check_session_state_rules(default_value=None if index == 0 else index, key=key)
@@ -227,12 +244,12 @@ def _selectbox(
             page=ctx.page_script_hash if ctx else None,
         )
 
-        if not isinstance(index, int):
+        if not isinstance(index, int) and index is not None:
             raise StreamlitAPIException(
                 "Selectbox Value has invalid type: %s" % type(index).__name__
             )
 
-        if len(opt) > 0 and not 0 <= index < len(opt):
+        if index is not None and len(opt) > 0 and not 0 <= index < len(opt):
             raise StreamlitAPIException(
                 "Selectbox index must be between 0 and length of options"
             )
@@ -240,7 +257,8 @@ def _selectbox(
         selectbox_proto = SelectboxProto()
         selectbox_proto.id = id
         selectbox_proto.label = label
-        selectbox_proto.default = index
+        if index is not None:
+            selectbox_proto.default = index
         selectbox_proto.options[:] = [str(format_func(option)) for option in opt]
         selectbox_proto.form_id = current_form_id(self.dg)
         selectbox_proto.placeholder = placeholder
@@ -267,7 +285,9 @@ def _selectbox(
         )
 
         if widget_state.value_changed:
-            selectbox_proto.value = serde.serialize(widget_state.value)
+            serialized_value = serde.serialize(widget_state.value)
+            if serialized_value is not None:
+                selectbox_proto.value = serialized_value
             selectbox_proto.set_value = True
 
         self.dg._enqueue("selectbox", selectbox_proto)


Test patch:
diff --git a/e2e_playwright/__snapshots__/linux/st_selectbox_test/st_selectbox-callback_help[dark_theme-chromium].png b/e2e_playwright/__snapshots__/linux/st_selectbox_test/st_selectbox-callback_help[dark_theme-chromium].png
new file mode 100644
Binary files /dev/null and b/e2e_playwright/__snapshots__/linux/st_selectbox_test/st_selectbox-callback_help[dark_theme-chromium].png differ
diff --git a/e2e_playwright/__snapshots__/linux/st_selectbox_test/st_selectbox-callback_help[dark_theme-firefox].png b/e2e_playwright/__snapshots__/linux/st_selectbox_test/st_selectbox-callback_help[dark_theme-firefox].png
new file mode 100644
Binary files /dev/null and b/e2e_playwright/__snapshots__/linux/st_selectbox_test/st_selectbox-callback_help[dark_theme-firefox].png differ
diff --git a/e2e_playwright/__snapshots__/linux/st_selectbox_test/st_selectbox-callback_help[dark_theme-webkit].png b/e2e_playwright/__snapshots__/linux/st_selectbox_test/st_selectbox-callback_help[dark_theme-webkit].png
new file mode 100644
Binary files /dev/null and b/e2e_playwright/__snapshots__/linux/st_selectbox_test/st_selectbox-callback_help[dark_theme-webkit].png differ
diff --git a/e2e_playwright/__snapshots__/linux/st_selectbox_test/st_selectbox-callback_help[light_theme-chromium].png b/e2e_playwright/__snapshots__/linux/st_selectbox_test/st_selectbox-callback_help[light_theme-chromium].png
new file mode 100644
Binary files /dev/null and b/e2e_playwright/__snapshots__/linux/st_selectbox_test/st_selectbox-callback_help[light_theme-chromium].png differ
diff --git a/e2e_playwright/__snapshots__/linux/st_selectbox_test/st_selectbox-callback_help[light_theme-firefox].png b/e2e_playwright/__snapshots__/linux/st_selectbox_test/st_selectbox-callback_help[light_theme-firefox].png
new file mode 100644
Binary files /dev/null and b/e2e_playwright/__snapshots__/linux/st_selectbox_test/st_selectbox-callback_help[light_theme-firefox].png differ
diff --git a/e2e_playwright/__snapshots__/linux/st_selectbox_test/st_selectbox-callback_help[light_theme-webkit].png b/e2e_playwright/__snapshots__/linux/st_selectbox_test/st_selectbox-callback_help[light_theme-webkit].png
new file mode 100644
Binary files /dev/null and b/e2e_playwright/__snapshots__/linux/st_selectbox_test/st_selectbox-callback_help[light_theme-webkit].png differ
diff --git a/e2e_playwright/__snapshots__/linux/st_selectbox_test/st_selectbox-clearable_input[chromium].png b/e2e_playwright/__snapshots__/linux/st_selectbox_test/st_selectbox-clearable_input[chromium].png
new file mode 100644
Binary files /dev/null and b/e2e_playwright/__snapshots__/linux/st_selectbox_test/st_selectbox-clearable_input[chromium].png differ
diff --git a/e2e_playwright/__snapshots__/linux/st_selectbox_test/st_selectbox-clearable_input[firefox].png b/e2e_playwright/__snapshots__/linux/st_selectbox_test/st_selectbox-clearable_input[firefox].png
new file mode 100644
Binary files /dev/null and b/e2e_playwright/__snapshots__/linux/st_selectbox_test/st_selectbox-clearable_input[firefox].png differ
diff --git a/e2e_playwright/__snapshots__/linux/st_selectbox_test/st_selectbox-clearable_input[webkit].png b/e2e_playwright/__snapshots__/linux/st_selectbox_test/st_selectbox-clearable_input[webkit].png
new file mode 100644
Binary files /dev/null and b/e2e_playwright/__snapshots__/linux/st_selectbox_test/st_selectbox-clearable_input[webkit].png differ
diff --git a/e2e_playwright/__snapshots__/linux/st_selectbox_test/st_selectbox-collapsed_label[dark_theme-chromium].png b/e2e_playwright/__snapshots__/linux/st_selectbox_test/st_selectbox-collapsed_label[dark_theme-chromium].png
new file mode 100644
Binary files /dev/null and b/e2e_playwright/__snapshots__/linux/st_selectbox_test/st_selectbox-collapsed_label[dark_theme-chromium].png differ
diff --git a/e2e_playwright/__snapshots__/linux/st_selectbox_test/st_selectbox-collapsed_label[dark_theme-firefox].png b/e2e_playwright/__snapshots__/linux/st_selectbox_test/st_selectbox-collapsed_label[dark_theme-firefox].png
new file mode 100644
Binary files /dev/null and b/e2e_playwright/__snapshots__/linux/st_selectbox_test/st_selectbox-collapsed_label[dark_theme-firefox].png differ
diff --git a/e2e_playwright/__snapshots__/linux/st_selectbox_test/st_selectbox-collapsed_label[dark_theme-webkit].png b/e2e_playwright/__snapshots__/linux/st_selectbox_test/st_selectbox-collapsed_label[dark_theme-webkit].png
new file mode 100644
Binary files /dev/null and b/e2e_playwright/__snapshots__/linux/st_selectbox_test/st_selectbox-collapsed_label[dark_theme-webkit].png differ
diff --git a/e2e_playwright/__snapshots__/linux/st_selectbox_test/st_selectbox-collapsed_label[light_theme-chromium].png b/e2e_playwright/__snapshots__/linux/st_selectbox_test/st_selectbox-collapsed_label[light_theme-chromium].png
new file mode 100644
Binary files /dev/null and b/e2e_playwright/__snapshots__/linux/st_selectbox_test/st_selectbox-collapsed_label[light_theme-chromium].png differ
diff --git a/e2e_playwright/__snapshots__/linux/st_selectbox_test/st_selectbox-collapsed_label[light_theme-firefox].png b/e2e_playwright/__snapshots__/linux/st_selectbox_test/st_selectbox-collapsed_label[light_theme-firefox].png
new file mode 100644
Binary files /dev/null and b/e2e_playwright/__snapshots__/linux/st_selectbox_test/st_selectbox-collapsed_label[light_theme-firefox].png differ
diff --git a/e2e_playwright/__snapshots__/linux/st_selectbox_test/st_selectbox-collapsed_label[light_theme-webkit].png b/e2e_playwright/__snapshots__/linux/st_selectbox_test/st_selectbox-collapsed_label[light_theme-webkit].png
new file mode 100644
Binary files /dev/null and b/e2e_playwright/__snapshots__/linux/st_selectbox_test/st_selectbox-collapsed_label[light_theme-webkit].png differ
diff --git a/e2e_playwright/__snapshots__/linux/st_selectbox_test/st_selectbox-dataframe_options[dark_theme-chromium].png b/e2e_playwright/__snapshots__/linux/st_selectbox_test/st_selectbox-dataframe_options[dark_theme-chromium].png
new file mode 100644
Binary files /dev/null and b/e2e_playwright/__snapshots__/linux/st_selectbox_test/st_selectbox-dataframe_options[dark_theme-chromium].png differ
diff --git a/e2e_playwright/__snapshots__/linux/st_selectbox_test/st_selectbox-dataframe_options[dark_theme-firefox].png b/e2e_playwright/__snapshots__/linux/st_selectbox_test/st_selectbox-dataframe_options[dark_theme-firefox].png
new file mode 100644
Binary files /dev/null and b/e2e_playwright/__snapshots__/linux/st_selectbox_test/st_selectbox-dataframe_options[dark_theme-firefox].png differ
diff --git a/e2e_playwright/__snapshots__/linux/st_selectbox_test/st_selectbox-dataframe_options[dark_theme-webkit].png b/e2e_playwright/__snapshots__/linux/st_selectbox_test/st_selectbox-dataframe_options[dark_theme-webkit].png
new file mode 100644
Binary files /dev/null and b/e2e_playwright/__snapshots__/linux/st_selectbox_test/st_selectbox-dataframe_options[dark_theme-webkit].png differ
diff --git a/e2e_playwright/__snapshots__/linux/st_selectbox_test/st_selectbox-dataframe_options[light_theme-chromium].png b/e2e_playwright/__snapshots__/linux/st_selectbox_test/st_selectbox-dataframe_options[light_theme-chromium].png
new file mode 100644
Binary files /dev/null and b/e2e_playwright/__snapshots__/linux/st_selectbox_test/st_selectbox-dataframe_options[light_theme-chromium].png differ
diff --git a/e2e_playwright/__snapshots__/linux/st_selectbox_test/st_selectbox-dataframe_options[light_theme-firefox].png b/e2e_playwright/__snapshots__/linux/st_selectbox_test/st_selectbox-dataframe_options[light_theme-firefox].png
new file mode 100644
Binary files /dev/null and b/e2e_playwright/__snapshots__/linux/st_selectbox_test/st_selectbox-dataframe_options[light_theme-firefox].png differ
diff --git a/e2e_playwright/__snapshots__/linux/st_selectbox_test/st_selectbox-dataframe_options[light_theme-webkit].png b/e2e_playwright/__snapshots__/linux/st_selectbox_test/st_selectbox-dataframe_options[light_theme-webkit].png
new file mode 100644
Binary files /dev/null and b/e2e_playwright/__snapshots__/linux/st_selectbox_test/st_selectbox-dataframe_options[light_theme-webkit].png differ
diff --git a/e2e_playwright/__snapshots__/linux/st_selectbox_test/st_selectbox-default[dark_theme-chromium].png b/e2e_playwright/__snapshots__/linux/st_selectbox_test/st_selectbox-default[dark_theme-chromium].png
new file mode 100644
Binary files /dev/null and b/e2e_playwright/__snapshots__/linux/st_selectbox_test/st_selectbox-default[dark_theme-chromium].png differ
diff --git a/e2e_playwright/__snapshots__/linux/st_selectbox_test/st_selectbox-default[dark_theme-firefox].png b/e2e_playwright/__snapshots__/linux/st_selectbox_test/st_selectbox-default[dark_theme-firefox].png
new file mode 100644
Binary files /dev/null and b/e2e_playwright/__snapshots__/linux/st_selectbox_test/st_selectbox-default[dark_theme-firefox].png differ
diff --git a/e2e_playwright/__snapshots__/linux/st_selectbox_test/st_selectbox-default[dark_theme-webkit].png b/e2e_playwright/__snapshots__/linux/st_selectbox_test/st_selectbox-default[dark_theme-webkit].png
new file mode 100644
Binary files /dev/null and b/e2e_playwright/__snapshots__/linux/st_selectbox_test/st_selectbox-default[dark_theme-webkit].png differ
diff --git a/e2e_playwright/__snapshots__/linux/st_selectbox_test/st_selectbox-default[light_theme-chromium].png b/e2e_playwright/__snapshots__/linux/st_selectbox_test/st_selectbox-default[light_theme-chromium].png
new file mode 100644
Binary files /dev/null and b/e2e_playwright/__snapshots__/linux/st_selectbox_test/st_selectbox-default[light_theme-chromium].png differ
diff --git a/e2e_playwright/__snapshots__/linux/st_selectbox_test/st_selectbox-default[light_theme-firefox].png b/e2e_playwright/__snapshots__/linux/st_selectbox_test/st_selectbox-default[light_theme-firefox].png
new file mode 100644
Binary files /dev/null and b/e2e_playwright/__snapshots__/linux/st_selectbox_test/st_selectbox-default[light_theme-firefox].png differ
diff --git a/e2e_playwright/__snapshots__/linux/st_selectbox_test/st_selectbox-default[light_theme-webkit].png b/e2e_playwright/__snapshots__/linux/st_selectbox_test/st_selectbox-default[light_theme-webkit].png
new file mode 100644
Binary files /dev/null and b/e2e_playwright/__snapshots__/linux/st_selectbox_test/st_selectbox-default[light_theme-webkit].png differ
diff --git a/e2e_playwright/__snapshots__/linux/st_selectbox_test/st_selectbox-disabled[dark_theme-chromium].png b/e2e_playwright/__snapshots__/linux/st_selectbox_test/st_selectbox-disabled[dark_theme-chromium].png
new file mode 100644
Binary files /dev/null and b/e2e_playwright/__snapshots__/linux/st_selectbox_test/st_selectbox-disabled[dark_theme-chromium].png differ
diff --git a/e2e_playwright/__snapshots__/linux/st_selectbox_test/st_selectbox-disabled[dark_theme-firefox].png b/e2e_playwright/__snapshots__/linux/st_selectbox_test/st_selectbox-disabled[dark_theme-firefox].png
new file mode 100644
Binary files /dev/null and b/e2e_playwright/__snapshots__/linux/st_selectbox_test/st_selectbox-disabled[dark_theme-firefox].png differ
diff --git a/e2e_playwright/__snapshots__/linux/st_selectbox_test/st_selectbox-disabled[dark_theme-webkit].png b/e2e_playwright/__snapshots__/linux/st_selectbox_test/st_selectbox-disabled[dark_theme-webkit].png
new file mode 100644
Binary files /dev/null and b/e2e_playwright/__snapshots__/linux/st_selectbox_test/st_selectbox-disabled[dark_theme-webkit].png differ
diff --git a/e2e_playwright/__snapshots__/linux/st_selectbox_test/st_selectbox-disabled[light_theme-chromium].png b/e2e_playwright/__snapshots__/linux/st_selectbox_test/st_selectbox-disabled[light_theme-chromium].png
new file mode 100644
Binary files /dev/null and b/e2e_playwright/__snapshots__/linux/st_selectbox_test/st_selectbox-disabled[light_theme-chromium].png differ
diff --git a/e2e_playwright/__snapshots__/linux/st_selectbox_test/st_selectbox-disabled[light_theme-firefox].png b/e2e_playwright/__snapshots__/linux/st_selectbox_test/st_selectbox-disabled[light_theme-firefox].png
new file mode 100644
Binary files /dev/null and b/e2e_playwright/__snapshots__/linux/st_selectbox_test/st_selectbox-disabled[light_theme-firefox].png differ
diff --git a/e2e_playwright/__snapshots__/linux/st_selectbox_test/st_selectbox-disabled[light_theme-webkit].png b/e2e_playwright/__snapshots__/linux/st_selectbox_test/st_selectbox-disabled[light_theme-webkit].png
new file mode 100644
Binary files /dev/null and b/e2e_playwright/__snapshots__/linux/st_selectbox_test/st_selectbox-disabled[light_theme-webkit].png differ
diff --git a/e2e_playwright/__snapshots__/linux/st_selectbox_test/st_selectbox-empty_selection[dark_theme-chromium].png b/e2e_playwright/__snapshots__/linux/st_selectbox_test/st_selectbox-empty_selection[dark_theme-chromium].png
new file mode 100644
Binary files /dev/null and b/e2e_playwright/__snapshots__/linux/st_selectbox_test/st_selectbox-empty_selection[dark_theme-chromium].png differ
diff --git a/e2e_playwright/__snapshots__/linux/st_selectbox_test/st_selectbox-empty_selection[dark_theme-firefox].png b/e2e_playwright/__snapshots__/linux/st_selectbox_test/st_selectbox-empty_selection[dark_theme-firefox].png
new file mode 100644
Binary files /dev/null and b/e2e_playwright/__snapshots__/linux/st_selectbox_test/st_selectbox-empty_selection[dark_theme-firefox].png differ
diff --git a/e2e_playwright/__snapshots__/linux/st_selectbox_test/st_selectbox-empty_selection[dark_theme-webkit].png b/e2e_playwright/__snapshots__/linux/st_selectbox_test/st_selectbox-empty_selection[dark_theme-webkit].png
new file mode 100644
Binary files /dev/null and b/e2e_playwright/__snapshots__/linux/st_selectbox_test/st_selectbox-empty_selection[dark_theme-webkit].png differ
diff --git a/e2e_playwright/__snapshots__/linux/st_selectbox_test/st_selectbox-empty_selection[light_theme-chromium].png b/e2e_playwright/__snapshots__/linux/st_selectbox_test/st_selectbox-empty_selection[light_theme-chromium].png
new file mode 100644
Binary files /dev/null and b/e2e_playwright/__snapshots__/linux/st_selectbox_test/st_selectbox-empty_selection[light_theme-chromium].png differ
diff --git a/e2e_playwright/__snapshots__/linux/st_selectbox_test/st_selectbox-empty_selection[light_theme-firefox].png b/e2e_playwright/__snapshots__/linux/st_selectbox_test/st_selectbox-empty_selection[light_theme-firefox].png
new file mode 100644
Binary files /dev/null and b/e2e_playwright/__snapshots__/linux/st_selectbox_test/st_selectbox-empty_selection[light_theme-firefox].png differ
diff --git a/e2e_playwright/__snapshots__/linux/st_selectbox_test/st_selectbox-empty_selection[light_theme-webkit].png b/e2e_playwright/__snapshots__/linux/st_selectbox_test/st_selectbox-empty_selection[light_theme-webkit].png
new file mode 100644
Binary files /dev/null and b/e2e_playwright/__snapshots__/linux/st_selectbox_test/st_selectbox-empty_selection[light_theme-webkit].png differ
diff --git a/e2e_playwright/__snapshots__/linux/st_selectbox_test/st_selectbox-empty_selection_placeholder[dark_theme-chromium].png b/e2e_playwright/__snapshots__/linux/st_selectbox_test/st_selectbox-empty_selection_placeholder[dark_theme-chromium].png
new file mode 100644
Binary files /dev/null and b/e2e_playwright/__snapshots__/linux/st_selectbox_test/st_selectbox-empty_selection_placeholder[dark_theme-chromium].png differ
diff --git a/e2e_playwright/__snapshots__/linux/st_selectbox_test/st_selectbox-empty_selection_placeholder[dark_theme-firefox].png b/e2e_playwright/__snapshots__/linux/st_selectbox_test/st_selectbox-empty_selection_placeholder[dark_theme-firefox].png
new file mode 100644
Binary files /dev/null and b/e2e_playwright/__snapshots__/linux/st_selectbox_test/st_selectbox-empty_selection_placeholder[dark_theme-firefox].png differ
diff --git a/e2e_playwright/__snapshots__/linux/st_selectbox_test/st_selectbox-empty_selection_placeholder[dark_theme-webkit].png b/e2e_playwright/__snapshots__/linux/st_selectbox_test/st_selectbox-empty_selection_placeholder[dark_theme-webkit].png
new file mode 100644
Binary files /dev/null and b/e2e_playwright/__snapshots__/linux/st_selectbox_test/st_selectbox-empty_selection_placeholder[dark_theme-webkit].png differ
diff --git a/e2e_playwright/__snapshots__/linux/st_selectbox_test/st_selectbox-empty_selection_placeholder[light_theme-chromium].png b/e2e_playwright/__snapshots__/linux/st_selectbox_test/st_selectbox-empty_selection_placeholder[light_theme-chromium].png
new file mode 100644
Binary files /dev/null and b/e2e_playwright/__snapshots__/linux/st_selectbox_test/st_selectbox-empty_selection_placeholder[light_theme-chromium].png differ
diff --git a/e2e_playwright/__snapshots__/linux/st_selectbox_test/st_selectbox-empty_selection_placeholder[light_theme-firefox].png b/e2e_playwright/__snapshots__/linux/st_selectbox_test/st_selectbox-empty_selection_placeholder[light_theme-firefox].png
new file mode 100644
Binary files /dev/null and b/e2e_playwright/__snapshots__/linux/st_selectbox_test/st_selectbox-empty_selection_placeholder[light_theme-firefox].png differ
diff --git a/e2e_playwright/__snapshots__/linux/st_selectbox_test/st_selectbox-empty_selection_placeholder[light_theme-webkit].png b/e2e_playwright/__snapshots__/linux/st_selectbox_test/st_selectbox-empty_selection_placeholder[light_theme-webkit].png
new file mode 100644
Binary files /dev/null and b/e2e_playwright/__snapshots__/linux/st_selectbox_test/st_selectbox-empty_selection_placeholder[light_theme-webkit].png differ
diff --git a/e2e_playwright/__snapshots__/linux/st_selectbox_test/st_selectbox-formatted_options[dark_theme-chromium].png b/e2e_playwright/__snapshots__/linux/st_selectbox_test/st_selectbox-formatted_options[dark_theme-chromium].png
new file mode 100644
Binary files /dev/null and b/e2e_playwright/__snapshots__/linux/st_selectbox_test/st_selectbox-formatted_options[dark_theme-chromium].png differ
diff --git a/e2e_playwright/__snapshots__/linux/st_selectbox_test/st_selectbox-formatted_options[dark_theme-firefox].png b/e2e_playwright/__snapshots__/linux/st_selectbox_test/st_selectbox-formatted_options[dark_theme-firefox].png
new file mode 100644
Binary files /dev/null and b/e2e_playwright/__snapshots__/linux/st_selectbox_test/st_selectbox-formatted_options[dark_theme-firefox].png differ
diff --git a/e2e_playwright/__snapshots__/linux/st_selectbox_test/st_selectbox-formatted_options[dark_theme-webkit].png b/e2e_playwright/__snapshots__/linux/st_selectbox_test/st_selectbox-formatted_options[dark_theme-webkit].png
new file mode 100644
Binary files /dev/null and b/e2e_playwright/__snapshots__/linux/st_selectbox_test/st_selectbox-formatted_options[dark_theme-webkit].png differ
diff --git a/e2e_playwright/__snapshots__/linux/st_selectbox_test/st_selectbox-formatted_options[light_theme-chromium].png b/e2e_playwright/__snapshots__/linux/st_selectbox_test/st_selectbox-formatted_options[light_theme-chromium].png
new file mode 100644
Binary files /dev/null and b/e2e_playwright/__snapshots__/linux/st_selectbox_test/st_selectbox-formatted_options[light_theme-chromium].png differ
diff --git a/e2e_playwright/__snapshots__/linux/st_selectbox_test/st_selectbox-formatted_options[light_theme-firefox].png b/e2e_playwright/__snapshots__/linux/st_selectbox_test/st_selectbox-formatted_options[light_theme-firefox].png
new file mode 100644
Binary files /dev/null and b/e2e_playwright/__snapshots__/linux/st_selectbox_test/st_selectbox-formatted_options[light_theme-firefox].png differ
diff --git a/e2e_playwright/__snapshots__/linux/st_selectbox_test/st_selectbox-formatted_options[light_theme-webkit].png b/e2e_playwright/__snapshots__/linux/st_selectbox_test/st_selectbox-formatted_options[light_theme-webkit].png
new file mode 100644
Binary files /dev/null and b/e2e_playwright/__snapshots__/linux/st_selectbox_test/st_selectbox-formatted_options[light_theme-webkit].png differ
diff --git a/e2e_playwright/__snapshots__/linux/st_selectbox_test/st_selectbox-fuzzy_matching[chromium].png b/e2e_playwright/__snapshots__/linux/st_selectbox_test/st_selectbox-fuzzy_matching[chromium].png
new file mode 100644
Binary files /dev/null and b/e2e_playwright/__snapshots__/linux/st_selectbox_test/st_selectbox-fuzzy_matching[chromium].png differ
diff --git a/e2e_playwright/__snapshots__/linux/st_selectbox_test/st_selectbox-fuzzy_matching[firefox].png b/e2e_playwright/__snapshots__/linux/st_selectbox_test/st_selectbox-fuzzy_matching[firefox].png
new file mode 100644
Binary files /dev/null and b/e2e_playwright/__snapshots__/linux/st_selectbox_test/st_selectbox-fuzzy_matching[firefox].png differ
diff --git a/e2e_playwright/__snapshots__/linux/st_selectbox_test/st_selectbox-fuzzy_matching[webkit].png b/e2e_playwright/__snapshots__/linux/st_selectbox_test/st_selectbox-fuzzy_matching[webkit].png
new file mode 100644
Binary files /dev/null and b/e2e_playwright/__snapshots__/linux/st_selectbox_test/st_selectbox-fuzzy_matching[webkit].png differ
diff --git a/e2e_playwright/__snapshots__/linux/st_selectbox_test/st_selectbox-hidden_label[dark_theme-chromium].png b/e2e_playwright/__snapshots__/linux/st_selectbox_test/st_selectbox-hidden_label[dark_theme-chromium].png
new file mode 100644
Binary files /dev/null and b/e2e_playwright/__snapshots__/linux/st_selectbox_test/st_selectbox-hidden_label[dark_theme-chromium].png differ
diff --git a/e2e_playwright/__snapshots__/linux/st_selectbox_test/st_selectbox-hidden_label[dark_theme-firefox].png b/e2e_playwright/__snapshots__/linux/st_selectbox_test/st_selectbox-hidden_label[dark_theme-firefox].png
new file mode 100644
Binary files /dev/null and b/e2e_playwright/__snapshots__/linux/st_selectbox_test/st_selectbox-hidden_label[dark_theme-firefox].png differ
diff --git a/e2e_playwright/__snapshots__/linux/st_selectbox_test/st_selectbox-hidden_label[dark_theme-webkit].png b/e2e_playwright/__snapshots__/linux/st_selectbox_test/st_selectbox-hidden_label[dark_theme-webkit].png
new file mode 100644
Binary files /dev/null and b/e2e_playwright/__snapshots__/linux/st_selectbox_test/st_selectbox-hidden_label[dark_theme-webkit].png differ
diff --git a/e2e_playwright/__snapshots__/linux/st_selectbox_test/st_selectbox-hidden_label[light_theme-chromium].png b/e2e_playwright/__snapshots__/linux/st_selectbox_test/st_selectbox-hidden_label[light_theme-chromium].png
new file mode 100644
Binary files /dev/null and b/e2e_playwright/__snapshots__/linux/st_selectbox_test/st_selectbox-hidden_label[light_theme-chromium].png differ
diff --git a/e2e_playwright/__snapshots__/linux/st_selectbox_test/st_selectbox-hidden_label[light_theme-firefox].png b/e2e_playwright/__snapshots__/linux/st_selectbox_test/st_selectbox-hidden_label[light_theme-firefox].png
new file mode 100644
Binary files /dev/null and b/e2e_playwright/__snapshots__/linux/st_selectbox_test/st_selectbox-hidden_label[light_theme-firefox].png differ
diff --git a/e2e_playwright/__snapshots__/linux/st_selectbox_test/st_selectbox-hidden_label[light_theme-webkit].png b/e2e_playwright/__snapshots__/linux/st_selectbox_test/st_selectbox-hidden_label[light_theme-webkit].png
new file mode 100644
Binary files /dev/null and b/e2e_playwright/__snapshots__/linux/st_selectbox_test/st_selectbox-hidden_label[light_theme-webkit].png differ
diff --git a/e2e_playwright/__snapshots__/linux/st_selectbox_test/st_selectbox-more_options[dark_theme-chromium].png b/e2e_playwright/__snapshots__/linux/st_selectbox_test/st_selectbox-more_options[dark_theme-chromium].png
new file mode 100644
Binary files /dev/null and b/e2e_playwright/__snapshots__/linux/st_selectbox_test/st_selectbox-more_options[dark_theme-chromium].png differ
diff --git a/e2e_playwright/__snapshots__/linux/st_selectbox_test/st_selectbox-more_options[dark_theme-firefox].png b/e2e_playwright/__snapshots__/linux/st_selectbox_test/st_selectbox-more_options[dark_theme-firefox].png
new file mode 100644
Binary files /dev/null and b/e2e_playwright/__snapshots__/linux/st_selectbox_test/st_selectbox-more_options[dark_theme-firefox].png differ
diff --git a/e2e_playwright/__snapshots__/linux/st_selectbox_test/st_selectbox-more_options[dark_theme-webkit].png b/e2e_playwright/__snapshots__/linux/st_selectbox_test/st_selectbox-more_options[dark_theme-webkit].png
new file mode 100644
Binary files /dev/null and b/e2e_playwright/__snapshots__/linux/st_selectbox_test/st_selectbox-more_options[dark_theme-webkit].png differ
diff --git a/e2e_playwright/__snapshots__/linux/st_selectbox_test/st_selectbox-more_options[light_theme-chromium].png b/e2e_playwright/__snapshots__/linux/st_selectbox_test/st_selectbox-more_options[light_theme-chromium].png
new file mode 100644
Binary files /dev/null and b/e2e_playwright/__snapshots__/linux/st_selectbox_test/st_selectbox-more_options[light_theme-chromium].png differ
diff --git a/e2e_playwright/__snapshots__/linux/st_selectbox_test/st_selectbox-more_options[light_theme-firefox].png b/e2e_playwright/__snapshots__/linux/st_selectbox_test/st_selectbox-more_options[light_theme-firefox].png
new file mode 100644
Binary files /dev/null and b/e2e_playwright/__snapshots__/linux/st_selectbox_test/st_selectbox-more_options[light_theme-firefox].png differ
diff --git a/e2e_playwright/__snapshots__/linux/st_selectbox_test/st_selectbox-more_options[light_theme-webkit].png b/e2e_playwright/__snapshots__/linux/st_selectbox_test/st_selectbox-more_options[light_theme-webkit].png
new file mode 100644
Binary files /dev/null and b/e2e_playwright/__snapshots__/linux/st_selectbox_test/st_selectbox-more_options[light_theme-webkit].png differ
diff --git a/e2e_playwright/__snapshots__/linux/st_selectbox_test/st_selectbox-no_options[dark_theme-chromium].png b/e2e_playwright/__snapshots__/linux/st_selectbox_test/st_selectbox-no_options[dark_theme-chromium].png
new file mode 100644
Binary files /dev/null and b/e2e_playwright/__snapshots__/linux/st_selectbox_test/st_selectbox-no_options[dark_theme-chromium].png differ
diff --git a/e2e_playwright/__snapshots__/linux/st_selectbox_test/st_selectbox-no_options[dark_theme-firefox].png b/e2e_playwright/__snapshots__/linux/st_selectbox_test/st_selectbox-no_options[dark_theme-firefox].png
new file mode 100644
Binary files /dev/null and b/e2e_playwright/__snapshots__/linux/st_selectbox_test/st_selectbox-no_options[dark_theme-firefox].png differ
diff --git a/e2e_playwright/__snapshots__/linux/st_selectbox_test/st_selectbox-no_options[dark_theme-webkit].png b/e2e_playwright/__snapshots__/linux/st_selectbox_test/st_selectbox-no_options[dark_theme-webkit].png
new file mode 100644
Binary files /dev/null and b/e2e_playwright/__snapshots__/linux/st_selectbox_test/st_selectbox-no_options[dark_theme-webkit].png differ
diff --git a/e2e_playwright/__snapshots__/linux/st_selectbox_test/st_selectbox-no_options[light_theme-chromium].png b/e2e_playwright/__snapshots__/linux/st_selectbox_test/st_selectbox-no_options[light_theme-chromium].png
new file mode 100644
Binary files /dev/null and b/e2e_playwright/__snapshots__/linux/st_selectbox_test/st_selectbox-no_options[light_theme-chromium].png differ
diff --git a/e2e_playwright/__snapshots__/linux/st_selectbox_test/st_selectbox-no_options[light_theme-firefox].png b/e2e_playwright/__snapshots__/linux/st_selectbox_test/st_selectbox-no_options[light_theme-firefox].png
new file mode 100644
Binary files /dev/null and b/e2e_playwright/__snapshots__/linux/st_selectbox_test/st_selectbox-no_options[light_theme-firefox].png differ
diff --git a/e2e_playwright/__snapshots__/linux/st_selectbox_test/st_selectbox-no_options[light_theme-webkit].png b/e2e_playwright/__snapshots__/linux/st_selectbox_test/st_selectbox-no_options[light_theme-webkit].png
new file mode 100644
Binary files /dev/null and b/e2e_playwright/__snapshots__/linux/st_selectbox_test/st_selectbox-no_options[light_theme-webkit].png differ
diff --git a/e2e_playwright/__snapshots__/linux/st_selectbox_test/st_selectbox-selection_dropdown[chromium].png b/e2e_playwright/__snapshots__/linux/st_selectbox_test/st_selectbox-selection_dropdown[chromium].png
new file mode 100644
Binary files /dev/null and b/e2e_playwright/__snapshots__/linux/st_selectbox_test/st_selectbox-selection_dropdown[chromium].png differ
diff --git a/e2e_playwright/__snapshots__/linux/st_selectbox_test/st_selectbox-selection_dropdown[firefox].png b/e2e_playwright/__snapshots__/linux/st_selectbox_test/st_selectbox-selection_dropdown[firefox].png
new file mode 100644
Binary files /dev/null and b/e2e_playwright/__snapshots__/linux/st_selectbox_test/st_selectbox-selection_dropdown[firefox].png differ
diff --git a/e2e_playwright/__snapshots__/linux/st_selectbox_test/st_selectbox-selection_dropdown[webkit].png b/e2e_playwright/__snapshots__/linux/st_selectbox_test/st_selectbox-selection_dropdown[webkit].png
new file mode 100644
Binary files /dev/null and b/e2e_playwright/__snapshots__/linux/st_selectbox_test/st_selectbox-selection_dropdown[webkit].png differ
diff --git a/e2e_playwright/st_selectbox_test.py b/e2e_playwright/st_selectbox_test.py
new file mode 100644
--- /dev/null
+++ b/e2e_playwright/st_selectbox_test.py
@@ -0,0 +1,190 @@
+# 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_selectbox_widget_rendering(
+    themed_app: Page, assert_snapshot: ImageCompareFunction
+):
+    """Test that the selectbox widgets are correctly rendered via screenshot matching."""
+    selectbox_widgets = themed_app.get_by_test_id("stSelectbox")
+    expect(selectbox_widgets).to_have_count(11)
+
+    assert_snapshot(selectbox_widgets.nth(0), name="st_selectbox-default")
+    assert_snapshot(selectbox_widgets.nth(1), name="st_selectbox-formatted_options")
+    assert_snapshot(selectbox_widgets.nth(2), name="st_selectbox-no_options")
+    assert_snapshot(selectbox_widgets.nth(3), name="st_selectbox-more_options")
+    assert_snapshot(selectbox_widgets.nth(4), name="st_selectbox-disabled")
+    assert_snapshot(selectbox_widgets.nth(5), name="st_selectbox-hidden_label")
+    assert_snapshot(selectbox_widgets.nth(6), name="st_selectbox-collapsed_label")
+    assert_snapshot(selectbox_widgets.nth(7), name="st_selectbox-callback_help")
+    assert_snapshot(selectbox_widgets.nth(8), name="st_selectbox-empty_selection")
+    assert_snapshot(
+        selectbox_widgets.nth(9), name="st_selectbox-empty_selection_placeholder"
+    )
+    assert_snapshot(selectbox_widgets.nth(10), name="st_selectbox-dataframe_options")
+
+
+def test_selectbox_has_correct_initial_values(app: Page):
+    """Test that st.selectbox returns the correct initial values."""
+    markdown_elements = app.get_by_test_id("stMarkdown")
+    expect(markdown_elements).to_have_count(12)
+
+    expected = [
+        "value 1: male",
+        "value 2: female",
+        "value 3: None",
+        "value 4: e2e/scripts/components_iframe.py",
+        "value 5: male",
+        "value 6: male",
+        "value 7: male",
+        "value 8: female",
+        "selectbox changed: False",
+        "value 9: None",
+        "value 10: None",
+        "value 11: male",
+    ]
+
+    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_option_selection(app: Page, assert_snapshot: ImageCompareFunction):
+    """Test that selection of an option via the dropdown works correctly."""
+    app.get_by_test_id("stSelectbox").nth(3).locator("input").click()
+
+    # Take a snapshot of the selection dropdown:
+    selection_dropdown = app.locator('[data-baseweb="popover"]').first
+    assert_snapshot(selection_dropdown, name="st_selectbox-selection_dropdown")
+    # Select last option:
+    selection_dropdown.locator("li").nth(1).click()
+    # Check that selection worked:
+    expect(app.get_by_test_id("stMarkdown").nth(3)).to_have_text(
+        "value 4: e2e/scripts/st_warning.py", use_inner_text=True
+    )
+
+
+def test_handles_option_selection_via_typing(app: Page):
+    """Test that selection of an option via typing works correctly."""
+    selectbox_input = app.get_by_test_id("stSelectbox").nth(3).locator("input")
+
+    # Type an option:
+    selectbox_input.type("e2e/scripts/st_warning.py")
+    selectbox_input.press("Enter")
+
+    # Check that selection worked:
+    expect(app.get_by_test_id("stMarkdown").nth(3)).to_have_text(
+        "value 4: e2e/scripts/st_warning.py", use_inner_text=True
+    )
+
+
+def test_shows_correct_options_via_fuzzy_search(
+    app: Page, assert_snapshot: ImageCompareFunction
+):
+    """Test that the fuzzy matching of options via typing works correctly."""
+    selectbox_input = app.get_by_test_id("stSelectbox").nth(3).locator("input")
+
+    # Start typing:
+    selectbox_input.type("exp")
+
+    # Check filtered options
+    selection_dropdown = app.locator('[data-baseweb="popover"]').first
+    assert_snapshot(selection_dropdown, name="st_selectbox-fuzzy_matching")
+
+
+def test_empty_selectbox_behaves_correctly(
+    app: Page, assert_snapshot: ImageCompareFunction
+):
+    """Test that st.selectbox behaves correctly when empty (no initial selection)."""
+    # Enter 10 in the first empty input:
+    empty_selectbox_input = app.get_by_test_id("stSelectbox").locator("input").nth(8)
+
+    # Type an option:
+    empty_selectbox_input.type("male")
+    empty_selectbox_input.press("Enter")
+
+    expect(app.get_by_test_id("stMarkdown").nth(9)).to_have_text(
+        "value 9: male", use_inner_text=True
+    )
+
+    assert_snapshot(
+        app.get_by_test_id("stSelectbox").nth(8), name="st_selectbox-clearable_input"
+    )
+
+    empty_selectbox_input.focus()
+    empty_selectbox_input.press("Escape")
+
+    # Should be empty again:
+    expect(app.get_by_test_id("stMarkdown").nth(9)).to_have_text(
+        "value 9: None", use_inner_text=True
+    )
+
+
+def test_keeps_value_on_selection_close(app: Page):
+    """Test that the fuzzy matching of options via typing works correctly."""
+    app.get_by_test_id("stSelectbox").nth(3).locator("input").click()
+
+    # Take a snapshot of the selection dropdown:
+    expect(app.locator('[data-baseweb="popover"]').first).to_be_visible()
+
+    # Click outside to close the dropdown:
+    app.get_by_test_id("stMarkdown").first.click()
+
+    # Check if value is still initial value:
+    expect(app.get_by_test_id("stMarkdown").nth(3)).to_have_text(
+        "value 4: e2e/scripts/components_iframe.py", use_inner_text=True
+    )
+
+
+def test_handles_callback_on_change_correctly(app: Page):
+    """Test that it correctly calls the callback on change."""
+    # Check initial state:
+    expect(app.get_by_test_id("stMarkdown").nth(7)).to_have_text(
+        "value 8: female", use_inner_text=True
+    )
+    expect(app.get_by_test_id("stMarkdown").nth(8)).to_have_text(
+        "selectbox changed: False", use_inner_text=True
+    )
+
+    app.get_by_test_id("stSelectbox").nth(7).locator("input").click()
+
+    # Take a snapshot of the selection dropdown:
+    selection_dropdown = app.locator('[data-baseweb="popover"]').first
+    # Select last option:
+    selection_dropdown.locator("li").first.click()
+
+    # Check that selection worked:
+    expect(app.get_by_test_id("stMarkdown").nth(7)).to_have_text(
+        "value 8: male", use_inner_text=True
+    )
+    expect(app.get_by_test_id("stMarkdown").nth(8)).to_have_text(
+        "selectbox changed: True", use_inner_text=True
+    )
+
+    # Change different date input to trigger delta path change
+    empty_selectbox_input = app.get_by_test_id("stSelectbox").locator("input").first
+
+    # Type an option:
+    empty_selectbox_input.type("female")
+    empty_selectbox_input.press("Enter")
+
+    expect(app.get_by_test_id("stMarkdown").first).to_have_text(
+        "value 1: female", use_inner_text=True
+    )
+    expect(app.get_by_test_id("stMarkdown").nth(7)).to_have_text(
+        "value 8: male", use_inner_text=True
+    )
diff --git a/frontend/lib/src/components/shared/Dropdown/Selectbox.test.tsx b/frontend/lib/src/components/shared/Dropdown/Selectbox.test.tsx
--- a/frontend/lib/src/components/shared/Dropdown/Selectbox.test.tsx
+++ b/frontend/lib/src/components/shared/Dropdown/Selectbox.test.tsx
@@ -20,7 +20,8 @@ import { shallow, mount } from "@streamlit/lib/src/test_util"
 
 import { Select as UISelect } from "baseui/select"
 import { LabelVisibilityOptions } from "@streamlit/lib/src/util/utils"
-import Selectbox, { Props, fuzzyFilterSelectOptions } from "./Selectbox"
+import { Selectbox, Props, fuzzyFilterSelectOptions } from "./Selectbox"
+import { mockTheme } from "@streamlit/lib/src/mocks/mockTheme"
 
 jest.mock("@streamlit/lib/src/WidgetStateManager")
 
@@ -31,6 +32,7 @@ const getProps = (props: Partial<Props> = {}): Props => ({
   width: 0,
   disabled: false,
   onChange: jest.fn(),
+  theme: mockTheme.emotion,
   placeholder: "Select...",
   ...props,
 })
diff --git a/frontend/lib/src/components/widgets/Selectbox/Selectbox.test.tsx b/frontend/lib/src/components/widgets/Selectbox/Selectbox.test.tsx
--- a/frontend/lib/src/components/widgets/Selectbox/Selectbox.test.tsx
+++ b/frontend/lib/src/components/widgets/Selectbox/Selectbox.test.tsx
@@ -20,7 +20,8 @@ import { WidgetStateManager } from "@streamlit/lib/src/WidgetStateManager"
 
 import { Select as UISelect } from "baseui/select"
 import { Selectbox as SelectboxProto } from "@streamlit/lib/src/proto"
-import Selectbox, { Props } from "./Selectbox"
+import { Selectbox, Props } from "./Selectbox"
+import { mockTheme } from "@streamlit/lib/src/mocks/mockTheme"
 
 const getProps = (elementProps: Partial<SelectboxProto> = {}): Props => ({
   element: SelectboxProto.create({
@@ -32,6 +33,7 @@ const getProps = (elementProps: Partial<SelectboxProto> = {}): Props => ({
   }),
   width: 0,
   disabled: false,
+  theme: mockTheme.emotion,
   widgetMgr: new WidgetStateManager({
     sendRerunBackMsg: jest.fn(),
     formsDataChanged: jest.fn(),
diff --git a/lib/streamlit/testing/element_tree.py b/lib/streamlit/testing/element_tree.py
--- a/lib/streamlit/testing/element_tree.py
+++ b/lib/streamlit/testing/element_tree.py
@@ -646,7 +646,7 @@ def widget_state(self) -> WidgetState:
 
 @dataclass(repr=False)
 class Selectbox(Widget, Generic[T]):
-    _value: T | None
+    _value: T | None | InitialValue
 
     proto: SelectboxProto = field(repr=False)
     options: list[str]
@@ -654,7 +654,7 @@ class Selectbox(Widget, Generic[T]):
     def __init__(self, proto: SelectboxProto, root: ElementTree):
         self.proto = proto
         self.root = root
-        self._value = None
+        self._value = InitialValue()
 
         self.type = "selectbox"
         self.id = proto.id
@@ -666,22 +666,25 @@ def __init__(self, proto: SelectboxProto, root: ElementTree):
         self.key = user_key_from_widget_id(self.id)
 
     @property
-    def index(self) -> int:
+    def index(self) -> int | None:
+        if self.value is None:
+            return None
+
         if len(self.options) == 0:
             return 0
         return self.options.index(str(self.value))
 
     @property
-    def value(self) -> T:
+    def value(self) -> T | None:
         """The currently selected value from the options."""
-        if self._value is not None:
+        if not isinstance(self._value, InitialValue):
             return self._value
         else:
             state = self.root.session_state
             assert state
             return cast(T, state[self.id])
 
-    def set_value(self, v: T) -> Selectbox[T]:
+    def set_value(self, v: T | None) -> Selectbox[T]:
         """
         Set the value of the selectbox.
         Implementation note: set_value not work correctly if `format_func` is also
@@ -691,10 +694,12 @@ def set_value(self, v: T) -> Selectbox[T]:
         self._value = v
         return self
 
-    def select(self, v: T) -> Selectbox[T]:
+    def select(self, v: T | None) -> Selectbox[T]:
         return self.set_value(v)
 
-    def select_index(self, index: int) -> Selectbox[T]:
+    def select_index(self, index: int | None) -> Selectbox[T]:
+        if index is None:
+            return self.set_value(None)
         return self.set_value(cast(T, self.options[index]))
 
     def widget_state(self) -> WidgetState:
@@ -704,7 +709,8 @@ def widget_state(self) -> WidgetState:
         """
         ws = WidgetState()
         ws.id = self.id
-        ws.int_value = self.index
+        if self.index is not None:
+            ws.int_value = self.index
         return ws
 
 
diff --git a/lib/tests/streamlit/elements/selectbox_test.py b/lib/tests/streamlit/elements/selectbox_test.py
--- a/lib/tests/streamlit/elements/selectbox_test.py
+++ b/lib/tests/streamlit/elements/selectbox_test.py
@@ -23,6 +23,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
 
 
@@ -40,6 +41,7 @@ def test_just_label(self):
             LabelVisibilityMessage.LabelVisibilityOptions.VISIBLE,
         )
         self.assertEqual(c.default, 0)
+        self.assertEqual(c.HasField("default"), True)
         self.assertEqual(c.disabled, False)
         self.assertEqual(c.placeholder, "Select...")
 
@@ -58,6 +60,17 @@ def test_valid_value(self):
         self.assertEqual(c.label, "the label")
         self.assertEqual(c.default, 1)
 
+    def test_none_index(self):
+        """Test that it can be called with None as index value."""
+        st.selectbox("the label", ("m", "f"), index=None)
+
+        c = self.get_delta_from_queue().new_element.selectbox
+        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, 0)
+        self.assertEqual(c.HasField("default"), False)
+
     def test_noneType_option(self):
         """Test NoneType option value."""
         current_value = st.selectbox("the label", (None, "selected"), 0)
@@ -188,3 +201,27 @@ def test_placeholder(self):
 
         c = self.get_delta_from_queue().new_element.selectbox
         self.assertEqual(c.placeholder, "Please select")
+
+
+class SelectboxInteractiveTest(InteractiveScriptTests):
+    def test_selectbox_interaction(self):
+        """Test interactions with an empty selectbox widget."""
+        script = self.script_from_string(
+            """
+        import streamlit as st
+        st.selectbox("the label", ("m", "f"), index=None)
+        """
+        )
+        sr = script.run()
+        selectbox = sr.selectbox[0]
+        assert selectbox.value is None
+
+        # Select option m
+        sr2 = selectbox.set_value("m").run()
+        selectbox = sr2.selectbox[0]
+        assert selectbox.value == "m"
+
+        # # Clear the value
+        sr3 = selectbox.set_value(None).run()
+        selectbox = sr3.selectbox[0]
+        assert selectbox.value is None