diff --git a/ui/src/styles/base.css b/ui/src/styles/base.css
index f77cff9ed..2c87cbb92 100644
--- a/ui/src/styles/base.css
+++ b/ui/src/styles/base.css
@@ -84,6 +84,7 @@
--mono: "JetBrains Mono", ui-monospace, SFMono-Regular, "SF Mono", Menlo, Monaco, Consolas, monospace;
--font-body: "Space Grotesk", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
--font-display: "Space Grotesk", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
+ --font-size-chat: 14px; /* Adjustable via UI settings */
/* Shadows - Richer with subtle color */
--shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.2);
diff --git a/ui/src/styles/chat/layout.css b/ui/src/styles/chat/layout.css
index 589b0b62d..d127e1983 100644
--- a/ui/src/styles/chat/layout.css
+++ b/ui/src/styles/chat/layout.css
@@ -325,6 +325,53 @@
color: rgba(16, 24, 40, 0.3);
}
+/* Font size control */
+.chat-controls__font-size {
+ display: flex;
+ align-items: center;
+ gap: 6px;
+}
+
+.chat-controls__font-icon {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ color: var(--muted);
+}
+
+.chat-controls__font-icon svg {
+ width: 16px;
+ height: 16px;
+ stroke: currentColor;
+ fill: none;
+ stroke-width: 2px;
+}
+
+.chat-controls__font-select {
+ padding: 4px 8px;
+ font-size: 12px;
+ border-radius: 6px;
+ border: 1px solid var(--border);
+ background: rgba(255, 255, 255, 0.06);
+ color: var(--text);
+ cursor: pointer;
+ min-width: 80px;
+}
+
+.chat-controls__font-select:hover {
+ border-color: var(--border-strong);
+}
+
+.chat-controls__font-select:focus {
+ outline: none;
+ border-color: var(--accent);
+}
+
+:root[data-theme="light"] .chat-controls__font-select {
+ background: #ffffff;
+ border-color: var(--border);
+}
+
.btn--icon:hover {
background: rgba(255, 255, 255, 0.12);
border-color: rgba(255, 255, 255, 0.2);
diff --git a/ui/src/styles/chat/text.css b/ui/src/styles/chat/text.css
index 13e245de2..ddeb089a3 100644
--- a/ui/src/styles/chat/text.css
+++ b/ui/src/styles/chat/text.css
@@ -19,7 +19,7 @@
}
.chat-text {
- font-size: 14px;
+ font-size: var(--font-size-chat, 14px);
line-height: 1.5;
word-wrap: break-word;
overflow-wrap: break-word;
diff --git a/ui/src/ui/app-render.helpers.ts b/ui/src/ui/app-render.helpers.ts
index 22f8d90db..fda74c50a 100644
--- a/ui/src/ui/app-render.helpers.ts
+++ b/ui/src/ui/app-render.helpers.ts
@@ -9,6 +9,7 @@ import { syncUrlWithSessionKey } from "./app-settings";
import type { SessionsListResult } from "./types";
import type { ThemeMode } from "./theme";
import type { ThemeTransitionContext } from "./theme-transition";
+import type { ChatFontSize } from "./storage";
export function renderTab(state: AppViewState, tab: Tab) {
const href = pathForTab(tab, state.basePath);
@@ -128,6 +129,44 @@ export function renderChatControls(state: AppViewState) {
>
${focusIcon}
+ |
+ ${renderFontSizeControl(state)}
+
+ `;
+}
+
+const FONT_SIZE_OPTIONS: Array<{ value: ChatFontSize; label: string }> = [
+ { value: "small", label: "Small" },
+ { value: "medium", label: "Medium" },
+ { value: "large", label: "Large" },
+ { value: "x-large", label: "X-Large" },
+];
+
+function renderFontSizeControl(state: AppViewState) {
+ const currentSize = state.settings.chatFontSize ?? "medium";
+ const textIcon = html``;
+ return html`
+
+ ${textIcon}
+
`;
}
diff --git a/ui/src/ui/app-settings.test.ts b/ui/src/ui/app-settings.test.ts
index aae48df6f..e4d2ac507 100644
--- a/ui/src/ui/app-settings.test.ts
+++ b/ui/src/ui/app-settings.test.ts
@@ -17,6 +17,7 @@ const createHost = (tab: Tab): SettingsHost => ({
theme: "system",
chatFocusMode: false,
chatShowThinking: true,
+ chatFontSize: "medium",
splitRatio: 0.6,
navCollapsed: false,
navGroupsCollapsed: {},
diff --git a/ui/src/ui/app-settings.ts b/ui/src/ui/app-settings.ts
index e269742b2..ee25cbfb3 100644
--- a/ui/src/ui/app-settings.ts
+++ b/ui/src/ui/app-settings.ts
@@ -10,7 +10,7 @@ import { loadPresence } from "./controllers/presence";
import { loadSessions } from "./controllers/sessions";
import { loadSkills } from "./controllers/skills";
import { inferBasePathFromPathname, normalizeBasePath, normalizePath, pathForTab, tabFromPath, type Tab } from "./navigation";
-import { saveSettings, type UiSettings } from "./storage";
+import { saveSettings, CHAT_FONT_SIZE_PX, type UiSettings, type ChatFontSize } from "./storage";
import { resolveTheme, type ResolvedTheme, type ThemeMode } from "./theme";
import { startThemeTransition, type ThemeTransitionContext } from "./theme-transition";
import { scheduleChatScroll, scheduleLogsScroll } from "./app-scroll";
@@ -46,6 +46,7 @@ export function applySettings(host: SettingsHost, next: UiSettings) {
host.theme = next.theme;
applyResolvedTheme(host, resolveTheme(next.theme));
}
+ applyChatFontSize(normalized.chatFontSize);
host.applySessionKey = host.settings.lastActiveSessionKey;
}
@@ -191,6 +192,7 @@ export function inferBasePath() {
export function syncThemeWithSettings(host: SettingsHost) {
host.theme = host.settings.theme ?? "system";
applyResolvedTheme(host, resolveTheme(host.theme));
+ applyChatFontSize(host.settings.chatFontSize ?? "medium");
}
export function applyResolvedTheme(host: SettingsHost, resolved: ResolvedTheme) {
@@ -201,6 +203,12 @@ export function applyResolvedTheme(host: SettingsHost, resolved: ResolvedTheme)
root.style.colorScheme = resolved;
}
+export function applyChatFontSize(size: ChatFontSize) {
+ if (typeof document === "undefined") return;
+ const px = CHAT_FONT_SIZE_PX[size] ?? CHAT_FONT_SIZE_PX.medium;
+ document.documentElement.style.setProperty("--font-size-chat", `${px}px`);
+}
+
export function attachThemeListener(host: SettingsHost) {
if (typeof window === "undefined" || typeof window.matchMedia !== "function") return;
host.themeMedia = window.matchMedia("(prefers-color-scheme: dark)");
diff --git a/ui/src/ui/storage.ts b/ui/src/ui/storage.ts
index 4b1836bfb..4b7fc9ab9 100644
--- a/ui/src/ui/storage.ts
+++ b/ui/src/ui/storage.ts
@@ -2,6 +2,15 @@ const KEY = "moltbot.control.settings.v1";
import type { ThemeMode } from "./theme";
+export type ChatFontSize = "small" | "medium" | "large" | "x-large";
+
+export const CHAT_FONT_SIZE_PX: Record = {
+ small: 13,
+ medium: 14,
+ large: 16,
+ "x-large": 18,
+};
+
export type UiSettings = {
gatewayUrl: string;
token: string;
@@ -10,6 +19,7 @@ export type UiSettings = {
theme: ThemeMode;
chatFocusMode: boolean;
chatShowThinking: boolean;
+ chatFontSize: ChatFontSize; // Chat text font size
splitRatio: number; // Sidebar split ratio (0.4 to 0.7, default 0.6)
navCollapsed: boolean; // Collapsible sidebar state
navGroupsCollapsed: Record; // Which nav groups are collapsed
@@ -29,6 +39,7 @@ export function loadSettings(): UiSettings {
theme: "system",
chatFocusMode: false,
chatShowThinking: true,
+ chatFontSize: "medium",
splitRatio: 0.6,
navCollapsed: false,
navGroupsCollapsed: {},
@@ -69,6 +80,13 @@ export function loadSettings(): UiSettings {
typeof parsed.chatShowThinking === "boolean"
? parsed.chatShowThinking
: defaults.chatShowThinking,
+ chatFontSize:
+ parsed.chatFontSize === "small" ||
+ parsed.chatFontSize === "medium" ||
+ parsed.chatFontSize === "large" ||
+ parsed.chatFontSize === "x-large"
+ ? parsed.chatFontSize
+ : defaults.chatFontSize,
splitRatio:
typeof parsed.splitRatio === "number" &&
parsed.splitRatio >= 0.4 &&