diff --git a/apps/web/index.html b/apps/web/index.html
index 790591cb..dd444f9a 100644
--- a/apps/web/index.html
+++ b/apps/web/index.html
@@ -8,7 +8,7 @@
OK Code
diff --git a/apps/web/src/components/MarkdownPreview.tsx b/apps/web/src/components/MarkdownPreview.tsx
index cd648258..b968e177 100644
--- a/apps/web/src/components/MarkdownPreview.tsx
+++ b/apps/web/src/components/MarkdownPreview.tsx
@@ -99,7 +99,7 @@ export const MarkdownPreview = memo(function MarkdownPreview({ contents }: Markd
"--cm-callout-bg": "var(--secondary)",
"--cm-radius": "12px",
"--cm-font":
- '"DM Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif',
+ 'var(--font-ui, "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif)',
"--cm-mono":
'"SF Mono", "SFMono-Regular", Consolas, "Liberation Mono", Menlo, monospace',
} as CSSProperties
diff --git a/apps/web/src/hooks/useTheme.ts b/apps/web/src/hooks/useTheme.ts
index 5d0d2118..79291476 100644
--- a/apps/web/src/hooks/useTheme.ts
+++ b/apps/web/src/hooks/useTheme.ts
@@ -5,25 +5,39 @@ type ColorTheme =
| "default"
| "iridescent-void"
| "solar-witch"
- | "cursor-dark"
+ | "midnight-clarity"
+ | "carbon"
+ | "vapor"
| "cathedral-circuit";
+type FontFamily = "dm-sans" | "inter" | "plus-jakarta-sans";
+
type ThemeSnapshot = {
theme: Theme;
systemDark: boolean;
colorTheme: ColorTheme;
+ fontFamily: FontFamily;
};
export const COLOR_THEMES: { id: ColorTheme; label: string }[] = [
{ id: "default", label: "Default" },
{ id: "iridescent-void", label: "Iridescent Void" },
{ id: "solar-witch", label: "Solar Witch" },
- { id: "cursor-dark", label: "Cursor Dark" },
+ { id: "midnight-clarity", label: "Midnight Clarity" },
+ { id: "carbon", label: "Carbon" },
+ { id: "vapor", label: "Vapor" },
{ id: "cathedral-circuit", label: "Cathedral Circuit" },
];
+export const FONT_FAMILIES: { id: FontFamily; label: string }[] = [
+ { id: "inter", label: "Inter" },
+ { id: "dm-sans", label: "DM Sans" },
+ { id: "plus-jakarta-sans", label: "Plus Jakarta Sans" },
+];
+
const STORAGE_KEY = "okcode:theme";
const COLOR_THEME_STORAGE_KEY = "okcode:color-theme";
+const FONT_FAMILY_STORAGE_KEY = "okcode:font-family";
const MEDIA_QUERY = "(prefers-color-scheme: dark)";
let listeners: Array<() => void> = [];
@@ -49,7 +63,9 @@ function getStoredColorTheme(): ColorTheme {
raw === "default" ||
raw === "iridescent-void" ||
raw === "solar-witch" ||
- raw === "cursor-dark" ||
+ raw === "midnight-clarity" ||
+ raw === "carbon" ||
+ raw === "vapor" ||
raw === "cathedral-circuit"
) {
return raw;
@@ -57,6 +73,26 @@ function getStoredColorTheme(): ColorTheme {
return "default";
}
+function getStoredFontFamily(): FontFamily {
+ const raw = localStorage.getItem(FONT_FAMILY_STORAGE_KEY);
+ if (raw === "dm-sans" || raw === "inter" || raw === "plus-jakarta-sans") {
+ return raw;
+ }
+ return "inter";
+}
+
+const FONT_FAMILY_MAP: Record = {
+ inter: '"Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif',
+ "dm-sans": '"DM Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif',
+ "plus-jakarta-sans":
+ '"Plus Jakarta Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif',
+};
+
+function applyFont(fontFamily?: FontFamily) {
+ const font = fontFamily ?? getStoredFontFamily();
+ document.documentElement.style.setProperty("--font-ui", FONT_FAMILY_MAP[font]);
+}
+
function applyTheme(theme: Theme, suppressTransitions = false) {
if (suppressTransitions) {
document.documentElement.classList.add("no-transitions");
@@ -78,6 +114,9 @@ function applyTheme(theme: Theme, suppressTransitions = false) {
document.documentElement.classList.add(`theme-${colorTheme}`);
}
+ // Apply font family
+ applyFont();
+
syncDesktopTheme(theme);
if (suppressTransitions) {
// Force a reflow so the no-transitions class takes effect before removal
@@ -110,17 +149,19 @@ function getSnapshot(): ThemeSnapshot {
const theme = getStored();
const systemDark = theme === "system" ? getSystemDark() : false;
const colorTheme = getStoredColorTheme();
+ const fontFamily = getStoredFontFamily();
if (
lastSnapshot &&
lastSnapshot.theme === theme &&
lastSnapshot.systemDark === systemDark &&
- lastSnapshot.colorTheme === colorTheme
+ lastSnapshot.colorTheme === colorTheme &&
+ lastSnapshot.fontFamily === fontFamily
) {
return lastSnapshot;
}
- lastSnapshot = { theme, systemDark, colorTheme };
+ lastSnapshot = { theme, systemDark, colorTheme, fontFamily };
return lastSnapshot;
}
@@ -137,7 +178,11 @@ function subscribe(listener: () => void): () => void {
// Listen for storage changes from other tabs
const handleStorage = (e: StorageEvent) => {
- if (e.key === STORAGE_KEY || e.key === COLOR_THEME_STORAGE_KEY) {
+ if (
+ e.key === STORAGE_KEY ||
+ e.key === COLOR_THEME_STORAGE_KEY ||
+ e.key === FONT_FAMILY_STORAGE_KEY
+ ) {
applyTheme(getStored(), true);
emitChange();
}
@@ -155,6 +200,7 @@ export function useTheme() {
const snapshot = useSyncExternalStore(subscribe, getSnapshot);
const theme = snapshot.theme;
const colorTheme = snapshot.colorTheme;
+ const fontFamily = snapshot.fontFamily;
const resolvedTheme: "light" | "dark" =
theme === "system" ? (snapshot.systemDark ? "dark" : "light") : theme;
@@ -171,10 +217,24 @@ export function useTheme() {
emitChange();
}, []);
+ const setFontFamily = useCallback((next: FontFamily) => {
+ localStorage.setItem(FONT_FAMILY_STORAGE_KEY, next);
+ applyFont(next);
+ emitChange();
+ }, []);
+
// Keep DOM in sync on mount/change
useEffect(() => {
applyTheme(theme);
}, [theme]);
- return { theme, setTheme, resolvedTheme, colorTheme, setColorTheme } as const;
+ return {
+ theme,
+ setTheme,
+ resolvedTheme,
+ colorTheme,
+ setColorTheme,
+ fontFamily,
+ setFontFamily,
+ } as const;
}
diff --git a/apps/web/src/index.css b/apps/web/src/index.css
index da1a706c..c94a29cd 100644
--- a/apps/web/src/index.css
+++ b/apps/web/src/index.css
@@ -122,13 +122,15 @@
}
body {
- font-family:
- "DM Sans",
+ font-family: var(
+ --font-ui,
+ "Inter",
-apple-system,
BlinkMacSystemFont,
"Segoe UI",
system-ui,
- sans-serif;
+ sans-serif
+ );
margin: 0;
padding: 0;
min-height: 100vh;
diff --git a/apps/web/src/routes/_chat.settings.tsx b/apps/web/src/routes/_chat.settings.tsx
index 6b4d83db..657b556b 100644
--- a/apps/web/src/routes/_chat.settings.tsx
+++ b/apps/web/src/routes/_chat.settings.tsx
@@ -34,7 +34,7 @@ import { SidebarInset } from "../components/ui/sidebar";
import { Tooltip, TooltipPopup, TooltipTrigger } from "../components/ui/tooltip";
import { resolveAndPersistPreferredEditor } from "../editorPreferences";
import { isElectron } from "../env";
-import { useTheme, COLOR_THEMES } from "../hooks/useTheme";
+import { useTheme, COLOR_THEMES, FONT_FAMILIES } from "../hooks/useTheme";
import {
environmentVariablesQueryKeys,
globalEnvironmentVariablesQueryOptions,
@@ -209,7 +209,7 @@ function getErrorMessage(error: unknown): string {
}
function SettingsRouteView() {
- const { theme, setTheme, colorTheme, setColorTheme } = useTheme();
+ const { theme, setTheme, colorTheme, setColorTheme, fontFamily, setFontFamily } = useTheme();
const { settings, defaults, updateSettings, resetSettings } = useAppSettings();
const serverConfigQuery = useQuery(serverConfigQueryOptions());
const queryClient = useQueryClient();
@@ -300,6 +300,7 @@ function SettingsRouteView() {
const changedSettingLabels = [
...(theme !== "system" ? ["Theme"] : []),
...(colorTheme !== "default" ? ["Color theme"] : []),
+ ...(fontFamily !== "inter" ? ["Font"] : []),
...(settings.timestampFormat !== defaults.timestampFormat ? ["Time format"] : []),
...(settings.diffWordWrap !== defaults.diffWordWrap ? ["Diff line wrapping"] : []),
...(settings.enableAssistantStreaming !== defaults.enableAssistantStreaming
@@ -445,6 +446,7 @@ function SettingsRouteView() {
setTheme("system");
setColorTheme("default");
+ setFontFamily("inter");
resetSettings();
setOpenInstallProviders({
codex: false,
@@ -571,6 +573,39 @@ function SettingsRouteView() {
}
/>
+ setFontFamily("inter")} />
+ ) : null
+ }
+ control={
+
+ }
+ />
+