From bdade096264e70db6187f29a21d4c1948633a187 Mon Sep 17 00:00:00 2001 From: pablofdezr Date: Fri, 24 Jul 2026 14:07:18 +0200 Subject: [PATCH 1/2] fix(core): expose multi-theme keys on for themed backgrounds With multiple themes, Shiki emits the theme keys (`--shiki-light-bg`, `--shiki-dark-bg`, ...) as inline styles on the `
` element only.
rehype-pretty-code's documented CSS targets ``:

    code[data-theme*=' '] { background-color: var(--shiki-light-bg); }

That worked only because the custom properties inherit from `
` down
to ``, so the background variables were never actually present on
`` as the docs (and the container itself) imply. This mirrors the
`
` theme-key style onto `` when multiple themes are active and
`keepBackground` is on, letting the container's own background be styled
per theme without relying on inheritance.

Single-theme output is untouched (its `
` style paints real colors,
which we must not duplicate onto ``), and `keepBackground: false`
still strips the keys from both elements.

Closes #222

Co-Authored-By: Claude Opus 4.8 (1M context) 
---
 packages/core/src/index.ts                    | 15 +++++
 .../fixtures/codeBackgroundMultipleTheme.md   |  5 ++
 .../results/codeBackgroundMultipleTheme.html  | 67 +++++++++++++++++++
 .../showLineNumbersStartAtMultipleTheme.html  |  6 +-
 4 files changed, 90 insertions(+), 3 deletions(-)
 create mode 100644 packages/core/test/fixtures/codeBackgroundMultipleTheme.md
 create mode 100644 packages/core/test/results/codeBackgroundMultipleTheme.html

diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts
index 0aa6313f..7e334a41 100644
--- a/packages/core/src/index.ts
+++ b/packages/core/src/index.ts
@@ -109,6 +109,21 @@ function apply(
       return code;
     }
 
+    // Shiki emits the dual-theme keys (e.g. `--shiki-light-bg`) as inline
+    // styles on 
 only. When multiple themes are active, mirror them onto
+    //  as well, so the documented `code[data-theme*=' ']` selector can
+    // read the theme background variables directly instead of relying on them
+    // inheriting from 
. See rehype-pretty/rehype-pretty-code#222.
+    if (
+      keepBackground &&
+      themeNames.length > 1 &&
+      typeof pre.properties.style === 'string'
+    ) {
+      code.properties.style = code.properties.style
+        ? `${pre.properties.style};${code.properties.style}`
+        : `${pre.properties.style};`;
+    }
+
     if (grid) {
       if (code.properties.style) {
         code.properties.style += 'display: grid;';
diff --git a/packages/core/test/fixtures/codeBackgroundMultipleTheme.md b/packages/core/test/fixtures/codeBackgroundMultipleTheme.md
new file mode 100644
index 00000000..0474a49f
--- /dev/null
+++ b/packages/core/test/fixtures/codeBackgroundMultipleTheme.md
@@ -0,0 +1,5 @@
+# Code background with multiple themes
+
+```js
+const answer = 42;
+```
diff --git a/packages/core/test/results/codeBackgroundMultipleTheme.html b/packages/core/test/results/codeBackgroundMultipleTheme.html
new file mode 100644
index 00000000..bdb53401
--- /dev/null
+++ b/packages/core/test/results/codeBackgroundMultipleTheme.html
@@ -0,0 +1,67 @@
+
+
+

Code background with multiple themes

+
+
const answer = 42;
+
diff --git a/packages/core/test/results/showLineNumbersStartAtMultipleTheme.html b/packages/core/test/results/showLineNumbersStartAtMultipleTheme.html index 87ad173b..7bc03daa 100644 --- a/packages/core/test/results/showLineNumbersStartAtMultipleTheme.html +++ b/packages/core/test/results/showLineNumbersStartAtMultipleTheme.html @@ -64,7 +64,7 @@

showLineNumbersStartAt

tabindex="0" data-language="js" data-theme="github-dark github-light" - >const a = 'a'; + >const a = 'a'; const b = 'b'; const c = 'c';
@@ -79,7 +79,7 @@

showLineNumbersStartAt

tabindex="0" data-language="js" data-theme="github-dark github-light" - >const a = 'a'; + >const a = 'a'; const b = 'b'; const c = 'c';
@@ -94,7 +94,7 @@

showLineNumbersStartAt

tabindex="0" data-language="js" data-theme="github-dark github-light" - >const a = 'a'; + >const a = 'a'; const b = 'b'; const c = 'c';
From 8f5bb604029443c62a3cbaf4576043e31cfdbabd Mon Sep 17 00:00:00 2001 From: pablofdezr Date: Fri, 24 Jul 2026 22:47:45 +0200 Subject: [PATCH 2/2] fix(core): copy only the --shiki-* declarations onto Copying the whole
 style moved every declaration on it, including
styling owned by user transformers: a transformer adding `padding:123px`
to 
 also changed 's layout under a multi-theme config.

Filter the copy down to the `--shiki-*` custom properties, which is all
the themed-background selector needs. Adds a transformer regression test
asserting the padding stays on 
 while the theme variables still
reach . Existing snapshots are unchanged: Shiki writes nothing but
those custom properties on 
 in these fixtures.

Also adds the patch changeset.
---
 .changeset/eleven-beans-jam.md      |  5 +++++
 packages/core/src/index.ts          | 15 ++++++++++++---
 packages/core/src/utils.ts          | 19 +++++++++++++++++++
 packages/core/test/fixtures.test.ts | 24 ++++++++++++++++++++++++
 4 files changed, 60 insertions(+), 3 deletions(-)
 create mode 100644 .changeset/eleven-beans-jam.md

diff --git a/.changeset/eleven-beans-jam.md b/.changeset/eleven-beans-jam.md
new file mode 100644
index 00000000..900db746
--- /dev/null
+++ b/.changeset/eleven-beans-jam.md
@@ -0,0 +1,5 @@
+---
+"rehype-pretty-code": patch
+---
+
+fix: mirror the `--shiki-*` theme variables onto `` in multi-theme configurations, so the documented `code[data-theme*=' ']` selector can apply the theme background without relying on inheritance from `
` (#222)
diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts
index 7e334a41..bf03f898 100644
--- a/packages/core/src/index.ts
+++ b/packages/core/src/index.ts
@@ -21,6 +21,7 @@ import {
   getInlineCodeLang,
   isInlineCode,
   getThemeNames,
+  getShikiCustomProperties,
   replaceLineClass,
   getLineId,
 } from './utils';
@@ -114,14 +115,22 @@ function apply(
     //  as well, so the documented `code[data-theme*=' ']` selector can
     // read the theme background variables directly instead of relying on them
     // inheriting from 
. See rehype-pretty/rehype-pretty-code#222.
+    //
+    // Only those custom properties are copied: 
 also carries
+    // declarations owned by someone else, such as a transformer's own
+    // styling, which must not be duplicated onto .
     if (
       keepBackground &&
       themeNames.length > 1 &&
       typeof pre.properties.style === 'string'
     ) {
-      code.properties.style = code.properties.style
-        ? `${pre.properties.style};${code.properties.style}`
-        : `${pre.properties.style};`;
+      const themeProperties = getShikiCustomProperties(pre.properties.style);
+      if (themeProperties.length > 0) {
+        const declarations = `${themeProperties.join(';')};`;
+        code.properties.style = code.properties.style
+          ? `${declarations}${code.properties.style}`
+          : declarations;
+      }
     }
 
     if (grid) {
diff --git a/packages/core/src/utils.ts b/packages/core/src/utils.ts
index 54b3942f..91c89fc0 100644
--- a/packages/core/src/utils.ts
+++ b/packages/core/src/utils.ts
@@ -100,6 +100,25 @@ export function getThemeNames(theme: Theme | Record) {
   );
 }
 
+// Declarations for the `--shiki-*` custom properties Shiki writes for the
+// non-default themes of a multi-theme config.
+const shikiCustomPropertyRegex = /(?:^|;)\s*(--shiki-[\w-]+\s*:[^;]*)/g;
+
+/**
+ * Extracts the `--shiki-*` custom-property declarations from an inline
+ * style string, dropping everything else.
+ *
+ * `
` also carries declarations that are none of our business — most
+ * notably whatever user transformers put there — so copying its whole
+ * style elsewhere would move unrelated layout with it.
+ */
+export function getShikiCustomProperties(style: string) {
+  return Array.from(
+    style.matchAll(shikiCustomPropertyRegex),
+    ([, declaration]) => declaration.trim(),
+  );
+}
+
 export function replaceLineClass(element: Element) {
   if (
     Array.isArray(element.properties?.className) &&
diff --git a/packages/core/test/fixtures.test.ts b/packages/core/test/fixtures.test.ts
index d745ad3a..8f101481 100644
--- a/packages/core/test/fixtures.test.ts
+++ b/packages/core/test/fixtures.test.ts
@@ -174,6 +174,30 @@ describe('Multiple theme', () => {
   });
 });
 
+it('only mirrors the theme variables from 
 onto ', async () => {
+  const html = await getHtml('```js\nconst answer = 42;\n```', {
+    theme: getTheme(true),
+    transformers: [
+      {
+        name: 'test-pre-padding',
+        pre(node) {
+          node.properties.style = `${node.properties.style ?? ''};padding:123px`;
+        },
+      },
+    ],
+  });
+
+  const preAttributes = html.match(/]*)>/)?.[1] ?? '';
+  const codeAttributes = html.match(/]*)>/)?.[1] ?? '';
+
+  // The theme variables are mirrored so `code[data-theme*=' ']` can read them
+  expect(codeAttributes).toContain('--shiki-light-bg');
+  expect(codeAttributes).toContain('--shiki-dark-bg');
+  // ...but a transformer's own styling stays where the transformer put it
+  expect(preAttributes).toContain('padding:123px');
+  expect(codeAttributes).not.toContain('padding');
+});
+
 it("highlighter caches don't overwrite each other", async () => {
   const [html1, html2] = await Promise.all([
     getHtml('`[1, 2, 3]{:js}`', { theme: 'github-light' }),