diff --git a/docs/data/api.data.ts b/docs/data/api.data.ts
index 9a493914..f273ba25 100644
--- a/docs/data/api.data.ts
+++ b/docs/data/api.data.ts
@@ -36,6 +36,7 @@ function isInternalMethod(name: string): boolean {
name === "maybeGroup" ||
name === "maybeStackX" ||
name === "maybeStackY" ||
+ name === "maybeTreeAnchor" ||
name === "normalizeScale" ||
name === "outerDimensions" ||
name === "ox" ||
diff --git a/src/marks/tree.ts b/src/marks/tree.ts
index 84b7b748..2851bc5e 100644
--- a/src/marks/tree.ts
+++ b/src/marks/tree.ts
@@ -3,7 +3,6 @@ import type {CompoundMark, Data, MarkOptions} from "../mark.js";
import {marks} from "../mark.js";
import {isNoneish, keyword} from "../options.js";
import type {TreeTransformOptions} from "../transforms/tree.js";
-// @ts-expect-error — runtime export missing from transforms/tree.d.ts
import {maybeTreeAnchor, treeLink, treeNode} from "../transforms/tree.js";
import type {DotOptions} from "./dot.js";
import {dot} from "./dot.js";
diff --git a/src/react/Replot.tsx b/src/react/Replot.tsx
index 67002fa2..0aefde62 100644
--- a/src/react/Replot.tsx
+++ b/src/react/Replot.tsx
@@ -27,6 +27,7 @@ import {buildAutoLegends, LegendDisplay, resolveLegendElement} from "./legends/L
import {createClipRegistry, registerClips, type ClipRegistry} from "./clip.js";
import {domToJsx, isDomNode, parseStyleString} from "./domToJsx.js";
import {datumIndexOf} from "./perDatum.js";
+import {plotStyleSheetElement} from "./plotStyles.js";
import {hasRenderTransform, renderTransformJSX} from "./renderTransform.js";
import {FigureLayout} from "./FigureLayout.js";
import {warningIndicatorElement} from "./warningIndicator.js";
@@ -935,26 +936,6 @@ function WarningIndicator({computed, serverRender}: {computed: any; serverRender
// other. The shell is what the two renderers have in common; what differs is
// how the marks inside it are rendered, which is what each path still owns.
//
-// The stylesheet, which is the plot's only forced CSS: it makes the svg scale
-// with its container and stops text being collapsed, all through :where() so
-// the selectors carry no specificity a user's own rules have to fight. The
-// class name is the plot's (meant to be unique per plot); upstream writes the
-// same two rules in style.js, with a comment that changing them means changing
-// defaultClassName there too.
-export function plotStyleSheet(className: string): string {
- return `:where(.${className}) {
- --plot-background: white;
- display: block;
- height: auto;
- height: intrinsic;
- max-width: 100%;
-}
-:where(.${className} text),
-:where(.${className} tspan) {
- white-space: pre;
-}`;
-}
-
// The shell's attributes, in the order they are written to the element — the
// order React writes them in for the JSX path, and the order the markup
// serializes in on both. `style` is deliberately NOT here: the JSX path passes
@@ -1013,7 +994,7 @@ function PlotSvg({
registerClips(computed, clipReg);
const inner = (
<>
-
+ {plotStyleSheetElement(computed.className)}
{clipReg.defs}
{renderMarks(computed, clipReg, getHandlers)}
diff --git a/src/react/plotStyles.ts b/src/react/plotStyles.ts
new file mode 100644
index 00000000..43bd347c
--- /dev/null
+++ b/src/react/plotStyles.ts
@@ -0,0 +1,35 @@
+// The plot's own stylesheet. The CSS lives here rather than inline in a .tsx
+// component: a