diff --git a/apps/frontend/src/renderer/components/KanbanBoard.tsx b/apps/frontend/src/renderer/components/KanbanBoard.tsx
index 3912d244..784aeb34 100644
--- a/apps/frontend/src/renderer/components/KanbanBoard.tsx
+++ b/apps/frontend/src/renderer/components/KanbanBoard.tsx
@@ -31,7 +31,7 @@ import { TASK_STATUS_COLUMNS, TASK_STATUS_LABELS } from '../../shared/constants'
import { cn } from '../lib/utils';
import { persistTaskStatus, forceCompleteTask, archiveTasks, deleteTasks, useTaskStore } from '../stores/task-store';
import { updateProjectSettings, useProjectStore } from '../stores/project-store';
-import { useKanbanSettingsStore, COLLAPSED_COLUMN_WIDTH, DEFAULT_COLUMN_WIDTH, MIN_COLUMN_WIDTH, MAX_COLUMN_WIDTH } from '../stores/kanban-settings-store';
+import { useKanbanSettingsStore, DEFAULT_COLUMN_WIDTH, MIN_COLUMN_WIDTH, MAX_COLUMN_WIDTH, COLLAPSED_COLUMN_WIDTH_REM, MIN_COLUMN_WIDTH_REM, MAX_COLUMN_WIDTH_REM, BASE_FONT_SIZE, pxToRem } from '../stores/kanban-settings-store';
import { useToast } from '../hooks/use-toast';
import { WorktreeCleanupDialog } from './WorktreeCleanupDialog';
import { BulkPRDialog } from './BulkPRDialog';
@@ -338,7 +338,7 @@ const DroppableColumn = memo(function DroppableColumn({ status, tasks, onTaskCli
'border-t-2',
isOver && 'drop-zone-highlight'
)}
- style={{ width: COLLAPSED_COLUMN_WIDTH, minWidth: COLLAPSED_COLUMN_WIDTH, maxWidth: COLLAPSED_COLUMN_WIDTH }}
+ style={{ width: COLLAPSED_COLUMN_WIDTH_REM, minWidth: COLLAPSED_COLUMN_WIDTH_REM, maxWidth: COLLAPSED_COLUMN_WIDTH_REM }}
>
{/* Expand button at top */}
@@ -381,7 +381,7 @@ const DroppableColumn = memo(function DroppableColumn({ status, tasks, onTaskCli
return (
{
if (!resizingColumn) return;
- const deltaX = clientX - resizeStartX.current;
+ const scaleFactor = parseFloat(getComputedStyle(document.documentElement).fontSize) / BASE_FONT_SIZE;
+ const deltaX = (clientX - resizeStartX.current) / scaleFactor;
const newWidth = Math.max(MIN_COLUMN_WIDTH, Math.min(MAX_COLUMN_WIDTH, resizeStartWidth.current + deltaX));
setColumnWidth(resizingColumn, newWidth);
}, [resizingColumn, setColumnWidth]);
diff --git a/apps/frontend/src/renderer/stores/kanban-settings-store.ts b/apps/frontend/src/renderer/stores/kanban-settings-store.ts
index 85a5e95f..1e6795ba 100644
--- a/apps/frontend/src/renderer/stores/kanban-settings-store.ts
+++ b/apps/frontend/src/renderer/stores/kanban-settings-store.ts
@@ -52,6 +52,9 @@ interface KanbanSettingsState {
/** localStorage key prefix for kanban settings persistence (sync cache) */
const KANBAN_SETTINGS_KEY_PREFIX = 'kanban-column-prefs';
+/** Base font size in pixels for rem conversion (matches CSS default) */
+export const BASE_FONT_SIZE = 16;
+
/** Default column width in pixels */
export const DEFAULT_COLUMN_WIDTH = 320;
@@ -64,6 +67,33 @@ export const MAX_COLUMN_WIDTH = 600;
/** Collapsed column width in pixels */
export const COLLAPSED_COLUMN_WIDTH = 48;
+// ============================================
+// Rem Conversion Helpers
+// ============================================
+
+/**
+ * Convert a pixel value to a rem string.
+ * Used for CSS width values that should scale with the UI scale system.
+ *
+ * @param px - The pixel value to convert
+ * @returns A rem string (e.g., "20rem" for 320px)
+ */
+export function pxToRem(px: number): string {
+ return `${px / BASE_FONT_SIZE}rem`;
+}
+
+/** Default column width in rem (scales with UI) */
+export const DEFAULT_COLUMN_WIDTH_REM = pxToRem(DEFAULT_COLUMN_WIDTH);
+
+/** Minimum column width in rem (scales with UI) */
+export const MIN_COLUMN_WIDTH_REM = pxToRem(MIN_COLUMN_WIDTH);
+
+/** Maximum column width in rem (scales with UI) */
+export const MAX_COLUMN_WIDTH_REM = pxToRem(MAX_COLUMN_WIDTH);
+
+/** Collapsed column width in rem (scales with UI) */
+export const COLLAPSED_COLUMN_WIDTH_REM = pxToRem(COLLAPSED_COLUMN_WIDTH);
+
// ============================================
// Debounce timer for saving kanban preferences to main process
// ============================================