Skip to content

Switch the docs theme to Furo (proof of concept) - #603

Draft
honzajavorek wants to merge 12 commits into
masterfrom
honzajavorek/furo
Draft

honzajavorek wants to merge 12 commits into
masterfrom
honzajavorek/furo

Conversation

@honzajavorek

@honzajavorek honzajavorek commented Nov 5, 2026 •

Copy link
Copy Markdown
Member

Proof of concept for #590. It swaps sphinx_rtd_theme, which is no longer developed, for Furo. Furo has dark mode built in: it follows the system setting and has a toggle.

Honza's decisions

  • Keep Furo's defaults wherever possible. Add custom.css only for things we actually don't like in the theme, so the old blockquote and image margin rules are gone.
  • Use Furo's default code highlighting instead of monokai, unless we decide we don't like it.
  • The SVG illustrations and the logo must adapt to light and dark mode and follow Furo's toggle, not just the OS setting, like on docs.apify.com/academy. Use a single adaptive logo image.
  • Set brand colours so the site looks more like Pyvec.
  • In dark mode, the illustrations should be light like the logo, not dim grey.
  • The sidebar in light mode was too plain, with barely any brand colour. Make it more Pyvec.
  • Selected text must stay readable. It was dark blue on a light page when the OS was in dark mode.
  • Don't override selection colours. Leave them to the browser and OS.
  • Dark-mode admonitions shouldn't be black, and the search placeholder must be readable.
  • Check the whole site for contrast violations, following Vzhůru dolů: Barevný kontrast: 4.5:1 for normal text, 3:1 for large text, 7:1 as the ideal.
  • Tables must not be centred.
  • The "Oblasti podpory" list must not render as a weird box.
  • The content must not jump around while the page loads.
  • The OG image must use the same blue as the docs.
  • The OG image's source is the SVG, and the PNG is exported from it. A manual export documented in CONTRIBUTING is enough. It doesn't need to happen automatically during the build.

Changes

  • pyproject.toml / uv.lock: replace sphinx-rtd-theme with furo.
  • docs/conf.py:
    • html_theme = "furo", with html_title set to the project name.
    • Code highlighting uses Furo's defaults.
    • Added "edit on GitHub" links through source_repository.
    • Brand colours come from pyvec.org: blue #0097df, slate #232f36 and #3b4f5a. Furo uses the brand colour for link text, and #0097df on white is only 3.2:1, so the blues are adjusted to pass WCAG AA. Light mode uses #0079b3 on white backgrounds. Dark mode uses #33b1ec on slate backgrounds.
    • The sidebar is Pyvec slate (#232f36, the pyvec.org header) in both light and dark mode, with #33b1ec links. Every text colour in it passes WCAG AA, including on hover.
    • Dark mode also sets slate admonitions (Furo's are near-black), slate-tinted greys (#aab8c0 secondary, #909ca4 muted) and a lighter visited purple (#bb8aee). Furo's greys and purple fell below 4.5:1 on the slate backgrounds.
  • Illustrations and logo follow the theme, including the toggle. The SVGs are plain black now; their prefers-color-scheme <style> is gone, which also supersedes Prefer black even for dark mode (for now, until we have dark mode) #464. In dark mode, custom.css inverts them to #ccc. It uses the same selectors Furo uses for its own colours: body[data-theme="dark"], plus the system setting when the toggle is on "auto". The rule targets only img, because Sphinx also puts the class on the wrapping link, and the filter would otherwise apply twice and make the illustrations look dim. Illustrations opt in with :class: adaptive-image. The logo is a single image (org.svg). The sidebar is always slate, so the logo is always inverted to #ccc, and the white org-i.svg is deleted.
  • custom.css no longer has the old blockquote and image margin rules. It has only:
    • the dark-mode inversion of illustrations and the logo;
    • a fix for the sidebar title, which Furo's overflow-wrap: break-word broke mid-word ("česk / é");
    • color-scheme matching Furo's active theme (the sidebar is always dark). Furo doesn't declare it, so browsers followed the OS. On a light page with the OS in dark mode, the native text selection was dark blue and unreadable. Now the browser and OS pick native selection colours that fit the page, with no colours of our own. Selected blue links on the dark-mode selection are readable but weaker; that's the browser's choice.
    • the search placeholder in the search icon's colour (5.8:1). The browser's default grey was about 2.6:1 on slate.
    • left-aligned tables. Sphinx centres tables that don't set :align:, and Furo keeps that (sphinx_rtd_theme overrode it).
    • the logo's aspect ratio, so the sidebar doesn't jump by 112 px when the logo loads.
  • Removed four .. contents:: directives (bylaws, runbooks, Twitter, the top of financial support). Furo shows them in red with an "ERROR: … unnecessary" message, because its "On this page" panel already lists the sections. Their links were also only 3:1 on the red. Two lists stay, because they're part of the text ("Poskytujeme tyto hlavní služby:" and "Oblasti podpory"). They use Furo's opt-out class, so they render as normal lists. Furo turns a titled contents into a teal box with a pencil icon, so the "Oblasti podpory" title is now a plain sentence before the list.
  • New pyvec_docs.ext.image_dimensions extension: it adds width and height, read from the viewBox, to SVG <img> tags. Browsers then reserve space for illustrations before they load (before this, the content below jumped by up to 265 px). The displayed size still comes from :width: and CSS. It has unit tests.
  • OG image: the panel was #2980b9, the old sphinx_rtd_theme blue. It's now #0079b3, the docs' light-mode brand blue, in og-image.svg, the source, and in og-image.png, exported from it. CONTRIBUTING.rst has a new section on exporting the PNG by hand with Inkscape, including the Inter Display Bold font it needs. The committed PNG was produced with exactly that command. Inkscape re-renders the original PNG to within 2/255 per pixel, so only the colour changed.
  • Fixed ukulele.svg. Removing its <style> also removed its opening <svg> tag, because they were on one line, so the image was broken in earlier previews of this PR.
  • docs/_templates/page.html: no changes needed. Furo provides the htmltitle and extrahead blocks, and the built pages still have the custom <title> and the og:image tags.

Testing

  • Layout shifts: with SVG loading artificially delayed, Chromium's layout-shift API reports no visible shifts on the home, meetup, beginners' course and financial support pages, at both desktop and mobile widths. Before, the sidebar moved 112 px and content moved up to 265 px. Rendered image sizes match the previous build.
  • Contrast: axe-core 4.14 (the engine behind Lighthouse) on all 26 built pages, in light and dark mode, finds no WCAG AA violations. axe doesn't check placeholders, typed text in the search box, or visited links, so I checked those by hand. About 1,300 elements meet AA but not the 7:1 AAA level. They're mostly the brand-blue links and Furo's grey secondary text.
  • Read the Docs preview: https://pyvec-guide--603.org.readthedocs.build/ (built after rebasing on Install uv from PyPI on Read the Docs #604, which fixed the Read the Docs install).
  • sphinx-build -W and pytest pass.
  • I checked in headless Chromium that the illustration filter applies correctly in all five combinations: light, dark, auto with a dark OS, light forced on a dark OS, and dark forced on a light OS.

🤖 Generated with Claude Code

honzajavorek and others added 2 commits November 5, 2026 10:32
Proof of concept for #590.
Furo has dark mode built in, so this also reverts the SVG workaround
from #464.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Illustrations and the logo are plain black SVGs, inverted with CSS in
  dark mode, so they follow both the system setting and Furo's toggle.
  The white org-i.svg logo is no longer needed.
- Brand colours from pyvec.org, adjusted for WCAG AA contrast.
- Keep Furo's default code highlighting.
- Drop the old blockquote and image margin CSS, keep only what Furo needs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
honzajavorek and others added 10 commits November 5, 2026 11:01
Sphinx puts the image class on the wrapping link too, so the invert
filter applied twice and the illustrations came out dark grey.
Target only the img, so they match the logo.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The light sidebar was plain grey with barely any brand colour. Use the
slate from the pyvec.org header, with Pyvec blue links. The logo is now
always on slate, so it is always inverted.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Furo does not style ::selection, so on a light page with the OS in dark
mode, browsers used a dark blue selection and text became unreadable.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The unreadable selection came from Furo not declaring color-scheme, so
browsers used the OS dark-mode selection on light pages. Declaring the
active scheme lets the browser pick fitting native colours.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Audited all 26 pages in light and dark mode with axe-core (WCAG AA,
4.5:1 for text). Fixes:

- Dark mode greys and visited links were below 4.5:1 on the slate
  backgrounds. Use slate-tinted greys and a lighter visited purple.
- Admonitions in dark mode used Furo's near-black background, which
  clashed with slate. Use slate.
- The search placeholder used the browser's default grey (about 2.6:1
  on slate). Use the search icon colour (5.8:1).
- Furo shows `.. contents::` in red with an error message, and links in
  it had 3:1 contrast. Remove the ones that only duplicate "On this
  page". Keep the two that are part of the text, using Furo's opt-out.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Sphinx centres tables without :align:, and Furo keeps that, unlike
  sphinx_rtd_theme. Align them left like the rest of the text.
- A titled `.. contents::` renders as a teal topic box with a pencil
  icon in Furo. Use a plain sentence before the list instead.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
In this file the <style> was on the same line as <svg>, so removing the
<style> line also removed the root element and broke the image.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- New pyvec_docs.ext.image_dimensions adds width and height attributes,
  read from the viewBox, to SVG images in the HTML. Browsers use them as
  the aspect ratio before the image loads. The displayed size still comes
  from :width: and CSS.
- Furo's logo has no dimensions, so give it the aspect ratio of org.svg.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The panel used #2980b9 from sphinx_rtd_theme. Use the brand blue the
docs use for links in light mode, #0079b3.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
og-image.svg is the source, og-image.png is exported from it by hand
with Inkscape. The PNG is regenerated with the documented command.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

This branch has not been deployed

No deployments
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.

1 participant