Skip to content
fossil-skin Skin Architecture
Login

Skin Architecture

How the dark theme works and how to modify it.


Files

File Purpose
skin/css.txt All CSS — design tokens, layout, timeline fixes, dark-mode overrides
skin/header.txt TH1 template — HTML injected before page content on every Fossil page
skin/footer.txt TH1 template — HTML injected after page content
skin/apply.py Writes skin parts to every repo's SQLite config table
www/index.html Custom landing page served by Caddy at /
www/a11y.js Accessibility + dark-mode JS served by Caddy at /a11y.js

Design Tokens

Defined in :root in css.txt. Match these in www/index.html too.

Token Value Usage
--bg #0f1117 Page background
--surface #1a1d27 Cards, nav bar, input fields
--surface2 #22263a Hover states, tooltips
--border #2a2d3e All borders
--accent #6c8ebf Links, active nav, focus indicators
--accent2 #4a7c9e Logo gradient secondary
--text #e2e8f0 Primary text
--muted #8892a4 Secondary text, nav items
--green #4ade80 Success states
--red #f87171 Error states, diff deletions
--yellow #fbbf24 Warning, warp commits
--radius 8px Standard border radius
--font Inter, system-ui Body text
--mono JetBrains Mono, Fira Code Code, monospace elements

Deploying Skin Changes

After editing any skin file, push to repos:

python3 /var/fossil/skin/apply.py

That's it. Fossil serves the updated skin immediately — no restart needed.


Key Dark-Mode Overrides

Timeline graph lines

Fossil's built-in CSS hardcodes all graph elements black (.tl-node, .tl-line, .tl-arrow). On a dark background these are completely invisible. Every graph selector is overridden with !important using --muted for lines/arrows and --accent for nodes.

Branch row colors

Fossil JS sets style.backgroundColor on timeline cells with light pastel branch colors designed for white backgrounds. Two layers:

  1. Inline styles (JS-injected) — caught with td[style*="background"]. We overlay a semi-transparent dark layer using background-image: linear-gradient(rgba(15,17,23,0.75), ...). CSS renders background-image on top of background-color, so the pastel bleeds through as a subtle hue tint while keeping text contrast above 6:1.

  2. Stylesheet rule — Fossil's built-in CSS also applies background-color: #efefef to .timelineModernCell[id]. Overridden with background-color: transparent.

Chat message bubbles

Same issue as branch colors: Fossil JS sets inline background-color from the user's assigned color (m.uclr). Same background-image overlay technique with 72% dark opacity.

isDark flag

Fossil's C code hardcodes isDark: false for all custom skins, ignoring the skin-detail config key. This affects the timeline graph renderer (it picks white vs. black node colors). Fixed in a11y.js via window.fossil.config.skin.isDark = true — runs after Fossil's own inline scripts because of defer, and again on window.load because Fossil's load handlers may reset it.


TH1 Template Notes

The header.txt and footer.txt files use Fossil's TH1 scripting language (a Tcl subset).

Available variables in header/footer:

The capexpr function checks user capabilities — used in the mainmenu loop to hide links the current user can't access.


CSP and JavaScript

Fossil's Content Security Policy:

script-src 'self' 'nonce-<per-request-nonce>'

Inline scripts without the matching nonce are blocked. a11y.js is served as a static asset by Caddy at /a11y.js, which satisfies 'self'. It's loaded in header.txt with:

<script defer src="/a11y.js"></script>

The defer attribute ensures:

  1. HTML is fully parsed before the script runs
  2. Fossil's inline config scripts (which set window.fossil) have already executed
  3. The script runs before DOMContentLoaded

window.load re-runs the timeline and isDark fixes because Fossil's own load handlers insert SVG graph elements and may reset config values.