Skip to content

feat: Allow customizing emphasis and bold text colors via html_theme_options#356

Merged
mmcky merged 11 commits intomainfrom
feature/customizable-emphasis-colors
Feb 6, 2026
Merged

feat: Allow customizing emphasis and bold text colors via html_theme_options#356
mmcky merged 11 commits intomainfrom
feature/customizable-emphasis-colors

Conversation

@mmcky
Copy link
Contributor

@mmcky mmcky commented Feb 6, 2026

Summary

Resolves #355 — Allow customizing emphasis, bold/strong, and definition text colors via html_theme_options.

Adds six new theme options that let users override the default colors for emphasis (italic), bold/strong, and definition list terms in both light and dark modes, using CSS custom properties with graceful fallbacks.

New Theme Options

Option Description Default
emphasis_color Color for em tags in light mode #2d9f42 (green)
emphasis_color_dark Color for em tags in dark mode #66bb6a (light green)
strong_color Color for strong/b tags in light mode #8b4513 (brown)
strong_color_dark Color for strong/b tags in dark mode #cd853f (peru)
definition_color Color for definition list terms (dl dt) in light mode Inherits from strong_color
definition_color_dark Color for definition list terms (dl dt) in dark mode Inherits from strong_color_dark

Usage

Sphinx conf.py:

html_theme_options = {
    "emphasis_color": "#1a73e8",
    "emphasis_color_dark": "#8ab4f8",
    "strong_color": "#d93025",
    "strong_color_dark": "#f28b82",
    "definition_color": "#6a1b9a",
    "definition_color_dark": "#ce93d8",
}

Jupyter Book _config.yml:

sphinx:
  config:
    html_theme_options:
      emphasis_color: "#1a73e8"
      emphasis_color_dark: "#8ab4f8"
      strong_color: "#d93025"
      strong_color_dark: "#f28b82"
      definition_color: "#6a1b9a"
      definition_color_dark: "#ce93d8"

Any option left empty uses the theme's built-in default. The definition_color options target Sphinx definition lists (dl.simple, dl.glossary, dl.field-list), while strong_color applies to all inline strong/b text. Definitions fall back to the strong color by default via CSS variable chain.

Implementation

  • SCSS: CSS custom properties (--qe-emphasis-color, --qe-strong-color, --qe-definition-color) with SCSS fallback values in _base.scss and _dark-theme.scss
  • Theme config: Six new options registered in theme.conf (all default to empty)
  • Template: layout.html conditionally injects <style> block with CSS variable overrides on :root and body.dark-theme
  • Tests: 40 tests covering theme options, SCSS variables, compiled CSS output, and template injection
  • Docs: Updated configure.md with usage examples, Jupyter Book config, and option reference table

Testing

All 40 custom color tests pass, plus existing test suite (65 total tests pass).

…options

Add four new theme options to customize emphasis (em) and definition
(strong/b) text colors for both light and dark modes:

- emphasis_color: Color for em tags in light mode (default: #2d9f42)
- emphasis_color_dark: Color for em tags in dark mode (default: #66bb6a)
- definition_color: Color for strong/b tags in light mode (default: #8b4513)
- definition_color_dark: Color for strong/b tags in dark mode (default: #cd853f)

Implementation approach:
- Replace hardcoded SCSS colors with CSS custom properties (var())
  with fallback values matching the original defaults
- Register new options in theme.conf with empty defaults
- Inject custom property overrides via inline style in layout.html
  when theme options are set

This is fully backward-compatible: existing sites see no visual change.
When options are left empty, CSS var() fallbacks provide the original colors.

Closes #355
@codecov
Copy link

codecov bot commented Feb 6, 2026

Codecov Report

❌ Patch coverage is 90.90909% with 1 line in your changes missing coverage. Please review.
⚠️ Please upload report for BASE (main@c25b251). Learn more about missing BASE report.

Files with missing lines Patch % Lines
src/quantecon_book_theme/__init__.py 90.90% 1 Missing ⚠️
Additional details and impacted files
@@           Coverage Diff           @@
##             main     #356   +/-   ##
=======================================
  Coverage        ?   46.48%           
=======================================
  Files           ?        2           
  Lines           ?      398           
  Branches        ?        0           
=======================================
  Hits            ?      185           
  Misses          ?      213           
  Partials        ?        0           
Flag Coverage Δ
pytests 46.48% <90.90%> (?)

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ 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.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@github-actions
Copy link
Contributor

github-actions bot commented Feb 6, 2026

🎭 Visual Regression Test Results

passed  45 passed
skipped  1 skipped

Details

stats  46 tests across 1 suite
duration  58 seconds
commit  c88b659

Skipped tests

mobile-chrome › theme.spec.ts › Theme Features › f-string interpolation styling

@github-actions
Copy link
Contributor

github-actions bot commented Feb 6, 2026

@github-actions github-actions bot temporarily deployed to pull request February 6, 2026 00:24 Inactive
mmcky added 2 commits February 6, 2026 11:28
The option applies to all <strong>/<b> elements, not just definitions.
Sphinx renders actual definitions via <dl>/<dt> elements which can be
targeted separately. Renamed across SCSS, theme.conf, layout.html,
tests, and documentation.
Add definition_color and definition_color_dark theme options that
target actual Sphinx definition list elements (dl.simple dt,
dl.glossary dt, dl.field-list dt) separately from strong/bold text.

The definition color falls back to the strong color via CSS variable
chain: var(--qe-definition-color, var(--qe-strong-color, fallback)),
so definitions inherit from strong_color by default but can be
independently customized.
@github-actions github-actions bot temporarily deployed to pull request February 6, 2026 00:41 Inactive
mmcky added 2 commits February 6, 2026 11:53
Add 5 Playwright visual tests in a new 'Typography Styling' describe block:
- bold text styling (light mode)
- italic text styling (light mode)
- mixed bold and italic styling
- bold text in dark mode
- italic text in dark mode

Uses .qe-page__content selectors to target paragraphs containing
<strong> and <em> elements on names.html, numpy.html, and
python_by_example.html.

Note: definition list (<dl>) tests not included as the lecture site
does not currently contain definition lists.
@mmcky
Copy link
Contributor Author

mmcky commented Feb 6, 2026

/update-new-snapshots

1 similar comment
@mmcky
Copy link
Contributor Author

mmcky commented Feb 6, 2026

/update-new-snapshots

@github-actions
Copy link
Contributor

github-actions bot commented Feb 6, 2026

✅ Visual snapshots have been updated and committed to this PR.

Copy link
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 pull request implements a feature to allow customization of emphasis (italic), bold/strong, and definition text colors through html_theme_options, resolving issue #355. The implementation uses CSS custom properties with graceful fallbacks to SCSS-defined default colors, allowing users to override text styling colors without needing custom CSS files.

Changes:

  • Added six new theme options (emphasis_color, emphasis_color_dark, strong_color, strong_color_dark, definition_color, definition_color_dark) for customizing text colors in light and dark modes
  • Implemented CSS variable injection in layout.html to apply user-specified colors via :root and body.dark-theme selectors
  • Updated SCSS files (_base.scss and _dark-theme.scss) to use CSS custom properties with fallback values for backward compatibility
  • Added comprehensive test coverage (40 tests) covering theme configuration, SCSS variables, compiled CSS, and template injection
  • Added visual regression tests for typography styling in both light and dark modes
  • Updated documentation with usage examples for both Sphinx and Jupyter Book configurations
  • Enhanced developer documentation (.github/copilot-instructions.md) with GitHub CLI best practices

Reviewed changes

Copilot reviewed 8 out of 16 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
src/quantecon_book_theme/theme/quantecon_book_theme/theme.conf Registers six new color customization options with empty defaults
src/quantecon_book_theme/theme/quantecon_book_theme/layout.html Conditionally injects CSS custom property overrides when theme options are set
src/quantecon_book_theme/assets/styles/_base.scss Updates em, strong/b, and dl dt selectors to use CSS variables with SCSS fallbacks
src/quantecon_book_theme/assets/styles/_dark-theme.scss Updates dark mode colors to use CSS variables with fallbacks
tests/test_custom_colors.py Adds 40 tests verifying theme options, SCSS variables, compiled CSS, and template injection
tests/visual/theme.spec.ts Adds visual regression tests for bold and italic text in light and dark modes
tests/visual/__snapshots__/**/*.png Visual test baseline snapshots for typography styling
docs/configure.md Documents the new color customization options with usage examples and reference table
.github/copilot-instructions.md Adds GitHub CLI best practices for avoiding shell escaping issues

@github-actions github-actions bot temporarily deployed to pull request February 6, 2026 02:27 Inactive
- Validate color theme options against safe CSS patterns (hex, named colors,
  rgb/hsl functions) at build time
- Invalid values are ignored with a warning logged
- Add 14 tests for validation regex (valid and malicious patterns)
- Add security note to documentation

Addresses Copilot code review feedback on PR #356.
@mmcky
Copy link
Contributor Author

mmcky commented Feb 6, 2026

Addressing Copilot Review Feedback

Thanks for the review. Both comments have been addressed in commit f77e6a1:

1. CSS Injection Vulnerability (layout.html)

Rather than the suggested |e filter approach (which would quote the values and break CSS custom properties), I added server-side validation in __init__.py. A validate_color_options() function runs at builder-inited time and checks all 6 color options against a regex that allows only safe CSS color patterns:

  • Hex codes: #RGB, #RRGGBB, #RRGGBBAA
  • Named colors: red, DarkSlateGray, etc.
  • Functions: rgb(), rgba(), hsl(), hsla()
  • CSS variables: var(--custom-prop)

Invalid values (e.g. red; } body { display: none; } /*) are replaced with empty strings and a Sphinx warning is logged.

2. Security Note in Documentation (configure.md)

Added a {note} admonition explaining that color values are validated at build time and should only come from trusted configuration files.

Tests

Added 14 new tests in TestColorValueValidation covering both valid color formats and malicious injection patterns (semicolons, braces, url(), expression()). Total: 54 tests passing.

@github-actions github-actions bot temporarily deployed to pull request February 6, 2026 02:46 Inactive
@mmcky mmcky closed this Feb 6, 2026
@mmcky mmcky reopened this Feb 6, 2026
@github-actions github-actions bot temporarily deployed to pull request February 6, 2026 03:39 Inactive
@github-actions github-actions bot temporarily deployed to pull request February 6, 2026 04:20 Inactive
@mmcky mmcky merged commit 8119fb3 into main Feb 6, 2026
10 checks passed
@mmcky mmcky deleted the feature/customizable-emphasis-colors branch February 6, 2026 04:26
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.

Feature: Allow customizing emphasis and bold text colors via html_theme_options

1 participant