diff --git a/studio/frontend/package-lock.json b/studio/frontend/package-lock.json
index 1d5c09ba72..b1b861840c 100644
--- a/studio/frontend/package-lock.json
+++ b/studio/frontend/package-lock.json
@@ -85,6 +85,7 @@
"eslint-plugin-react-hooks": "^7.0.1",
"eslint-plugin-react-refresh": "^0.5.2",
"globals": "^17.4.0",
+ "postcss": "^8.5.15",
"typescript": "~5.9.3",
"typescript-eslint": "^8.55.0",
"vite": "^8.0.16"
diff --git a/studio/frontend/package.json b/studio/frontend/package.json
index fc6911c4be..9846384133 100644
--- a/studio/frontend/package.json
+++ b/studio/frontend/package.json
@@ -104,6 +104,7 @@
"eslint-plugin-react-hooks": "^7.0.1",
"eslint-plugin-react-refresh": "^0.5.2",
"globals": "^17.4.0",
+ "postcss": "^8.5.15",
"typescript": "~5.9.3",
"typescript-eslint": "^8.55.0",
"vite": "^8.0.16"
diff --git a/studio/frontend/src/components/shutdown-dialog.tsx b/studio/frontend/src/components/shutdown-dialog.tsx
index e3e6ca9920..8f746c9414 100644
--- a/studio/frontend/src/components/shutdown-dialog.tsx
+++ b/studio/frontend/src/components/shutdown-dialog.tsx
@@ -52,8 +52,8 @@ export function ShutdownDialog({
onAfterShutdown?.();
document.body.innerHTML = `
-
Unsloth Studio has stopped.
-
You can now close this tab.
+
Unsloth Studio has stopped.
+
You can now close this tab.
`;
};
diff --git a/studio/frontend/src/features/settings/stores/appearance-custom-store.ts b/studio/frontend/src/features/settings/stores/appearance-custom-store.ts
index f3618ddca5..9345a91f98 100644
--- a/studio/frontend/src/features/settings/stores/appearance-custom-store.ts
+++ b/studio/frontend/src/features/settings/stores/appearance-custom-store.ts
@@ -510,11 +510,14 @@ export function applyCustomizationToDocument(
setVar("--custom-chat-font", null);
}
+ // Scale typography without changing the root rem or layout dimensions.
if (c.uiFontSize !== null && c.uiFontSize !== UI_FONT_SIZE_RANGE.default) {
- style.fontSize = `${c.uiFontSize}px`;
+ setVar("--ui-font-scale", String(c.uiFontSize / UI_FONT_SIZE_RANGE.default));
} else {
- style.removeProperty("font-size");
+ setVar("--ui-font-scale", null);
}
+ // Clear the root size used by older builds.
+ style.removeProperty("font-size");
if (c.codeFontSize !== null) {
el.setAttribute("data-code-font-size", "");
diff --git a/studio/frontend/src/features/studio/sections/charts/eval-loss-chart-card.tsx b/studio/frontend/src/features/studio/sections/charts/eval-loss-chart-card.tsx
index b5eeac34f3..20b2eeeeab 100644
--- a/studio/frontend/src/features/studio/sections/charts/eval-loss-chart-card.tsx
+++ b/studio/frontend/src/features/studio/sections/charts/eval-loss-chart-card.tsx
@@ -17,6 +17,7 @@ import type { ReactElement } from "react";
import { CartesianGrid, Line, LineChart, XAxis, YAxis } from "recharts";
import {
CHART_CONTAINER_CLASS,
+ CHART_FONT_SIZE,
DEFAULT_CHART_MARGIN,
DEFAULT_Y_AXIS_WIDTH,
formatAxisMetric,
@@ -70,7 +71,7 @@ export function EvalLossChartCard({
tickLine={false}
axisLine={false}
tickMargin={8}
- fontSize={10}
+ fontSize={CHART_FONT_SIZE}
tickFormatter={(value) => formatStepTick(Number(value))}
interval="preserveStartEnd"
/>
@@ -81,7 +82,7 @@ export function EvalLossChartCard({
axisLine={false}
tickMargin={8}
tickCount={5}
- fontSize={10}
+ fontSize={CHART_FONT_SIZE}
width={DEFAULT_Y_AXIS_WIDTH}
tickFormatter={(value) => formatAxisMetric(Number(value))}
/>
@@ -132,7 +133,7 @@ export function EvalLossChartCard({
tickLine={false}
axisLine={false}
tickMargin={8}
- fontSize={10}
+ fontSize={CHART_FONT_SIZE}
interval="preserveStartEnd"
/>
formatStepTick(Number(value))}
interval="preserveStartEnd"
/>
@@ -89,7 +90,7 @@ export function GradNormChartCard({
axisLine={false}
tickMargin={8}
tickCount={5}
- fontSize={10}
+ fontSize={CHART_FONT_SIZE}
width={DEFAULT_Y_AXIS_WIDTH}
tickFormatter={(value) => {
const num = Number(value);
diff --git a/studio/frontend/src/features/studio/sections/charts/learning-rate-chart-card.tsx b/studio/frontend/src/features/studio/sections/charts/learning-rate-chart-card.tsx
index 1a7495b493..badc0f22dc 100644
--- a/studio/frontend/src/features/studio/sections/charts/learning-rate-chart-card.tsx
+++ b/studio/frontend/src/features/studio/sections/charts/learning-rate-chart-card.tsx
@@ -16,6 +16,7 @@ import { CartesianGrid, Line, LineChart, XAxis, YAxis } from "recharts";
import type { ScaleMode } from "./types";
import {
CHART_CONTAINER_CLASS,
+ CHART_FONT_SIZE,
CHART_SYNC_ID,
DEFAULT_CHART_MARGIN,
DEFAULT_Y_AXIS_WIDTH,
@@ -76,7 +77,7 @@ export function LearningRateChartCard({
tickLine={false}
axisLine={false}
tickMargin={8}
- fontSize={10}
+ fontSize={CHART_FONT_SIZE}
tickFormatter={(value) => formatStepTick(Number(value))}
interval="preserveStartEnd"
/>
@@ -87,7 +88,7 @@ export function LearningRateChartCard({
axisLine={false}
tickMargin={8}
tickCount={5}
- fontSize={10}
+ fontSize={CHART_FONT_SIZE}
width={DEFAULT_Y_AXIS_WIDTH}
tickFormatter={(value) => {
const num = Number(value);
diff --git a/studio/frontend/src/features/studio/sections/charts/training-loss-chart-card.tsx b/studio/frontend/src/features/studio/sections/charts/training-loss-chart-card.tsx
index 16113f3219..5acd90deba 100644
--- a/studio/frontend/src/features/studio/sections/charts/training-loss-chart-card.tsx
+++ b/studio/frontend/src/features/studio/sections/charts/training-loss-chart-card.tsx
@@ -22,8 +22,9 @@ import {
} from "recharts";
import type { ScaleMode } from "./types";
import {
- CHART_SYNC_ID,
CHART_CONTAINER_CLASS,
+ CHART_FONT_SIZE,
+ CHART_SYNC_ID,
DEFAULT_CHART_MARGIN,
DEFAULT_Y_AXIS_WIDTH,
formatAxisMetric,
@@ -96,7 +97,7 @@ export function TrainingLossChartCard({
tickLine={false}
axisLine={false}
tickMargin={8}
- fontSize={10}
+ fontSize={CHART_FONT_SIZE}
tickFormatter={(value) => formatStepTick(Number(value))}
interval="preserveStartEnd"
/>
@@ -107,7 +108,7 @@ export function TrainingLossChartCard({
axisLine={false}
tickMargin={8}
tickCount={5}
- fontSize={10}
+ fontSize={CHART_FONT_SIZE}
width={DEFAULT_Y_AXIS_WIDTH}
tickFormatter={(value) => {
const num = Number(value);
@@ -152,7 +153,7 @@ export function TrainingLossChartCard({
value: formatMetric(avgRaw),
}),
position: "insideTopRight",
- fontSize: 10,
+ fontSize: CHART_FONT_SIZE,
fill: "#3b82f6",
}}
/>
diff --git a/studio/frontend/src/features/studio/sections/charts/utils.ts b/studio/frontend/src/features/studio/sections/charts/utils.ts
index 4a4a1f3b48..22f1169b41 100644
--- a/studio/frontend/src/features/studio/sections/charts/utils.ts
+++ b/studio/frontend/src/features/studio/sections/charts/utils.ts
@@ -9,6 +9,7 @@ export const DEFAULT_VISIBLE_POINTS = 160;
export const CHART_CONTAINER_CLASS = "h-[220px] w-full";
export const DEFAULT_CHART_MARGIN = { top: 4, right: 8, bottom: 0, left: 4 };
export const DEFAULT_Y_AXIS_WIDTH = 45;
+export const CHART_FONT_SIZE = "calc(10px * var(--ui-font-scale, 1))";
const TRAILING_ZEROES_RE = /\.?0+$/;
const NEGATIVE_ZERO_RE = /^-0$/;
diff --git a/studio/frontend/src/features/studio/sections/training-section.tsx b/studio/frontend/src/features/studio/sections/training-section.tsx
index 5650b9c145..d33e6bf218 100644
--- a/studio/frontend/src/features/studio/sections/training-section.tsx
+++ b/studio/frontend/src/features/studio/sections/training-section.tsx
@@ -140,13 +140,13 @@ export function TrainingSection() {
tickLine={false}
axisLine={false}
tickMargin={8}
- fontSize={10}
+ fontSize="calc(10px * var(--ui-font-scale, 1))"
/>
{
+ if (!shouldScaleFontSize(declaration.value)) {
+ return;
+ }
+ declaration.value = `calc(${declaration.value} * var(${UI_FONT_SCALE_VAR}, 1))`;
+ },
+ },
+};
+
+// Scale named, arbitrary, and vendor font sizes in Tailwind's generated CSS.
+function uiFontScalingPlugin(): VitePlugin {
+ return {
+ name: "unsloth-ui-font-scaling",
+ // Run after Tailwind and before Vite converts CSS into a module.
+ enforce: "pre",
+ async transform(code, id) {
+ if (!CSS_MODULE_ID_PATTERN.test(id)) {
+ return null;
+ }
+ const result = await postcss([scaleAbsoluteFontSizes]).process(code, {
+ from: id,
+ map: false,
+ });
+ if (result.css === code) {
+ return null;
+ }
+ return { code: result.css, map: null };
+ },
+ };
+}
// https://vite.dev/config/
export default defineConfig({
- plugins: [react(), tailwindcss()],
+ plugins: [react(), tailwindcss(), uiFontScalingPlugin()],
optimizeDeps: {
include: ["@dagrejs/dagre", "@dagrejs/graphlib"],
},