Skip to content

Runtime light/dark theme toggle with end-to-end export support #20

@nicolasguelfi

Description

@nicolasguelfi

Description

Allow users to define a light/dark theme in their StreamTeX document and switch between themes at runtime via a sidebar toggle, with the selected theme correctly propagated through the entire pipeline: live rendering, HTML export, and PDF export.

Currently, the export pipeline (generate_full_html()) reads theme colors exclusively from st.get_option("theme.backgroundColor"), which reads .streamlit/config.toml — a static file loaded once at server startup. There is no mechanism to override theme colors at runtime for the export.

Problem Analysis

The current theme color resolution chain for the export HTML is:

generate_full_html() → ExportConfig.theme_bg → _get_theme_color() → st.get_option() → config.toml

The PdfConfig at lines 375-376 and 438-439 of book.py reads session_state["_stx_theme_bg"], but this only affects the PDF footer/page-number color — not the HTML body background, which is written inline by generate_full_html() at line 368 of export.py:

f"body {{ font-family: Arial, Helvetica, sans-serif; background: {bg}; color: {text}; }}\n"

This inline style on <body> cannot be overridden by CSS injected via stx.st_html() into the export buffer content, because buffer content is rendered inside <div class="streamtex-page">, which is a child of the <body> that already has the hardcoded background.

What was tried and why it failed

Approach Why it fails
st.html(css) with Streamlit selectors Not in the export buffer at all
stx.st_html(css) with body { background: #fff !important } In the buffer, but buffer content is inside <div class="streamtex-page">, not at <body> level — the inline background on <body> wins
session_state["_stx_theme_bg"] = "#fff" Read by PdfConfig (line 375) but not by generate_full_html() which calls _get_theme_color()st.get_option() → config.toml
Modify config.toml at runtime Streamlit doesn't reload config.toml without server restart

Proposed Solution

Minimal fix (2 lines in export.py)

Add session_state lookup in the resolution chain of generate_full_html(), lines 356-358:

# Before (current):
bg = c.theme_bg or _get_theme_color("theme.backgroundColor", "#fff")
text = c.theme_text or _get_theme_color("theme.textColor", "#333")

# After (proposed):
bg = c.theme_bg or st.session_state.get(_STX_THEME_BG_KEY) or _get_theme_color("theme.backgroundColor", "#fff")
text = c.theme_text or st.session_state.get(_STX_THEME_TEXT_KEY) or _get_theme_color("theme.textColor", "#333")

This adds one level of priority: ExportConfig.theme_bg > session_state > config.toml, and is backward-compatible (existing projects that don't set session_state are unaffected).

Full feature (sidebar toggle)

  1. Add a theme_toggle: bool = False parameter to st_book()
  2. When enabled, render a st.sidebar.toggle("Dark mode") in the sidebar Settings expander (alongside existing toggles like "Wrap All")
  3. On toggle change, update session_state["_stx_theme_bg"] and session_state["_stx_theme_text"]
  4. Apply sts.theme = dark_overrides or sts.theme = light_overrides for StreamTeX style IDs
  5. Inject CSS via stx.st_html() for Streamlit DOM overrides (live rendering)
  6. With the minimal fix above, the export pipeline automatically picks up the correct colors

Optional: st_book() theme parameter

st_book(
    [...],
    theme="light",         # or "dark", or "auto" (follow config.toml)
    theme_toggle=True,     # show toggle in sidebar
)

Files to modify

File Change
streamtex/export.py L356-358 Add session_state lookup in generate_full_html()
streamtex/book.py Add theme_toggle parameter, render sidebar toggle, update session_state
streamtex/book.py L796 Optionally pass theme_bg/theme_text to ExportConfig from session_state

Environment

Key Value
StreamTeX >=0.3.0 (editable install, dev)
Python 3.10.9
OS Darwin 25.4.0 arm64
UV 0.8.12
Project ai4se6d
Branch main
Commit 4e17082

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions