pythondarkmodepy-shiny

How to dynamically adjust colors depending on the dark mode?


The default header color for DataFrames (render.DataGrid(), render.DataTable()) is a very light grey (probably gainsboro). If I switch an app to dark mode using and DataFrame the header becomes unreadable. Is there any way to make a dict to tell shiny what color to use for the color gainsboro or for a specific object, if I switch to dark mode?

Here's an (not so minimal) MRE:

from shiny import App, render, ui
import polars as pl

app_ui = ui.page_fillable(
    ui.layout_sidebar(
        ui.sidebar(ui.input_dark_mode()),
        ui.layout_columns(
            ui.card(
                ui.card_header("card_header1", style='background:gainsboro'),
                ui.output_data_frame("card1"),
                full_screen=True
            ),
            col_widths=12
        )
    )
)

def server(input, output, session):

    @output
    @render.data_frame
    def card1():
        return render.DataGrid(pl.DataFrame({'a': ['a','b','c','d']}), filters=True)

app = App(app_ui, server)

Solution

  • We can supply an id to the input_dark_mode(), say id="mode". And then we can define css dynamically depending on input.mode() (which is either "light" or "dark") for the required selectors. Below is an example for the card header.

    Express

    from shiny.express import input, render, ui
    
    with ui.layout_sidebar():
        with ui.sidebar():  
            ui.input_dark_mode(id="mode")
        with ui.layout_columns():
            with ui.card():
                ui.card_header("card_header1", id="card_header1")
    
    @render.ui
    def style():
        color = "gainsboro" if input.mode() == "light" else "red"
        css = f"#card_header1 {{ background: {color} }}"
        
        return ui.tags.style(css)
    

    Core

    from shiny import App, ui, reactive
    
    app_ui = ui.page_fillable(
        ui.layout_sidebar(
            ui.sidebar(ui.input_dark_mode(id="mode")),
            ui.layout_columns(
                ui.card(
                    ui.card_header("card_header1", id="card_header1"),
                    full_screen=True
                ),
                col_widths=12
            )
        )
    )
    
    
    def server(input, output, session):
    
        @reactive.effect
        @reactive.event(input.mode)
        def _():
            ui.remove_ui("#header_color")  # Remove any previously added style tag
            
            color = "gainsboro" if input.mode() == "light" else "red"
            css = f"#card_header1 {{ background: {color} }}"
    
            # Add the CSS to the head of the document
            if css:
                style = ui.tags.style(css, id="#header_color")
                ui.insert_ui(style, selector="head")
    
    app = App(app_ui, server)
    

    enter image description here