Skip to content

Conversation

@rich-iannone
Copy link
Member

This PR adds the .opt_css() method. It allows users to add custom CSS to HTML-output tables. This opt_*() method builds on the existing functionality of the tab_options(table_additional_css=) option by allowing users to more easily and safely append CSS rulesets to that Options parameter.

from great_tables import GT, exibble
import polars as pl

exibble_mini = pl.from_pandas(exibble).select(["num", "currency"])

(
    GT(exibble_mini, id="one")
    .fmt_currency(columns="currency", currency="HKD")
    .fmt_scientific(columns="num")
    .opt_css(
        css='''
        #one .gt_table {
          background-color: skyblue;
        }
        #one .gt_row {
          padding: 20px 30px;
        }
        #one .gt_col_heading {
          text-align: center !important;
        }
        '''
    )
)
image

Notice in that rendered table that: (1) body cells get a 'skyblue' background color, (2) the padding of body cells is increased horizontally and vertically, and (3) column-label text is now centered instead of being right-aligned.

Fixes: #174

@codecov
Copy link

codecov bot commented Oct 1, 2025

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 91.67%. Comparing base (de3580d) to head (7e6ab01).

Additional details and impacted files
@@            Coverage Diff             @@
##             main     #775      +/-   ##
==========================================
+ Coverage   91.61%   91.67%   +0.05%     
==========================================
  Files          47       47              
  Lines        5773     5790      +17     
==========================================
+ Hits         5289     5308      +19     
+ Misses        484      482       -2     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@github-actions github-actions bot temporarily deployed to pr-775 October 1, 2025 19:36 Destroyed
@github-actions github-actions bot temporarily deployed to pr-775 October 6, 2025 02:31 Destroyed
@rich-iannone rich-iannone marked this pull request as ready for review October 6, 2025 02:38
@rich-iannone rich-iannone requested a review from machow as a code owner October 6, 2025 02:38
@github-actions github-actions bot temporarily deployed to pr-775 October 6, 2025 18:49 Destroyed
@machow
Copy link
Collaborator

machow commented Jan 12, 2026

Code review (updated)

MICHAEL NOTES:

  • None of these issues seems too bad

Found 5 issues (note: all scored below 80% confidence threshold, so these are minor):

  1. Test functions test multiple behaviors (CLAUDE.md says "Test one behavior per case") - Score: 75

    test_opt_css_basic() tests both basic CSS addition AND chaining; test_opt_css_duplicate_handling() tests both default duplicate prevention AND allowing duplicates.

    def test_opt_css_basic():
    # Test adding CSS
    res = GT(exibble).opt_css(css=".gt_table { background-color: red; }")
    assert res._options.table_additional_css.value == [".gt_table { background-color: red; }"]
    # Test chaining CSS additions
    res_2 = res.opt_css(css=".gt_row { color: blue; }")
    assert len(res_2._options.table_additional_css.value) == 2
    assert ".gt_table { background-color: red; }" in res_2._options.table_additional_css.value
    assert ".gt_row { color: blue; }" in res_2._options.table_additional_css.value
    def test_opt_css_duplicate_handling():
    gt_tbl = GT(exibble)
    css_rule = ".gt_table { color: green; }"
    # Add same CSS twice (should only appear once)
    res = gt_tbl.opt_css(css=css_rule).opt_css(css=css_rule)
    assert res._options.table_additional_css.value == [css_rule]
    # Test for allowing duplicates
    res_2 = gt_tbl.opt_css(css=css_rule).opt_css(css=css_rule, allow_duplicates=True)
    assert res_2._options.table_additional_css.value == [css_rule, css_rule]

  2. Tests don't use kwargs dict pattern (CLAUDE.md says "Use kwargs dicts, only specify non-defaults") - Score: 75

    Tests use direct method calls instead of @pytest.mark.parametrize with kwargs dicts pattern.

    def test_opt_css_basic():
    # Test adding CSS
    res = GT(exibble).opt_css(css=".gt_table { background-color: red; }")
    assert res._options.table_additional_css.value == [".gt_table { background-color: red; }"]
    # Test chaining CSS additions
    res_2 = res.opt_css(css=".gt_row { color: blue; }")
    assert len(res_2._options.table_additional_css.value) == 2
    assert ".gt_table { background-color: red; }" in res_2._options.table_additional_css.value
    assert ".gt_row { color: blue; }" in res_2._options.table_additional_css.value
    def test_opt_css_duplicate_handling():
    gt_tbl = GT(exibble)
    css_rule = ".gt_table { color: green; }"
    # Add same CSS twice (should only appear once)
    res = gt_tbl.opt_css(css=css_rule).opt_css(css=css_rule)
    assert res._options.table_additional_css.value == [css_rule]
    # Test for allowing duplicates
    res_2 = gt_tbl.opt_css(css=css_rule).opt_css(css=css_rule, allow_duplicates=True)
    assert res_2._options.table_additional_css.value == [css_rule, css_rule]
    def test_opt_css_replace_mode():
    # Add initial CSS
    res = (
    GT(exibble).opt_css(css=".gt_table { color: red; }").opt_css(css=".gt_row { color: blue; }")
    )
    assert len(res._options.table_additional_css.value) == 2
    # Replace all CSS with `add=False`
    res_2 = res.opt_css(css=".gt_table { color: green; }", add=False)
    assert res_2._options.table_additional_css.value == [".gt_table { color: green; }"]
    def test_opt_css_whitespace_handling():
    gt_tbl = GT(exibble)
    # Test using empty-string CSS
    res = gt_tbl.opt_css(css="")
    assert res._options.table_additional_css.value == []
    # Test whitespace-only CSS
    res_2 = gt_tbl.opt_css(css=" \n \t ")
    assert res_2._options.table_additional_css.value == []
    # Test CSS with leading and trailing whitespace (it should get stripped)
    res_3 = gt_tbl.opt_css(css=" .gt_table { color: red; } ")
    assert res_3._options.table_additional_css.value == [".gt_table { color: red; }"]
    def test_opt_css_multiline():
    multiline_css = """
    #test_table .gt_table {
    background-color: skyblue;
    }
    #test_table .gt_row {
    padding: 20px;
    }"""
    result = GT(exibble, id="test_table").opt_css(css=multiline_css)
    expected_css = multiline_css.strip()
    assert result._options.table_additional_css.value == [expected_css]
    def test_opt_css_html_output():
    css_rule = "#test_table .gt_table { background-color: lightblue; }"
    res = GT(exibble, id="test_table").opt_css(css=css_rule)
    html = res.as_raw_html()
    # Check that the CSS appears in the HTML string
    assert css_rule in html
    # For the CSS ordering, the added CSS should come after the default CSS
    default_css_pos = html.find("#test_table .gt_table { display: table;")
    custom_css_pos = html.find(css_rule)
    assert default_css_pos > 0
    assert custom_css_pos > 0
    assert custom_css_pos > default_css_pos
    def test_opt_css_with_tab_options():
    res = (
    GT(exibble)
    .tab_options(table_additional_css=".initial { color: red; }")
    .opt_css(css=".added { color: blue; }")
    )
    css_list = res._options.table_additional_css.value
    assert len(css_list) == 2
    assert ".initial { color: red; }" in css_list
    assert ".added { color: blue; }" in css_list
    # Test that `opt_css()` rule comes after the `tab_options()` rule in the rendered HTML
    html = res.as_raw_html()
    assert html.index(".added { color: blue; }") > html.index(".initial { color: red; }")

  3. Empty string not filtered in tab_options() normalization - Score: 50

    When tab_options(table_additional_css="") is called, it becomes [""] instead of []. This is a rare edge case with minimal impact (extra newline in CSS output).

    # - `table_additional_css` should be a list but if given as a string, ensure it is list
    if "table_additional_css" in modified_args:
    if isinstance(modified_args["table_additional_css"], str):
    modified_args["table_additional_css"] = [modified_args["table_additional_css"]]

  4. Comment capitalization inconsistency - Score: 50

    One comment starts with lowercase (# if CSS already exists...) while all others in the function start with capitals.

    # Use tab_options() to set the additional CSS

  5. Missing test coverage for edge cases - Score: 75

    No explicit tests for: opt_css(css="", add=False) clearing existing CSS, or opt_css(css="") preserving existing CSS.

    res = gt_tbl.opt_css(css="")
    assert res._options.table_additional_css.value == []
    # Test whitespace-only CSS
    res_2 = gt_tbl.opt_css(css=" \n \t ")
    assert res_2._options.table_additional_css.value == []
    # Test CSS with leading and trailing whitespace (it should get stripped)
    res_3 = gt_tbl.opt_css(css=" .gt_table { color: red; } ")
    assert res_3._options.table_additional_css.value == [".gt_table { color: red; }"]


All issues are below the 80% confidence threshold for reporting as definite problems. The implementation is solid overall.

🤖 Generated with Claude Code

- If this code review was useful, please react with 👍. Otherwise, react with 👎.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

epic: Implement opt_css()

3 participants