Repository: streamlit/streamlit

Issue:
`st.metric` should have `label_visibility` parameter
We had that in the original spec but probably forgot it during implementation. Let's fix it. Not super urgent but would be good to do soon so we don't forget. 

---

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:

Resolving patch:
diff --git a/e2e/scripts/st_metric.py b/e2e/scripts/st_metric.py
--- a/e2e/scripts/st_metric.py
+++ b/e2e/scripts/st_metric.py
@@ -33,3 +33,11 @@
     st.metric("Test 2", -4.56, 1.23, "inverse")
 with col3:
     st.slider("Pick another")
+
+
+with col1:
+    st.metric("Test 3", -4.56, 1.23, label_visibility="visible")
+with col2:
+    st.metric("Test 4", -4.56, 1.23, label_visibility="hidden")
+with col3:
+    st.metric("Test 5", -4.56, 1.23, label_visibility="collapsed")
diff --git a/lib/streamlit/elements/metric.py b/lib/streamlit/elements/metric.py
--- a/lib/streamlit/elements/metric.py
+++ b/lib/streamlit/elements/metric.py
@@ -18,10 +18,12 @@
 
 from typing_extensions import Literal, TypeAlias
 
+from streamlit.elements.utils import get_label_visibility_proto_value
 from streamlit.errors import StreamlitAPIException
 from streamlit.proto.Metric_pb2 import Metric as MetricProto
 from streamlit.runtime.metrics_util import gather_metrics
 from streamlit.string_util import clean_text
+from streamlit.type_util import LabelVisibility, maybe_raise_label_warnings
 
 if TYPE_CHECKING:
     import numpy as np
@@ -49,6 +51,7 @@ def metric(
         delta: Delta = None,
         delta_color: DeltaColor = "normal",
         help: Optional[str] = None,
+        label_visibility: LabelVisibility = "visible",
     ) -> "DeltaGenerator":
         """Display a metric in big bold font, with an optional indicator of how the metric changed.
 
@@ -79,6 +82,11 @@ def metric(
              regardless of its value.
         help : str
             An optional tooltip that gets displayed next to the metric label.
+        label_visibility : "visible" or "hidden" or "collapsed"
+            The visibility of the label. If "hidden", the label doesn't show but there
+            is still empty space for it (equivalent to label="").
+            If "collapsed", both the label and the space are removed. Default is
+            "visible". This argument can only be supplied by keyword.
 
         Example
         -------
@@ -112,6 +120,8 @@ def metric(
             height: 320px
 
         """
+        maybe_raise_label_warnings(label, label_visibility)
+
         metric_proto = MetricProto()
         metric_proto.body = self.parse_value(value)
         metric_proto.label = self.parse_label(label)
@@ -124,6 +134,9 @@ def metric(
         )
         metric_proto.color = color_and_direction.color
         metric_proto.direction = color_and_direction.direction
+        metric_proto.label_visibility.value = get_label_visibility_proto_value(
+            label_visibility
+        )
 
         return self.dg._enqueue("metric", metric_proto)
 


Test patch:
diff --git a/frontend/src/components/elements/Metric/Metric.test.tsx b/frontend/src/components/elements/Metric/Metric.test.tsx
--- a/frontend/src/components/elements/Metric/Metric.test.tsx
+++ b/frontend/src/components/elements/Metric/Metric.test.tsx
@@ -19,7 +19,10 @@ import { mount } from "src/lib/test_util"
 
 import StreamlitMarkdown from "src/components/shared/StreamlitMarkdown"
 
-import { Metric as MetricProto } from "src/autogen/proto"
+import {
+  Metric as MetricProto,
+  LabelVisibilityMessage as LabelVisibilityMessageProto,
+} from "src/autogen/proto"
 import Metric, { MetricProps } from "./Metric"
 
 const getProps = (elementProps: Partial<MetricProto> = {}): MetricProps => ({
@@ -47,6 +50,30 @@ describe("Metric element", () => {
     expect(wrappedMetricLabel.props().isLabel).toBe(true)
   })
 
+  it("pass labelVisibility prop to StyledMetricLabelText correctly when hidden", () => {
+    const props = getProps({
+      labelVisibility: {
+        value: LabelVisibilityMessageProto.LabelVisibilityOptions.HIDDEN,
+      },
+    })
+    const wrapper = mount(<Metric {...props} />)
+    expect(wrapper.find("StyledMetricLabelText").prop("visibility")).toEqual(
+      LabelVisibilityMessageProto.LabelVisibilityOptions.HIDDEN
+    )
+  })
+
+  it("pass labelVisibility prop to StyledMetricLabelText correctly when collapsed", () => {
+    const props = getProps({
+      labelVisibility: {
+        value: LabelVisibilityMessageProto.LabelVisibilityOptions.COLLAPSED,
+      },
+    })
+    const wrapper = mount(<Metric {...props} />)
+    expect(wrapper.find("StyledMetricLabelText").prop("visibility")).toEqual(
+      LabelVisibilityMessageProto.LabelVisibilityOptions.COLLAPSED
+    )
+  })
+
   it("renders direction icon based on props", () => {
     const props = getProps()
     const wrapper = mount(<Metric {...props} />)
diff --git a/lib/tests/streamlit/elements/metric_test.py b/lib/tests/streamlit/elements/metric_test.py
--- a/lib/tests/streamlit/elements/metric_test.py
+++ b/lib/tests/streamlit/elements/metric_test.py
@@ -13,9 +13,13 @@
 # limitations under the License.
 
 """metric unit tests."""
+from parameterized import parameterized
+
 import streamlit as st
 from streamlit.errors import StreamlitAPIException
+from streamlit.proto.LabelVisibilityMessage_pb2 import LabelVisibilityMessage
 from streamlit.proto.Metric_pb2 import Metric as MetricProto
+from streamlit.type_util import _LOGGER
 from tests.delta_generator_test_case import DeltaGeneratorTestCase
 
 
@@ -28,6 +32,10 @@ def test_no_value(self):
         self.assertEqual(c.label, "label_test")
         # This is an em dash. Not a regular "-"
         self.assertEqual(c.body, "—")
+        self.assertEqual(
+            c.label_visibility.value,
+            LabelVisibilityMessage.LabelVisibilityOptions.VISIBLE,
+        )
 
     def test_label_and_value(self):
         """Test that metric can be called with label and value passed in."""
@@ -39,6 +47,22 @@ def test_label_and_value(self):
         self.assertEqual(c.color, MetricProto.MetricColor.GRAY)
         self.assertEqual(c.direction, MetricProto.MetricDirection.NONE)
 
+    @parameterized.expand(
+        [
+            ("visible", LabelVisibilityMessage.LabelVisibilityOptions.VISIBLE),
+            ("hidden", LabelVisibilityMessage.LabelVisibilityOptions.HIDDEN),
+            ("collapsed", LabelVisibilityMessage.LabelVisibilityOptions.COLLAPSED),
+        ]
+    )
+    def test_label_visibility(self, label_visibility_value, proto_value):
+        """Test that metric can be called with label_visibility param."""
+        st.metric("label_test", "123", label_visibility=label_visibility_value)
+
+        c = self.get_delta_from_queue().new_element.metric
+        self.assertEqual(c.label, "label_test")
+        self.assertEqual(c.body, "123")
+        self.assertEqual(c.label_visibility.value, proto_value)
+
     def test_label_and_value_and_delta_and_delta_color(self):
         """Test that metric can be called with label, value, delta, and delta
         colors passed in."""
@@ -157,6 +181,26 @@ def test_invalid_label(self):
             str(exc.exception),
         )
 
+    def test_invalid_label_visibility(self):
+        with self.assertRaises(StreamlitAPIException) as e:
+            st.metric("label_test", "123", label_visibility="wrong_value")
+        self.assertEqual(
+            str(e.exception),
+            "Unsupported label_visibility option 'wrong_value'. Valid values are "
+            "'visible', 'hidden' or 'collapsed'.",
+        )
+
+    def test_empty_label_warning(self):
+        """Test that a warning is logged if st.metric was called with empty label."""
+
+        with self.assertLogs(_LOGGER) as logs:
+            st.metric(label="", value="123")
+
+        self.assertIn(
+            "`label` got an empty value. This is discouraged for accessibility reasons",
+            logs.records[0].msg,
+        )
+
     def test_invalid_value(self):
         with self.assertRaises(TypeError) as exc:
             st.metric("Testing", [1, 2, 3])