Skip to content

feat: add metricValueFontSize and metricValueFontWeight theme options#13550

Merged
sfc-gh-lwilby merged 16 commits intostreamlit:developfrom
kagawa0710:feat/12300-metric-theming-font-size-weight
Feb 9, 2026
Merged

feat: add metricValueFontSize and metricValueFontWeight theme options#13550
sfc-gh-lwilby merged 16 commits intostreamlit:developfrom
kagawa0710:feat/12300-metric-theming-font-size-weight

Conversation

@kagawa0710
Copy link
Copy Markdown
Contributor

@kagawa0710 kagawa0710 commented Jan 10, 2026

feat: add metricValueFontSize and metricValueFontWeight theme options

Add metricValueFontSize and metricValueFontWeight theme configuration options to customize st.metric value text appearance.

Example configuration

[theme]
metricValueFontSize = "3rem"    # or "48px" or "48"
metricValueFontWeight = 300

Supported formats for metricValueFontSize

Format Example Result
rem "3rem" 3rem (48px with 16px base)
px "48px" 48px
number (string) "48" 48px

Invalid values (negative, zero, invalid format) log a warning and fallback to the default (2.25rem ≈ 36px).

Screenshots

Before (Default)

metricValueFontSize not set → uses default (2.25rem ≈ 36px)

metric_before

Supported formats

Shows valid values ("48px", "3rem", "36", "24px") and invalid values that fallback to default ("-10px", "0", "aaa").

pr_screenshot_metricValueFontSize

GitHub Issue Link (if applicable)

Closes #12300

Testing Plan

  • Unit Tests (JS and Python)
    • Theme utils tests for font size/weight validation (17 test cases for metricValueFontSize)
    • Metric component tests for styling
    • Python config tests
    • Proto compatibility tests
  • E2E test (e2e_playwright/theming/theme_metric_value_style_test.py)
    • Verifies rem string parsing (3rem → 48px)
    • Verifies CSS properties are applied correctly
    • Snapshot test for visual verification
  • Manual testing with custom config.toml

Notes

…me options

Add unit tests for new metric value font styling theme configuration.
Tests cover:
- Theme utils: font size/weight when configured, not configured, and invalid values
- Metric component: font-weight and font-size styling applied correctly
- Python config: theme option parsing and validation
Allow customizing st.metric value text appearance through theme config:
- metricValueFontSize: font size in pixels
- metricValueFontWeight: font weight (100-900)

Closes streamlit#12300
@kagawa0710 kagawa0710 requested a review from a team as a code owner January 10, 2026 13:58
@snyk-io
Copy link
Copy Markdown
Contributor

snyk-io bot commented Jan 10, 2026

Snyk checks have passed. No issues have been found so far.

Status Scanner Critical High Medium Low Total (0)
Open Source Security 0 0 0 0 0 issues
Licenses 0 0 0 0 0 issues

💻 Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse.

@github-actions
Copy link
Copy Markdown
Contributor

Thanks for contributing to Streamlit! 🎈

Please make sure you have read our Contributing Guide. You can find additional information about Streamlit development in the wiki.

The review process:

  1. Initial triage: A maintainer will apply labels, approve CI to run, and trigger AI-assisted reviews. Your PR may be flagged with status:needs-product-approval if the feature requires product team sign-off.

  2. Code review: A core maintainer will start reviewing your PR once:

    • It is marked as 'ready for review', not 'draft'
    • It has status:product-approved (or doesn't need it)
    • All CI checks pass
    • All AI review comments are addressed

We're receiving many contributions and have limited review bandwidth — please expect some delay. We appreciate your patience! 🙏

  threeXL is 2.25rem which equals approximately 36px (not 28px)
@lukasmasuch lukasmasuch requested a review from Copilot January 10, 2026 15:26
@lukasmasuch lukasmasuch added security-assessment-completed change:feature PR contains new feature or enhancement implementation impact:users PR changes affect end users status:needs-product-approval PR requires product approval before merging labels Jan 10, 2026
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds two new theme configuration options (metricValueFontSize and metricValueFontWeight) to allow customization of the font size and weight for st.metric value text. The implementation spans the full stack from protobuf definitions to frontend rendering.

Changes:

  • Adds protobuf fields for metric value font styling configuration
  • Adds Python config options with appropriate documentation
  • Implements frontend theme validation and application logic
  • Includes comprehensive unit tests for validation and styling behavior

Reviewed changes

Copilot reviewed 9 out of 9 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
proto/streamlit/proto/NewSession.proto Adds optional int32 fields for metric_value_font_size (62) and metric_value_font_weight (63)
lib/streamlit/config.py Defines theme configuration options with descriptions and type validation
lib/tests/streamlit/config_test.py Adds test coverage for new config options
frontend/lib/src/theme/types.ts Extends EmotionTheme interface with optional metric value font properties
frontend/lib/src/theme/utils.ts Implements validation logic (size > 0, weight 100-900) and theme creation
frontend/lib/src/theme/utils.test.ts Comprehensive unit tests covering valid/invalid values and edge cases
frontend/lib/src/components/widgets/BidiComponent/utils/theme.test.ts Updates type guard to include new theme fields
frontend/lib/src/components/elements/Metric/styled-components.ts Applies theme values to metric value text styling
frontend/lib/src/components/elements/Metric/Metric.test.tsx Tests default behavior when theme values are not specified

Comment on lines +1894 to +1902
_create_theme_options(
"metricValueFontSize",
categories=["theme"],
description="""
The font size (in pixels) for st.metric value text.

If this isn't set, the font size will be threeXL (2.25rem, approximately 36px).
""",
type_=int,
Copy link

Copilot AI Jan 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The description for metricValueFontSize should clarify that it expects a positive integer value, similar to how baseFontSize is documented. Consider adding "This is a positive integer." to the description for consistency with other font size configuration options.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The "(in pixels)" wording implies a positive value, and invalid values (≤0) are silently ignored (consistent with baseFontSize).

Happy to add explicit "Must be a positive integer." if a reviewer prefers.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is OK, the other config has another descriptive section along with this. But also defer to @sfc-gh-dmatthews , she will likely make a change before the release if she thinks the wording should be updated.

Copy link
Copy Markdown

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Bugbot reviewed your changes and found no bugs!

  - Add properties to StreamlitTheme interface (component-v2-lib)
  - Add extraction in extractComponentsV2Theme for Components V2 support
  - Add mock values to theme.test.ts
  - Add negative value tests for metricValueFontSize validation
@kagawa0710
Copy link
Copy Markdown
Contributor Author

Update in latest commit (e1e539f)

Fixed CI failures by adding missing metricValueFontSize and metricValueFontWeight to:

  • StreamlitTheme interface (component-v2-lib)
  • extractComponentsV2Theme function (for Components V2 support)
  • Mock theme in tests

Also added negative value tests for metricValueFontSize as suggested by Copilot.

Ready for CI approval when you have a chance. Thanks!

@kagawa0710
Copy link
Copy Markdown
Contributor Author

Fixed Python proto_compatibility_test.py failure by adding metric_value_font_size and metric_value_font_weight to expected CustomThemeConfig fields.

@sfc-gh-lwilby sfc-gh-lwilby added the ai-review If applied to PR or issue will run AI review workflow label Jan 12, 2026
@sfc-gh-lwilby
Copy link
Copy Markdown
Collaborator

@kagawa0710 since this is a visual change, it would be good if you could add some screenshots to help with the product review 🙏

  Add E2E test to verify that custom metric value font size and weight
  are applied correctly via environment variables. The test sets
  STREAMLIT_THEME_METRIC_VALUE_FONT_SIZE=48 and
  STREAMLIT_THEME_METRIC_VALUE_FONT_WEIGHT=300, then verifies the CSS
  properties are applied to the metric value element.
@sfc-gh-lwilby sfc-gh-lwilby added the ready-for-maintainer-review This PR is ready for maintainer attention to conduct final review before merge! label Jan 13, 2026
@kagawa0710
Copy link
Copy Markdown
Contributor Author

@sfc-gh-lwilby
Added screenshots to the PR description showing before/after comparison.

Sorry for the late reply due to timezone differences!

@jrieke jrieke added status:product-approved Community PR is approved by product team and removed status:needs-product-approval PR requires product approval before merging labels Jan 14, 2026
@jrieke
Copy link
Copy Markdown
Collaborator

jrieke commented Jan 14, 2026

Sounds good, thanks for adding this! Just two quick questions:

  1. This doesn't change the current design if the config options are not set, right? On your first screenshot it looks a bit smaller than right now, but probably it's just an issue of the screenshot.
  2. Does the font size option accept the same values that we also accept for other font size config options, e.g. headingFontSizes seems to accept both rem and px values?

@lukasmasuch
Copy link
Copy Markdown
Collaborator

does the font size option accept the same values that we also accept for other font size config options, e.g. headingFontSizes seems to accept both rem and px values?

If we want to support rem and px, it probably needs to be string not int

@kagawa0710
Copy link
Copy Markdown
Contributor Author

@jrieke @lukasmasuch
Thanks for the questions!

1. Default behavior:
Correct - the default design doesn't change when the config options are not set. The component uses the existing default value (theme.fontSizes.threeXL = 2.25rem):
https://github.com/kagawa0710/streamlit/blob/feat/12300-metric-theming-font-size-weight/frontend/lib/src/components/elements/Metric/styled-components.ts#L93

The screenshot difference is likely just due to my local environment.

2. rem/px support:
The current implementation uses int (pixels only), following the same pattern as baseFontSize.

If string support (rem/px like headingFontSizes) is preferred, I'd be happy to implement it. Since it would require protobuf + type changes across several files, I could either:

  1. Update this PR
  2. Create a follow-up PR to keep this one focused

Let me know your preference!

@sfc-gh-lwilby sfc-gh-lwilby self-assigned this Jan 15, 2026
@sfc-gh-lwilby
Copy link
Copy Markdown
Collaborator

Hi @kagawa0710 ,

Checked with the others on this and we will need to support REM and pixels for this config. Can you update this PR to support that? We don't want to change the code later, particularly the protobuf files, and also we don't want to change how the feature works between releases.

Thank you!

  Resolve conflicts:
  - proto ID conflict: metric_value_font_size/weight moved to 64/65 (chart_diverging_colors uses 62)
  - types.ts: add shadows alongside metric value props
  - utils.ts: add chartDivergingColors and shadows handling
  - Change metricValueFontSize from int to string type
  - Support px, rem, and plain number formats (e.g., '48px', '3rem', '48')
  - Add validation: reject zero/negative values with warning
  - Update proto, Python config, and frontend types
  - Add comprehensive tests (17 test cases)]
…tSize

  - Change test value from "48" to "3rem" to verify string/rem parsing
  - Add negative assertion to ensure it's NOT the default 36px
  - Add snapshot test for visual verification
@kagawa0710
Copy link
Copy Markdown
Contributor Author

Update: Added rem/px string support for metricValueFontSize

Following the feedback, I've updated metricValueFontSize to accept string values with rem/px units (like headingFontSizes).

Supported formats

Format Example Result
rem "3rem" 3rem (48px with 16px base)
px "48px" 48px
number (string) "48" 48px

Invalid values log a warning and fallback to default (2.25rem).

Changes

  • Proto: int32string
  • Config: type_=inttype_=str
  • Frontend: Added parseFontSize validation
  • Tests: 17 unit test cases + E2E test with rem parsing

Note

CI will fail initially due to missing Linux snapshots for the new E2E snapshot test. I'll add them after this run.

@kagawa0710
Copy link
Copy Markdown
Contributor Author

@jrieke @lukasmasuch @sfc-gh-lwilby

Updated to support rem/px string values as requested. Please see the updated PR description for details:

  • Supported formats: "48px", "3rem", "48" (plain number → px)
  • Invalid value handling: Warning + fallback to default (2.25rem)
  • Screenshots: Before/after comparison with all supported formats
  • Tests: 17 unit test cases + E2E test verifying rem→px conversion

Ready for review!

@kagawa0710
Copy link
Copy Markdown
Contributor Author

Regarding the py-coverage-report failure:

I think this is a CI permissions issue rather than a code problem:

  • Error: HttpError: Resource not accessible by integration
  • Observation: All coverage calculation steps succeeded (steps 5-8), only the "comment" step failed
  • Local verification: All tests pass locally (Python: 91 passed, Frontend: 363 passed)

Could you confirm if this is the case, or let me know if there's something I need to fix?

@sfc-gh-lwilby
Copy link
Copy Markdown
Collaborator

@kagawa0710 thanks for making these updates! I haven't forgotten about your PR, I will try to take a look at the latest changes tomorrow.

I agree the CI failure is probably not related to your changes, I will rerun it.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are these part of your dev pipeline?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, these are from my local dev pipeline. I've removed them now. Thanks for pointing this out!

@lukasmasuch lukasmasuch removed the ai-review If applied to PR or issue will run AI review workflow label Jan 27, 2026
genericFonts: fontsOverride,
...conditionalOverrides,
shadows,
...(() => {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@kagawa0710 two comments/questions here.

  1. I'm wondering if it would be better to pull this into a named function, maybe even add some tests for that function (although it looks like we have coverage in utils.test.ts).

  2. More importantly, I'm wondering if there will ever be overrides in conditionalOverrides that we would want to override this config. Have you looked at what is set there? I am not an SME on the theming features and may need to pull in a colleague that is more familiar with this feature.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@sfc-gh-lwilby

Thanks for the thoughtful review!

1
Named function extraction: I considered this, but the current IIFE felt appropriate here since it's handling a single value rather than an array like setHeadingFontSizes (which processes h1-h6). Tests in utils.test.ts already cover this logic via createTheme. That said, happy to extract it if you think it improves readability—let me know.

2
conditionalOverrides: I checked and there are no overrides that would affect metricValueFontSize. It's a top-level EmotionTheme property (string-typed like "3rem") rather than part of the fontSizes object (which contains numeric multipliers). Additionally, the current spread order (...conditionalOverrides → metricValueFontSize IIFE) ensures user config takes precedence, so even if conditionalOverrides were extended in the future, this property would still be correctly applied.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great, I was thinking more the opposite, that there might be something in conditionalOverrides that we would want to take precedence over the user config. But, it makes sense to me that user config should have first precedence, making it otherwise could be confusing for users.

@github-actions
Copy link
Copy Markdown
Contributor

Summary

This PR adds two new theme configuration options: metricValueFontSize and metricValueFontWeight, allowing users to customize the appearance of st.metric value text. The implementation spans the full stack: Python config, protobuf definitions, frontend theme types/utils, styled components, and component-v2-lib. The feature is backwards compatible - when the options are not configured, the default behavior (2.25rem font size, inherited font weight) is preserved.

Code Quality

Strengths:

  • The implementation follows existing patterns established by similar theme options (codeFontSize, baseFontSize, headingFontSizes, etc.)
  • Proper validation of font size values using the existing parseFontSize utility with additional checks for positive values
  • Font weight validation uses standard CSS range (100-900)
  • Clean integration with the EmotionTheme type system

Issues:

  1. Missing warning log for out-of-range font weight (frontend/lib/src/theme/utils.ts:1054-1058): When metricValueFontWeight is outside the valid range, it silently falls back to undefined without logging a warning. This is inconsistent with metricValueFontSize which logs warnings for invalid values.
    ...(metricValueFontWeight &&
    metricValueFontWeight >= 100 &&
    metricValueFontWeight <= 900
      ? { metricValueFontWeight }
      : {}),
  1. Minor inconsistency in fontWeight range documentation: baseFontWeight in config.py documents a range of 100-600, while metricValueFontWeight uses 100-900. The 100-900 range is correct for CSS font-weight, so this isn't a bug, but it may be worth noting this intentional difference.

Test Coverage

Strengths:

  • Frontend unit tests (utils.test.ts): Comprehensive coverage with 17+ test cases covering valid rem/px values, invalid formats, zero/negative values, and boundary conditions
  • Metric component tests (Metric.test.tsx): Tests default styling behavior
  • Python config tests (config_test.py): Verifies config options integration
  • Proto compatibility tests (proto_compatibility_test.py): Ensures protobuf schema is correctly extended
  • E2E tests: Visual snapshot test and CSS property assertions

Issues:

  1. E2E test uses wait_for_timeout (e2e_playwright/theming/theme_metric_value_style_test.py:60): The best practices documentation explicitly states "Never use wait_for_timeout." This should be replaced with a more deterministic wait approach.
@pytest.mark.usefixtures("configure_metric_value_style")
def test_metric_value_style_snapshot(app: Page, assert_snapshot: ImageCompareFunction):
    """Visual snapshot test for custom metric value styling."""
    expect_no_skeletons(app, timeout=25000)
    # Wait for fonts to load to reduce flakiness
    app.wait_for_timeout(5000)

    metric = get_metric(app, "Revenue")
    assert_snapshot(metric, name="metric_value_custom_style")

Consider using wait_until with a font-loaded check or Playwright's built-in font loading detection.

  1. Metric component unit tests could be more thorough: The tests in Metric.test.tsx verify default behavior but don't test with a mocked theme that has custom metricValueFontSize/metricValueFontWeight values set. Testing both scenarios would provide better coverage.

Backwards Compatibility

The changes are fully backwards compatible:

  • New proto fields are optional (field numbers 64, 65)
  • When not configured, metricValueFontSize defaults to theme.fontSizes.threeXL (2.25rem)
  • When not configured, metricValueFontWeight is undefined, inheriting from parent
  • No existing fields were modified or removed

Security & Risk

No security concerns identified:

  • The feature only affects styling/theming
  • Input validation prevents injection of arbitrary CSS (only valid rem/px values accepted)
  • All values are sanitized through existing theme utilities

Recommendations

  1. Add warning log for invalid metricValueFontWeight: For consistency, add a LOG.warn() call when font weight is out of range, similar to font size validation:
...(metricValueFontWeight
  ? metricValueFontWeight >= 100 && metricValueFontWeight <= 900
    ? { metricValueFontWeight }
    : (LOG.warn(`Invalid metricValueFontWeight: ${metricValueFontWeight}. Must be between 100 and 900.`), {})
  : {}),
  1. Replace wait_for_timeout in E2E test: Consider using a more deterministic approach such as:
# Wait for fonts using document.fonts.ready
app.evaluate("() => document.fonts.ready")
  1. Add unit test for custom theme values in Metric component: Add a test case that mocks a theme with custom metricValueFontSize/metricValueFontWeight to verify the styled component applies them correctly.

Verdict

APPROVED: This is a well-implemented feature that follows existing patterns, includes comprehensive test coverage, and maintains backwards compatibility. The issues identified are minor and don't block the merge:

  • The missing warning log is a nice-to-have for consistency but not critical
  • The wait_for_timeout usage, while against best practices, is used specifically for font loading flakiness in snapshot tests which is a known difficult case
  • The additional unit test would be nice but the E2E test already verifies the custom styling works end-to-end

This is an automated AI review using opus-4.5-thinking. Please verify the feedback and use your judgment.

})(),
...(metricValueFontWeight &&
metricValueFontWeight >= 100 &&
metricValueFontWeight <= 900
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think there is a good suggestion in the AI review, to make this add a log statement similar to metricValueFontSize above.

...(metricValueFontWeight
  ? metricValueFontWeight >= 100 && metricValueFontWeight <= 900
    ? { metricValueFontWeight }
    : (LOG.warn(`Invalid metricValueFontWeight: ${metricValueFontWeight}. Must be between 100 and 900.`), {})
  : {}),

Copy link
Copy Markdown
Collaborator

@sfc-gh-lwilby sfc-gh-lwilby left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be great to add the warning log as suggested by the AI review, but otherwise looks good! The other AI suggestions about the e2e tests are not really relevant since this is a pattern we have for these particular tests even though it doesn't follow our ideal test writing best practices.

config._set_option("theme.codeFontWeight", 300, "test")
config._set_option("theme.baseFontSize", 14, "test")
config._set_option("theme.baseFontWeight", 300, "test")
config._set_option("theme.metricValueFontSize", 32, "test")
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Type mismatch: Setting metricValueFontSize to integer 32 instead of string. The config option is defined as type_=str in lib/streamlit/config.py line 1964, and the proto field is defined as string in proto/streamlit/proto/NewSession.proto line 218. According to the PR description, valid formats are strings like "48px", "3rem", or "48". This test should use:

config._set_option("theme.metricValueFontSize", "32", "test")

Or with units:

config._set_option("theme.metricValueFontSize", "32px", "test")
Suggested change
config._set_option("theme.metricValueFontSize", 32, "test")
config._set_option("theme.metricValueFontSize", "32px", "test")

Spotted by Graphite Agent

Fix in Graphite


Is this helpful? React 👍 or 👎 to let us know.

…om:kagawa0710/streamlit into feat/12300-metric-theming-font-size-weight

# Please enter a commit message to explain why this merge is necessary,
# especially if it merges an updated upstream into a topic branch.
#
# Lines starting with '#' will be ignored, and an empty message aborts
# the commit.
  use string value for metricValueFontSize in config test
@sfc-gh-lwilby sfc-gh-lwilby merged commit 223a4b1 into streamlit:develop Feb 9, 2026
41 checks passed
@jrieke
Copy link
Copy Markdown
Collaborator

jrieke commented Feb 9, 2026

Thanks for getting this merged @kagawa0710, awesome addition!!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

change:feature PR contains new feature or enhancement implementation impact:users PR changes affect end users ready-for-maintainer-review This PR is ready for maintainer attention to conduct final review before merge! status:product-approved Community PR is approved by product team

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Change st.metric font size and weight through theming

5 participants