diff --git a/pygitweb/main.py b/pygitweb/main.py
index ba4b5de..0306bb6 100644
--- a/pygitweb/main.py
+++ b/pygitweb/main.py
@@ -248,7 +248,7 @@ def git_project_list(
             pr.get('descr') or pr.get('path', "")]
              for pr in list_[:50]]
     )
-    pre = PREAMBLE.render(title=f'{esc_html(config.SITE_NAME)} - Projects', theme="dark", site_name=config.SITE_NAME)
+    pre = PREAMBLE.render(title=f'{esc_html(config.SITE_NAME)} - Projects', site_name=config.SITE_NAME)
     return HTMLResponse(f"{pre}<h1>Project List</h1>{table}{POSTAMBLE}")
 
 
@@ -308,7 +308,6 @@ def addproject_page(request: Request):
     """Add project form page."""
     pre = PREAMBLE.render(
         title=f"{config.SITE_NAME} - Add Project",
-        theme="dark",
         site_name=config.SITE_NAME,
     )
     tpl = env.get_template("addproject.html")
@@ -519,7 +518,7 @@ def dispatch(
     if action == "remotes":
         return git_remotes(proj)
     # Stub others with minimal response
-    pre = PREAMBLE.render(title=f"{esc_html(action)} - {esc_html(proj)}", theme="dark", site_name=config.SITE_NAME)
+    pre = PREAMBLE.render(title=f"{esc_html(action)} - {esc_html(proj)}", site_name=config.SITE_NAME)
     return HTMLResponse(
         f"{pre}<p>Action: {esc_html(action)}</p><p>Project: {esc_html(proj)}</p>{POSTAMBLE}"
     )
@@ -606,7 +605,7 @@ def git_summary(project: str) -> HTMLResponse:
             ["Remotes", f"<a href='/{project}?a=remotes'>view remotes</a>"],
         ]
     )
-    pre = PREAMBLE.render(title=f"{esc_html(config.SITE_NAME)} - {project}", theme="dark", site_name=config.SITE_NAME)
+    pre = PREAMBLE.render(title=f"{esc_html(config.SITE_NAME)} - {project}", site_name=config.SITE_NAME)
     body_parts = [f"{pre}<h1>{esc_html(project)}</h1>{table}"]
 
     readme = _get_readme_at_ref_path(project, head, "")
@@ -622,7 +621,7 @@ def git_remotes(project: str) -> HTMLResponse:
     """Remotes page: list configured remotes (name, url, push_url)."""
     remotes = git_get_remotes_info(project)
     if not remotes:
-        pre = PREAMBLE.render(title=f"Remotes - {esc_html(project)}", theme="dark", site_name=config.SITE_NAME)
+        pre = PREAMBLE.render(title=f"Remotes - {esc_html(project)}", site_name=config.SITE_NAME)
         return HTMLResponse(
             f"{pre}<h1>Remotes</h1><p>No remotes configured.</p>{POSTAMBLE}"
         )
@@ -636,7 +635,7 @@ def git_remotes(project: str) -> HTMLResponse:
         cols=["Name", "URL", "Push URL"],
         rows=rows,
     )
-    pre = PREAMBLE.render(title=f"Remotes - {esc_html(project)}", theme="dark", site_name=config.SITE_NAME)
+    pre = PREAMBLE.render(title=f"Remotes - {esc_html(project)}", site_name=config.SITE_NAME)
     return HTMLResponse(f"{pre}<h1>Remotes</h1>{table}{POSTAMBLE}")
 
 
@@ -684,7 +683,7 @@ def git_blob(project: str, h: str | None, f: str | None, *, raw: bool = False) -
         f"</div>"
     )
     blob_script = '<script src="static/blob-view.js"></script>'
-    pre = PREAMBLE.render(title=f"{esc_html(f)} - {esc_html(project)}", theme="dark", site_name=config.SITE_NAME)
+    pre = PREAMBLE.render(title=f"{esc_html(f)} - {esc_html(project)}", site_name=config.SITE_NAME)
     return HTMLResponse(f"{pre}{blob_html}{blob_script}{POSTAMBLE}")
 
 
@@ -725,7 +724,7 @@ def git_tree(project: str, h: str | None, f: str | None) -> HTMLResponse:
             [f'<a href="{link}">{esc_html(name)}</a>', "blob"]
         )
     title_path = f" / {f}" if f else ""
-    pre = PREAMBLE.render(title=f"{esc_html(project)}{esc_html(title_path)} - Tree", theme="dark", site_name=config.SITE_NAME)
+    pre = PREAMBLE.render(title=f"{esc_html(project)}{esc_html(title_path)} - Tree", site_name=config.SITE_NAME)
     table = env.get_template("table.html").render(
         cols=["Name", "Type"],
         rows=rows
@@ -849,7 +848,7 @@ def git_log(project: str, h: str | None, request: Request, page: int, pagecount:
         rows.append(_format_commit_table_row(project, commit, short=False))
     ref_display = h[:7] if h else "HEAD"
     title = f"Log - {esc_html(project)} @ {esc_html(ref_display)}"
-    pre = PREAMBLE.render(title=title, theme="dark", site_name=config.SITE_NAME)
+    pre = PREAMBLE.render(title=title, site_name=config.SITE_NAME)
     table = env.get_template("table.html").render(
         cols=["Commit", "Subject", "Author", "Date", "Age"],
         rows=rows
@@ -874,7 +873,7 @@ def git_shortlog(project: str, h: str | None, request: Request, page: int, pagec
         rows.append(_format_commit_table_row(project, commit, short=True))
     ref_display = h[:7] if h else "HEAD"
     title = f"Shortlog - {esc_html(project)} @ {esc_html(ref_display)}"
-    pre = PREAMBLE.render(title=title, theme="dark", site_name=config.SITE_NAME)
+    pre = PREAMBLE.render(title=title, site_name=config.SITE_NAME)
     table = env.get_template("table.html").render(
         cols=["Commit", "Subject", "Author", "Age"],
         rows=rows
@@ -903,7 +902,7 @@ def git_history(project: str, h: str | None, f: str | None, request: Request, pa
         rows.append(_format_commit_table_row(project, commit, short=False))
     ref_display = h[:7] if h else "HEAD"
     title = f"History - {esc_html(f)} - {esc_html(project)} @ {esc_html(ref_display)}"
-    pre = PREAMBLE.render(title=title, theme="dark", site_name=config.SITE_NAME)
+    pre = PREAMBLE.render(title=title, site_name=config.SITE_NAME)
     table = env.get_template("table.html").render(
         cols=["Commit", "Subject", "Author", "Date", "Age"],
         rows=rows
@@ -955,7 +954,7 @@ def git_tags(project: str, request: Request, page: int, pagecount: int) -> HTMLR
             obj_cell,
         ])
     title = f"Tags - {esc_html(project)}"
-    pre = PREAMBLE.render(title=title, theme="dark", site_name=config.SITE_NAME)
+    pre = PREAMBLE.render(title=title, site_name=config.SITE_NAME)
     table = env.get_template("table.html").render(
         cols=["Tag", "Object"],
         rows=rows,
@@ -1016,7 +1015,7 @@ def git_tag(project: str, h: str | None) -> HTMLResponse:
         table_rows.append(["Message", f"<pre class='tag-message'>{esc_html(message)}</pre>"])
     table = env.get_template("table.html").render(cols=["Field", "Value"], rows=table_rows)
     title = f"Tag {esc_html(tag_name)} - {esc_html(project)}"
-    pre = PREAMBLE.render(title=title, theme="dark", site_name=config.SITE_NAME)
+    pre = PREAMBLE.render(title=title, site_name=config.SITE_NAME)
     return HTMLResponse(
         f"{pre}<h1>Tag {esc_html(tag_name)}</h1><p>Project: <a href='/{project}'>{esc_html(project)}</a></p>{table}{POSTAMBLE}"
     )
@@ -1064,7 +1063,7 @@ def git_commit(project: str, h: str | None) -> HTMLResponse:
     table_rows.append(["", f'<a href="{diff_link}">diff</a> · <a href="{patch_link}">patch</a>'])
     table = env.get_template("table.html").render(cols=["Field", "Value"], rows=table_rows)
     title = f"Commit {esc_html(oid_short)} - {esc_html(project)}"
-    pre = PREAMBLE.render(title=title, theme="dark", site_name=config.SITE_NAME)
+    pre = PREAMBLE.render(title=title, site_name=config.SITE_NAME)
     return HTMLResponse(
         f"{pre}<h1>Commit {esc_html(oid_short)}</h1><p>Project: <a href='/{project}'>{esc_html(project)}</a></p>{table}{POSTAMBLE}"
     )
@@ -1104,7 +1103,7 @@ def git_commitdiff(project: str, h: str | None) -> HTMLResponse:
     # Base64-encode diff so we can embed safely in HTML without breaking script tags
     diff_b64 = base64.b64encode(diff_text.encode("utf-8")).decode("ascii")
     title = f"Commit diff {oid_short} - {project}"
-    pre = PREAMBLE.render(title=title, theme="dark", site_name=config.SITE_NAME)
+    pre = PREAMBLE.render(title=title, site_name=config.SITE_NAME)
     body = env.get_template("commitdiff.html").render(
         project=project,
         oid_short=oid_short,
diff --git a/pygitweb/static/main.css b/pygitweb/static/main.css
index f385f0b..84398cf 100644
--- a/pygitweb/static/main.css
+++ b/pygitweb/static/main.css
@@ -1,6 +1,100 @@
-@import "./themes/light.css";
-@import "./themes/dark.css";
-@import "./themes/solarized.css";
+/*
+ * Custom themes: load an extra stylesheet after this one that redefines
+ * the same --pgw-* (and optional --d2h-*) variables on :root.
+ * Example: <link rel="stylesheet" href="static/themes/solarized.css">
+ */
+
+:root {
+	color-scheme: light;
+	/* Surfaces */
+	--pgw-bg: #fafafa;
+	--pgw-bg-raised: #ffffff;
+	--pgw-bg-muted: #f4f4f5;
+	--pgw-border: #e4e4e7;
+	--pgw-border-muted: #d4d4d8;
+
+	--pgw-text: #18181b;
+	--pgw-text-muted: #52525b;
+	--pgw-text-dim: #71717a;
+
+	--pgw-link: #2563eb;
+	--pgw-link-visited: #7c3aed;
+	--pgw-link-hover: #dc2626;
+
+	--pgw-accent-bg: #e4e4e7;
+	--pgw-accent-text: #18181b;
+	--pgw-title-bg: #f4f4f5;
+	--pgw-title-hover-bg: #e4e4e7;
+
+	--pgw-code-bg: #f4f4f5;
+	--pgw-code-border: #e4e4e7;
+
+	--pgw-table-stripe: #fafafa;
+	--pgw-table-hover: #f4f4f5;
+
+	--pgw-add-fg: #166534;
+	--pgw-add-bg: #dcfce7;
+	--pgw-rem-fg: #991b1b;
+	--pgw-rem-bg: #fee2e2;
+	--pgw-info-fg: #52525b;
+
+	--pgw-blob-selection-bg: rgba(193, 173, 0, 0.35);
+
+	/* diff2html (optional overrides) */
+	--d2h-info-bg-color: #f0f0f0;
+	--d2h-file-header-bg-color: rgb(220, 220, 255);
+	--d2h-empty-placeholder-bg-color: #f5f5f5;
+	--d2h-del-bg-color: rgb(255, 220, 220);
+	--d2h-ins-bg-color: rgb(220, 255, 220);
+	--d2h-dim-color: #666;
+	--d2h-bg-color: #fff;
+}
+
+@media (prefers-color-scheme: dark) {
+	:root {
+		color-scheme: dark;
+		--pgw-bg: #1a1a1e;
+		--pgw-bg-raised: #25252c;
+		--pgw-bg-muted: #2d2d36;
+		--pgw-border: #3d3d48;
+		--pgw-border-muted: #35353d;
+
+		--pgw-text: #e4e4e7;
+		--pgw-text-muted: #a1a1aa;
+		--pgw-text-dim: #71717a;
+
+		--pgw-link: #7dd3fc;
+		--pgw-link-visited: #c4b5fd;
+		--pgw-link-hover: #fda4af;
+
+		--pgw-accent-bg: #27272a;
+		--pgw-accent-text: #fafafa;
+		--pgw-title-bg: #2d2d36;
+		--pgw-title-hover-bg: #3d3d48;
+
+		--pgw-code-bg: #18181b;
+		--pgw-code-border: #27272a;
+
+		--pgw-table-stripe: #222226;
+		--pgw-table-hover: #2d2d36;
+
+		--pgw-add-fg: #86efac;
+		--pgw-add-bg: #14532d;
+		--pgw-rem-fg: #fca5a5;
+		--pgw-rem-bg: #7f1d1d;
+		--pgw-info-fg: #a1a1aa;
+
+		--pgw-blob-selection-bg: rgba(193, 173, 0, 0.4);
+
+		--d2h-info-bg-color: #222;
+		--d2h-file-header-bg-color: rgb(38, 38, 98);
+		--d2h-empty-placeholder-bg-color: #222;
+		--d2h-del-bg-color: rgb(98, 38, 38);
+		--d2h-ins-bg-color: rgb(38, 98, 38);
+		--d2h-dim-color: #b0b0b0;
+		--d2h-bg-color: #222;
+	}
+}
 
 html {
 	box-sizing: border-box;
@@ -24,6 +118,53 @@ body:has(.page) {
 	padding: 0;
 }
 
+/* Card: override Tabler so cards use color-scheme variables */
+.card {
+	background-color: var(--pgw-bg-raised);
+	border-color: var(--pgw-border);
+	color: var(--pgw-text);
+}
+.card-header {
+	background-color: var(--pgw-accent-bg);
+	border-bottom-color: var(--pgw-border);
+	color: var(--pgw-accent-text);
+}
+.card-title {
+	color: var(--pgw-accent-text);
+}
+.card-body {
+	background-color: var(--pgw-bg-raised);
+	color: var(--pgw-text);
+}
+
+/* Table: override Tabler .table / .card-table so tables use color-scheme variables */
+.table,
+.table.card-table {
+	background-color: var(--pgw-bg-raised);
+	border-color: var(--pgw-border);
+	color: var(--pgw-text);
+}
+.table thead th,
+.table th {
+	background-color: var(--pgw-accent-bg);
+	border-bottom-color: var(--pgw-border-muted);
+	color: var(--pgw-accent-text);
+}
+.table td {
+	border-top-color: var(--pgw-border-muted);
+	border-bottom-color: var(--pgw-border-muted);
+	color: var(--pgw-text);
+}
+.table tbody tr:nth-child(even) {
+	background-color: var(--pgw-table-stripe);
+}
+.table tbody tr:hover {
+	background-color: var(--pgw-table-hover);
+}
+.table tbody tr:hover td {
+	background-color: var(--pgw-table-hover);
+}
+
 /* Fluid vertical layout: fixed sidebar + main content offset */
 .page .navbar-vertical.position-fixed {
 	top: 0;
@@ -168,10 +309,77 @@ tbody tr:hover {
 /* Line selection from URL param lines= (client-side) */
 .blob-view .blob-line-selected,
 .blob-view .blob-selection {
-	background-color: var(--pgw-blob-selection-bg, rgba(193, 173, 0, 0.35));
+	background-color: var(--pgw-blob-selection-bg);
 }
 
-/* README markdown: linkable section headings + table of contents */
+/* README markdown: base and all elements use color-scheme variables */
+.markdown-body {
+	color: var(--pgw-text);
+	background-color: transparent;
+}
+.markdown-body h1,
+.markdown-body h2,
+.markdown-body h3,
+.markdown-body h4,
+.markdown-body h5,
+.markdown-body h6 {
+	color: var(--pgw-accent-text);
+	border-bottom-color: var(--pgw-border-muted);
+}
+.markdown-body a {
+	color: var(--pgw-link);
+}
+.markdown-body a:hover {
+	color: var(--pgw-link-hover);
+}
+.markdown-body a:visited {
+	color: var(--pgw-link-visited);
+}
+.markdown-body code {
+	background-color: var(--pgw-code-bg);
+	color: var(--pgw-text);
+	border: 1px solid var(--pgw-code-border);
+}
+.markdown-body pre {
+	background-color: var(--pgw-code-bg);
+	border: 1px solid var(--pgw-code-border);
+	color: var(--pgw-text);
+}
+.markdown-body pre code {
+	background: none;
+	border: none;
+}
+.markdown-body blockquote {
+	border-left: 4px solid var(--pgw-border);
+	color: var(--pgw-text-muted);
+	padding-left: 1em;
+	margin-left: 0;
+}
+.markdown-body hr {
+	border-color: var(--pgw-border-muted);
+	background-color: var(--pgw-border-muted);
+}
+.markdown-body table {
+	background-color: var(--pgw-bg-raised);
+	border-color: var(--pgw-border);
+}
+.markdown-body th,
+.markdown-body td {
+	border-color: var(--pgw-border-muted);
+}
+.markdown-body th {
+	background-color: var(--pgw-accent-bg);
+	color: var(--pgw-accent-text);
+}
+.markdown-body tbody tr:nth-child(even) {
+	background-color: var(--pgw-table-stripe);
+}
+.markdown-body tbody tr:hover {
+	background-color: var(--pgw-table-hover);
+}
+.markdown-body img {
+	background-color: var(--pgw-bg-muted);
+}
 .markdown-body .readme-heading-wrap {
 	position: relative;
 	display: block;
@@ -231,12 +439,12 @@ tbody tr:hover {
 }
 .readme-toc-link:hover {
 	color: var(--pgw-link-hover);
-	background-color: var(--pgw-bg-raised, rgba(128, 128, 128, 0.1));
+	background-color: var(--pgw-bg-raised);
 }
 .readme-toc-item.readme-toc-active .readme-toc-link {
 	color: var(--pgw-link);
 	font-weight: 600;
-	background-color: var(--pgw-accent-bg, rgba(128, 128, 128, 0.15));
+	background-color: var(--pgw-accent-bg);
 }
 .readme-toc-level-1 { padding-left: 0; }
 .readme-toc-level-2 { padding-left: 0.5rem; }
diff --git a/pygitweb/static/themes/dark.css b/pygitweb/static/themes/dark.css
deleted file mode 100644
index db0c092..0000000
--- a/pygitweb/static/themes/dark.css
+++ /dev/null
@@ -1,47 +0,0 @@
-.theme-dark {
-	/* Surfaces */
-	--pgw-bg: #1a1a1e;
-	--pgw-bg-raised: #25252c;
-	--pgw-bg-muted: #2d2d36;
-	--pgw-border: #3d3d48;
-	--pgw-border-muted: #35353d;
-
-	/* Text */
-	--pgw-text: #e4e4e7;
-	--pgw-text-muted: #a1a1aa;
-	--pgw-text-dim: #71717a;
-
-	/* Links */
-	--pgw-link: #7dd3fc;
-	--pgw-link-visited: #c4b5fd;
-	--pgw-link-hover: #fda4af;
-
-	/* Accents (headers, table headers, emphasis) */
-	--pgw-accent-bg: #27272a;
-	--pgw-accent-text: #fafafa;
-	--pgw-title-bg: #2d2d36;
-	--pgw-title-hover-bg: #3d3d48;
-
-	/* Code / monospace */
-	--pgw-code-bg: #18181b;
-	--pgw-code-border: #27272a;
-
-	/* Tables */
-	--pgw-table-stripe: #222226;
-	--pgw-table-hover: #2d2d36;
-
-	/* Diff / status (optional) */
-	--pgw-add-fg: #86efac;
-	--pgw-add-bg: #14532d;
-	--pgw-rem-fg: #fca5a5;
-	--pgw-rem-bg: #7f1d1d;
-	--pgw-info-fg: #a1a1aa;
-
-	--d2h-info-bg-color: #222;
-	--d2h-file-header-bg-color: rgb(38, 38, 98);
-	--d2h-empty-placeholder-bg-color: #222;
-	--d2h-del-bg-color: rgb(98, 38, 38);
-	--d2h-ins-bg-color: rgb(38, 98, 38);
-	--d2h-dim-color: #b0b0b0;
-	--d2h-bg-color: #222;
-}
diff --git a/pygitweb/static/themes/light.css b/pygitweb/static/themes/light.css
deleted file mode 100644
index 01074df..0000000
--- a/pygitweb/static/themes/light.css
+++ /dev/null
@@ -1,32 +0,0 @@
-.theme-light {
-	--pgw-bg: #fafafa;
-	--pgw-bg-raised: #ffffff;
-	--pgw-bg-muted: #f4f4f5;
-	--pgw-border: #e4e4e7;
-	--pgw-border-muted: #d4d4d8;
-
-	--pgw-text: #18181b;
-	--pgw-text-muted: #52525b;
-	--pgw-text-dim: #71717a;
-
-	--pgw-link: #2563eb;
-	--pgw-link-visited: #7c3aed;
-	--pgw-link-hover: #dc2626;
-
-	--pgw-accent-bg: #e4e4e7;
-	--pgw-accent-text: #18181b;
-	--pgw-title-bg: #f4f4f5;
-	--pgw-title-hover-bg: #e4e4e7;
-
-	--pgw-code-bg: #f4f4f5;
-	--pgw-code-border: #e4e4e7;
-
-	--pgw-table-stripe: #fafafa;
-	--pgw-table-hover: #f4f4f5;
-
-	--pgw-add-fg: #166534;
-	--pgw-add-bg: #dcfce7;
-	--pgw-rem-fg: #991b1b;
-	--pgw-rem-bg: #fee2e2;
-	--pgw-info-fg: #52525b;
-}
diff --git a/pygitweb/static/themes/solarized.css b/pygitweb/static/themes/solarized.css
index 4186c84..24a8c1e 100644
--- a/pygitweb/static/themes/solarized.css
+++ b/pygitweb/static/themes/solarized.css
@@ -1,4 +1,4 @@
-.theme-solarized {
+:root {
 	--pgw-bg: #002b36;
 	--pgw-bg-raised: #073642;
 	--pgw-bg-muted: #0d3d4a;
@@ -29,4 +29,6 @@
 	--pgw-rem-fg: #dc322f;
 	--pgw-rem-bg: #4a0a0a;
 	--pgw-info-fg: #657b83;
+
+	color-scheme: dark;
 }
diff --git a/pygitweb/templates/preamble.html b/pygitweb/templates/preamble.html
index 6c9a536..ef7bc7f 100644
--- a/pygitweb/templates/preamble.html
+++ b/pygitweb/templates/preamble.html
@@ -1,4 +1,5 @@
-<!DOCTYPE html><html class="theme-{{theme}}"><head><title>{{title | default('DistGit')}}</title>
+<!DOCTYPE html><html><head><title>{{title | default('DistGit')}}</title>
+<script>document.documentElement.setAttribute('data-bs-theme', window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'); window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', function(e){ document.documentElement.setAttribute('data-bs-theme', e.matches ? 'dark' : 'light'); });</script>
 <link rel="icon" href="static/logo.svg" type="image/svg+xml">
 <script id="Highlight-script" async src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js"></script>
 <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/github-dark.min.css">
@@ -14,7 +15,7 @@
 <link rel="stylesheet" href="static/main.css"/>
 </head><body>
 <div class="page">
-  <aside class="navbar navbar-vertical navbar-expand-sm position-fixed" data-bs-theme="{{ theme }}">
+  <aside class="navbar navbar-vertical navbar-expand-sm position-fixed">
     <div class="container-fluid d-flex flex-column vh-100 overflow-y-auto">
       <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#sidebar-menu" aria-controls="sidebar-menu" aria-expanded="false" aria-label="Toggle navigation">
         <span class="navbar-toggler-icon"></span>
