Skip to content

feat: Add built-in color names support for chart color parameter#12698

Closed
K-dash wants to merge 7 commits intostreamlit:developfrom
K-dash:feature/built-in-color-names-12694
Closed

feat: Add built-in color names support for chart color parameter#12698
K-dash wants to merge 7 commits intostreamlit:developfrom
K-dash:feature/built-in-color-names-12694

Conversation

@K-dash
Copy link
Copy Markdown
Contributor

@K-dash K-dash commented Oct 5, 2025

Describe your changes

Summary

Adds support for Streamlit's built-in color names in the color= parameter of built-in chart functions (st.line_chart, st.bar_chart, st.area_chart, st.scatter_chart). Color names are resolved to theme tokens, enabling theme-aware color usage that automatically adapts to light/dark mode.

In Scope

  • Built-in charts only: st.line_chart, st.bar_chart, st.area_chart, st.scatter_chart
  • 8 built-in color names: red, orange, yellow, blue, green, violet, gray/grey, primary
  • Theme-aware resolution: color names → theme tokens (e.g., "red"theme.redColor)
  • Arrays and mixed formats: color=["blue", "green"], color=["primary", "#ff0000"]

Out of Scope

Core Implementation

  • Added built-in color name support in built_in_chart_utils.py:

    • BUILTIN_COLOR_NAMES: frozenset of 8 color names (red, orange, yellow, blue, green, violet, gray/grey, primary)
    • is_builtin_color_name(): Validates if a string is a built-in color name (case-insensitive)
    • resolve_builtin_color_name(): Resolves color names to theme tokens with fallback defaults
    • ThemeLike Protocol: Duck typing for theme objects
    • _resolve_color_names(): Helper to resolve color names in arrays
  • Integrated theme-aware color resolution in chart generation:

    • Modified generate_chart() to accept optional theme parameter
    • Added _get_theme() helper in vega_charts.py to retrieve current theme
    • Color resolution happens AFTER column parsing to preserve column name priority

Documentation Updates

  • Updated docstrings for all 4 chart functions with built-in color names documentation
  • Added examples showing built-in color usage
  • Clarified that built-in colors are theme-aware

Color Resolution Priority

  1. Column name (if exists in DataFrame) → use as column reference
  2. Built-in color name (if valid) → resolve to theme token
  3. HEX/RGB/other → pass through unchanged

Theme Resolution

  • With theme: "red"theme.redColor (e.g., #ff4b4b)
  • Without theme: "red" → default fallback (e.g., #ff4b4b)
  • Special case: "primary"theme.primaryColor

GitHub Issue Link (if applicable)

Closes #12694

Testing Plan

  • built_in_chart_utils_test.py:
    • Color name recognition (case-insensitive, grey/gray alias)
    • Theme resolution with fallbacks
    • Array handling and mixed types
  • vega_charts_test.py:
    • Vega-Lite spec validation (primary verification method)
    • Tests for all 4 chart types
    • Regression tests for existing HEX/RGB behavior

Contribution License Agreement

By submitting this pull request you agree that all contributions to this project are made under the Apache 2.0 license.

@snyk-io
Copy link
Copy Markdown
Contributor

snyk-io bot commented Oct 5, 2025

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.

@sfc-gh-lwilby sfc-gh-lwilby added security-assessment-completed impact:users PR changes affect end users status:needs-product-approval PR requires product approval before merging change:feature PR contains new feature or enhancement implementation labels Oct 6, 2025
@K-dash
Copy link
Copy Markdown
Contributor Author

K-dash commented Oct 6, 2025

@sfc-gh-lwilby
Fixed the missing test updates in vega_charts_test.py - I had updated the implementation to use correct default colors from config.py, but forgot to update the test expectations accordingly.

CI should be green now!

Please review when you get a chance. Thanks 🙏

@lukasmasuch lukasmasuch requested a review from Copilot October 8, 2025 11:01
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 built-in color name support to Streamlit's chart functions (st.line_chart, st.bar_chart, st.area_chart, st.scatter_chart). Users can now use theme-aware color names like "red", "blue", "primary", etc., which automatically resolve to appropriate hex values based on the current theme.

  • Added built-in color name recognition and theme-aware resolution system
  • Integrated color name resolution into chart generation pipeline with proper priority handling
  • Updated documentation for all chart functions to include built-in color usage examples

Reviewed Changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.

File Description
lib/streamlit/elements/lib/built_in_chart_utils.py Core implementation of built-in color name support with validation, resolution, and theme integration
lib/streamlit/elements/vega_charts.py Integration of theme retrieval and color resolution into chart functions with updated documentation
lib/tests/streamlit/elements/lib/built_in_chart_utils_test.py Comprehensive unit tests for color name validation, resolution, and theme handling
lib/tests/streamlit/elements/vega_charts_test.py Integration tests verifying color resolution in Vega-Lite specs and regression tests for existing functionality

@lukasmasuch
Copy link
Copy Markdown
Collaborator

@cursor review

ctx = get_script_run_ctx()
if ctx and hasattr(ctx, "session_state") and ctx.session_state:
return getattr(ctx.session_state, "theme", None)
return None
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Bug: Theme retrieval function always returns None

The _get_theme() function attempts to retrieve a theme object from ctx.session_state.theme, but this attribute is never set anywhere in the Streamlit codebase. The session_state object is for user-defined variables, not system theme configuration. Theme colors are actually accessible via config.get_option("theme.redColor") etc. As a result, _get_theme() always returns None, and the feature falls back to hardcoded light-theme default colors instead of respecting the user's actual theme configuration. The feature advertised as "theme-aware" won't actually adapt to custom themes.

Fix in Cursor Fix in Web

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.

@K-dash can you take a look at this feedback?

Copy link
Copy Markdown
Contributor Author

@K-dash K-dash Jan 10, 2026

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 catching this bug! You're absolutely right.

The _get_theme() function was attempting to access ctx.session_state.theme, which is never set anywhere in the codebase—session_state is for user-defined variables, not system configuration.

e732f0d

Fix:

  • Removed _get_theme() and the ThemeLike protocol entirely
  • Updated resolve_builtin_color_name() to use config.get_option("theme.{color}Color") directly
  • Falls back to hardcoded defaults when config returns None (no custom theme)

Tests updated:

  • Modified built_in_chart_utils_test.py to mock config.get_option instead of using MockTheme
  • Added tests for both custom theme config and default fallback scenarios
  • All existing tests pass

The feature now correctly respects user's custom theme configuration.

@jrieke
Copy link
Copy Markdown
Collaborator

jrieke commented Dec 29, 2025

Hey @K-dash! Sorry for the delay. Could you add some screenshots of all different chart types with the different colors, so I can quickly check whether this looks correct?

@jrieke jrieke added the missing:visual-demonstration PR should include a video or screenshots showing how the feature/fix works label Dec 29, 2025
@K-dash
Copy link
Copy Markdown
Contributor Author

K-dash commented Dec 29, 2025

@jrieke
Hi! I've attached screenshots showing all chart types with the different built-in colors in both light and dark modes.
Please zoom in to view the details, as these are full-page screenshots.

Let me know if you need any additional screenshots or clarification.

light-2025-12-30-07_51_28 dark-2025-12-30-07_48_01

@K-dash K-dash force-pushed the feature/built-in-color-names-12694 branch from 7169f37 to cc15020 Compare December 29, 2025 23:04
@jrieke
Copy link
Copy Markdown
Collaborator

jrieke commented Dec 30, 2025

That's awesome, thank you so much for that screenshot, very helpful! Looks correct in my eyes, and I also roughly checked that we're using the same colors for the sparkline charts in st.metric. Approved from product side, will get a review from the engs.

One thing that is a little bit annoying/inconsistent is that for st.area_chart, we're currently using the main colors (e.g. blueColor) instead of the background colors (e.g. blueBackgroundColor), and then apply some opacity if there are multiple areas. Potentially something we should rethink/clean up in the future, but for now we're following the existing behavior with this PR, so that's fine!

@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 Dec 30, 2025
@github-actions
Copy link
Copy Markdown
Contributor

Summary

This PR adds support for Streamlit's built-in color names in the color= parameter of built-in chart functions (st.line_chart, st.bar_chart, st.area_chart, st.scatter_chart). Users can now use color names like "red", "blue", "green", "orange", "yellow", "violet", "gray" (or "grey"), and "primary" as color values. These color names are resolved to theme tokens, enabling theme-aware color usage.

Key Changes:

  • Added BUILTIN_COLOR_NAMES constant and utility functions (is_builtin_color_name, resolve_builtin_color_name, _resolve_color_names) in built_in_chart_utils.py
  • Added ThemeLike Protocol for duck-typing theme objects
  • Updated generate_chart() to accept a theme parameter
  • Added _get_theme() helper in vega_charts.py to retrieve theme from script run context
  • Updated docstrings for all 4 chart functions to document the new color names
  • Updated error message in StreamlitInvalidColorError to mention built-in color names

Code Quality

Strengths:

  • Clean, well-organized code that follows existing patterns in the codebase
  • Good use of Protocol for duck-typing the theme object
  • Proper use of frozenset for the constant to ensure immutability
  • Color resolution correctly happens AFTER column parsing to maintain column name priority
  • Case-insensitive color name matching
  • Grey/gray aliasing handled correctly

Minor Issue:

In lib/streamlit/elements/lib/built_in_chart_utils.py (lines 136-137), the docstring example shows an incorrect hex value for grey:

    Examples
    --------
    >>> resolve_builtin_color_name("red", theme)
    "#ff4b4b"  # theme.redColor
    >>> resolve_builtin_color_name("primary", theme)
    "#ff4b4b"  # theme.primaryColor (prioritized)
    >>> resolve_builtin_color_name("grey", theme)
    "#808495"  # theme.grayColor (grey is alias for gray)

The value #808495 is incorrect. According to config.py, grayColor defaults to #a3a8b8 for the light theme. The code itself uses the correct value (defaults["gray"] = "#a3a8b8"), but the docstring example is wrong.

Question about _get_theme() implementation:

The _get_theme() function in vega_charts.py (lines 110-121) attempts to retrieve a theme from ctx.session_state.theme:

def _get_theme() -> Any:
    """Get the current theme from script run context.

    Returns
    -------
    Any | None
        Theme object if available, None otherwise
    """
    ctx = get_script_run_ctx()
    if ctx and hasattr(ctx, "session_state") and ctx.session_state:
        return getattr(ctx.session_state, "theme", None)
    return None

I couldn't find evidence that session_state.theme is populated anywhere in the codebase (theme colors are typically accessed via st.get_option("theme.primaryColor")). The code gracefully handles None by falling back to default colors, so this works correctly, but it means theme-aware color resolution may not actually be functional at runtime until a theme object is added to session_state. This might be intentional for future-proofing. A brief comment explaining when session_state.theme would be populated would be helpful.

Test Coverage

Excellent test coverage with both unit tests and integration tests:

Unit Tests (lib/tests/streamlit/elements/lib/built_in_chart_utils_test.py):

  • TestIsBuiltinColorName: Tests valid color names (with case variations), invalid inputs (non-strings, unknown colors, hex colors)
  • TestResolveBuiltinColorName: Tests theme resolution, no-theme defaults, grey alias, case insensitivity, theme fallback
  • TestResolveColorNames: Tests None handling, single built-in colors, non-built-in passthrough, color lists, mixed lists
  • TestBuiltinColorNamesConstant: Tests expected colors are present, frozenset type

Integration Tests (lib/tests/streamlit/elements/vega_charts_test.py):

  • test_builtin_color_name_resolved_to_hex: All 4 chart types
  • test_builtin_color_names_in_list_resolved: Line, area, bar charts with color lists
  • test_builtin_color_name_case_insensitive: Tests RED, Red, rEd variations
  • test_grey_alias_resolved_to_gray: Validates grey → gray aliasing
  • test_non_builtin_color_passed_through: Hex colors remain unchanged
  • test_column_name_takes_priority_over_color_name: Critical edge case for backwards compatibility

The tests follow the Python unit test best practices:

  • Uses pytest.mark.parametrize for parameterized tests
  • Has clear, descriptive docstrings
  • Tests are fully type annotated
  • Proper use of @parameterized.expand in the integration tests

Note: The tests use a MockTheme class since the actual theme object from session_state is not easily available in tests. This is an appropriate testing pattern.

Backwards Compatibility

This change is NOT breaking. Before this PR:

  • Named colors like "blue" were not valid color arguments
  • Passing color="blue" would raise StreamlitInvalidColorError (unless a column named "blue" existed)
  • Only hex strings (#fff), RGB tuples, and column names were accepted

After this PR:

  • Named colors like "blue" are now valid and resolve to theme colors
  • Users who previously got errors with color names will now get working charts

The test file update confirms this:

# Old: bad_args = ["foo", "blue"]
# New: bad_args = ["foo", "notacolor"]  # "blue" is now a valid built-in color name

The column name priority logic ensures that if a DataFrame has a column named "red", using color="red" will still reference that column, not the built-in color. This is explicitly tested.

Security & Risk

No security concerns identified.

Low regression risk:

  • Changes are additive (new functionality that was previously an error)
  • Column name priority is preserved
  • Existing hex/RGB color handling is unchanged
  • The fallback to default colors when theme is unavailable ensures reliability

Recommendations

  1. Fix docstring (minor): Update the example in resolve_builtin_color_name docstring from #808495 to #a3a8b8 for the grey example.

  2. Consider adding a comment: In _get_theme(), add a brief comment explaining when/if session_state.theme is expected to be populated, or if this is intentional future-proofing.

  3. Optional enhancement: Consider whether CSS named colors beyond Streamlit's built-in palette (e.g., "pink", "cyan") should be supported in the future, potentially using a different mechanism.

Verdict

APPROVED: This is a well-implemented feature addition with excellent test coverage. The code follows existing patterns, is backwards compatible, and includes comprehensive documentation updates. The single minor issue (docstring typo) is trivial and doesn't block approval.


This is an automated AI review. Please verify the feedback and use your judgment.

@sfc-gh-lwilby sfc-gh-lwilby added ready-for-maintainer-review This PR is ready for maintainer attention to conduct final review before merge! and removed missing:visual-demonstration PR should include a video or screenshots showing how the feature/fix works labels Jan 9, 2026
…ion_state

Remove _get_theme() which always returned None since ctx.session_state.theme
is never set. Theme colors are now retrieved directly via config.get_option()
in resolve_builtin_color_name().
@K-dash K-dash requested a review from sfc-gh-lwilby January 10, 2026 08:31


# Streamlit's built-in color names (7 basic palette colors + grey alias + primary)
BUILTIN_COLOR_NAMES: Final[frozenset[str]] = frozenset(
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.

[suggestion: blocking] From the issue, it sounds like these colours are the same ones we are using for Markdown, it would be good to share the utils for the built-in colours amongst all places that use these built-in colours to ensure consistency.

"sort_column": sort_column,
},
# Chart styling properties
color=color_from_user,
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.

This might need some updates as well, can you manually check whether the colour work after rows are added?



def _resolve_color_names(
color: str | Color | list[Color] | None,
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.

Have you considered handling the built-in colours in _get_color_encoding?

# Array case: resolve each element
if isinstance(color, list):
return [
resolve_builtin_color_name(cast("str", c))
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.

It seems like here c could be a tuple of RGB/RGBA here? I was just wondering if we could eliminate that case by doing this in _get_color_encoding so that we don't have to do a casting to string here. Perhaps not, though.


# Single value case
if is_builtin_color_name(color):
return resolve_builtin_color_name(cast("str", color))
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.

Here it should be a str if is_builtin_color_name(color) is true. Alternatively, maybe just check if it is a tuple and if so, don't pass it to is_builtin_color_name?

For example,

if not isInstance(tuple, color) and is_builtin_color_name(color):
    ... 

Then you should be able to update the input type to "str" for is_builtin_color_name" from Any` as well.

)


def is_builtin_color_name(color: Any) -> bool:
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 the type for color could be more specific, maybe either expand it to include Color (which includes Tuple), or handle the Tuple case outside of this function.


# Default values (fallback when config returns None)
# Source: lib/streamlit/config.py theme color definitions (light theme defaults)
defaults: dict[str, str] = {
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.

Do we already have this defined somewhere, where the logic could be shared with other areas that are using the built-in colors?

return isinstance(color, str) and color.lower() in BUILTIN_COLOR_NAMES


def resolve_builtin_color_name(name: str) -> str:
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.

Wondering actually if this whole function is not already a util somewhere since we use these built-in colours for other elements?

"blue",
"green",
"violet",
"gray",
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.

"GRAY" as well?

"",
None,
123,
["red"],
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.

Maybe also a tuple like (255, 0, 0)?

def test_resolve_with_custom_theme_config(self) -> None:
"""Test color resolution when theme is configured via config.get_option()."""

def mock_get_option(key: str) -> str | None:
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.

Can you use the existing util from tests.testutil import patch_config_options?

assert resolve_builtin_color_name("primary") == "#ff4b4b"

def test_grey_alias(self) -> None:
"""Test that 'grey' is correctly aliased to 'gray'."""
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.

Can you test upper case GREY as well?

assert result == "mycolumn"


class TestBuiltinColorNamesConstant:
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.

These tests seem unnecessary, they are testing the implementation.

assert color_value == "#ff4b4b", (
f"Expected default red color #ff4b4b, got {color_value}"
)
assert color_value != "red", (
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.

This seems redundant with the assert above?

[
(st.line_chart, "line"),
(st.area_chart, "area"),
(st.bar_chart, "bar"),
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.

Is there a reason to exclude area charts?

]
)
def test_builtin_color_names_in_list_resolved(
self, chart_command: Callable, mark_type: str
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.

It doesn't seem like the mark_type param is used.

proto = self.get_delta_from_queue().new_element.arrow_vega_lite_chart
chart_spec = json.loads(proto.spec)

# Handle both layer (line_chart) and non-layer structures
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.

We are only testing line chart here, so the if else isn't necessary.

proto = self.get_delta_from_queue().new_element.arrow_vega_lite_chart
chart_spec = json.loads(proto.spec)

# Handle both layer (line_chart) and non-layer structures
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.

Same here.

df = pd.DataFrame([[20, 30]], columns=["a", "b"])

# Hex color should pass through
st.line_chart(df, x="a", y="b", color="#AABBCC")
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.

Should we also do the RGB/RGBA tuple?

proto = self.get_delta_from_queue().new_element.arrow_vega_lite_chart
chart_spec = json.loads(proto.spec)

# Handle both layer (line_chart) and non-layer structures
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.

The test only includes line charts.

# If a column is named "red", it should be used as a column, not resolved as a color
df = pd.DataFrame([[20, 30, 1]], columns=["a", "b", "red"])

st.line_chart(df, x="a", y="b", color="red")
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.

Maybe vary these tests with different types. I agree we don't need to repeat these tests for every chart type though.

with pytest.raises(StreamlitColumnNotFoundError):
st.bar_chart(df, x="A", y="B", sort="-nonexistent_column")

@parameterized.expand(
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.

[suggestion] For all of these new unit tests, it might be better to parameterize them by color_val and then expected_proto_val, then it is easy to scan through the tests and see all of the cases of input mapped to output.

For checking the different chart types, you could run all the tests on all the chart types and then add subtests for each chart type.

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

@K-dash I've just been chatting with my theming-SME teammate, and it looks like we need to take a different approach for this to make sure that all of the theming works with this. I will share some more details afterwards.

@github-actions
Copy link
Copy Markdown
Contributor

Summary

This PR adds support for a small set of Streamlit “built-in” color names (e.g. "red", "blue", "primary") in the color= parameter of built-in chart commands (st.line_chart, st.area_chart, st.bar_chart, st.scatter_chart). The names are resolved to theme palette colors via Streamlit config options and then emitted into the Vega-Lite spec as concrete color values.

Code Quality

  • Overall structure looks good: resolving color names after _parse_generic_column preserves column-name priority, which avoids breaking data-driven color when a column happens to be named "red" (lib/streamlit/elements/lib/built_in_chart_utils.py:L331).

  • Potential robustness issue with theme config values:

    • resolve_builtin_color_name returns the raw config.get_option(...) value (lib/streamlit/elements/lib/built_in_chart_utils.py:L158-L166).
    • Downstream, built-in charts only accept hex or rgb()/rgba() strings (and tuples) via is_color_like / to_css_color (lib/streamlit/elements/lib/built_in_chart_utils.py:L1221-L1248, lib/streamlit/elements/lib/color_util.py:L60-L80).
    • Streamlit theme options are typed as str without additional validation (lib/streamlit/config.py:L332-L364), and Streamlit tests elsewhere set theme.redColor to "red" (a CSS color name) (see lib/tests/streamlit/config_test.py:L1067 via repo search).
    • If a user (or a test) configures e.g. theme.redColor="red", this PR can cause st.*_chart(color="red") to raise StreamlitInvalidColorError because "red" is not considered color-like by color_util (it is neither hex nor rgb()/rgba()), and _get_color_encoding will fall through to raise StreamlitInvalidColorError(...) (lib/streamlit/elements/lib/built_in_chart_utils.py:L1248).
  • API surface / encapsulation:

    • BUILTIN_COLOR_NAMES, is_builtin_color_name, and resolve_builtin_color_name are now public module-level symbols (lib/streamlit/elements/lib/built_in_chart_utils.py:L64, L79, L108). If these are intended as internal helpers (they currently appear only used within this module), Streamlit’s Python guide suggests prefixing internal module-level names with _ to avoid accidental external coupling.
  • Docs alignment:

    • Docstrings in vega_charts.py describe “resolved to theme color tokens” (lib/streamlit/elements/vega_charts.py:L729-L731, etc.). The current implementation resolves to the configured theme color string and then writes a concrete value into the Vega-Lite spec. That’s probably fine, but “tokens” could be read as “frontend-resolved CSS variables” rather than “theme palette values from config.”

Test Coverage

  • Python unit tests added:

    • New unit tests cover case-insensitive matching, grey aliasing, config key mapping, list handling, and fallbacks (lib/tests/streamlit/elements/lib/built_in_chart_utils_test.py:L30-L236). These follow the repo’s unit test conventions (pytest, typed signatures, docstrings).
  • Vega spec assertions are good, but could be more deterministic:

    • lib/tests/streamlit/elements/vega_charts_test.py adds spec-level tests ensuring the string "red" becomes "#ff4b4b" (L2222-L2248) and that lists resolve (L2255+), plus a good anti-regression check for column-name priority (L2338+).
    • However, these tests implicitly assume the resolved value is the light-theme default #ff4b4b and #1c83e1 (lib/tests/streamlit/elements/vega_charts_test.py:L2242, L2273). If global config/theme state changes across the test suite, these could become flaky. Patching config.get_option (or otherwise isolating theme config) in these tests would make them resilient while still verifying correct behavior.
  • No e2e tests:

    • That seems acceptable here since the change is backend-side parsing/spec generation and is covered by unit/spec tests. Nothing in e2e_playwright/ was modified.

Backwards Compatibility

  • Behavior change: previously, color="blue" (with no "blue" column) would be treated as an invalid color string and raise. Now it is accepted and mapped to a theme color. This is the intended feature, but note it may also mask user mistakes where "blue" was a typo for a column name that does not exist (it will now silently “work” with a constant color).

  • Column-name priority is preserved, so DataFrames that do have a "red"/"blue" column won’t be broken (lib/streamlit/elements/lib/built_in_chart_utils.py:L331, test at lib/tests/streamlit/elements/vega_charts_test.py:L2338+).

Security & Risk

  • Low security risk: changes are confined to chart spec generation and config lookups.
  • Main regression risk: raising StreamlitInvalidColorError due to unexpected (but plausible) theme config values that are not hex / rgb(a) (see Code Quality section).

Recommendations

  1. Harden resolve_builtin_color_name against non-hex / non-rgb(a) theme values:
    • In lib/streamlit/elements/lib/built_in_chart_utils.py:L108-L166, validate the returned config value using the same acceptance criteria as charts (e.g., is_color_like(...) / is_css_color_like(...)), and fallback to the default palette value when invalid rather than returning an incompatible string that later triggers StreamlitInvalidColorError (L1248).
  2. Decide whether these helpers are intended to be public:
    • If not, rename to _BUILTIN_COLOR_NAMES, _is_builtin_color_name, _resolve_builtin_color_name and keep _resolve_color_names as the only internal entry point.
  3. Make Vega spec tests deterministic with respect to theme config:
    • In lib/tests/streamlit/elements/vega_charts_test.py:L2222-L2348, patch streamlit.elements.lib.built_in_chart_utils.config.get_option to return stable values (or None to force fallback) so the expected spec colors don’t depend on global config state.
  4. Add at least one spec-level test for "primary":
    • The unit tests cover primary mapping (lib/tests/streamlit/elements/lib/built_in_chart_utils_test.py:L75-L109), but adding a single Vega spec assertion would ensure the full chart pipeline handles "primary" correctly too.

Verdict

CHANGES REQUESTED: The feature is solid and well-tested overall, but resolve_builtin_color_name should defensively handle theme config values that aren’t valid Streamlit/Vega chart color inputs to avoid unexpected StreamlitInvalidColorError regressions.


This is an automated AI review. Please verify the feedback and use your judgment.

@mayagbarnes
Copy link
Copy Markdown
Collaborator

Hi @K-dash, thank you so much for your efforts in contributing this feature to streamlit!
We would like to implement this theme-awareness of colors, but think its best to do the handling on the frontend instead.
I started on a PR here consistent with this approach. Unless you have any objections, I'll get that one across the line instead and provide co-author credit.

@K-dash
Copy link
Copy Markdown
Contributor Author

K-dash commented Jan 29, 2026

@mayagbarnes
Hi,thanks for reaching out. I have no objections at all. Please go ahead with your PR!
I appreciate the co-author credit as well.

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 do-not-merge PR is blocked from merging 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.

Support built-in color names for the color parameter of charts

6 participants