diff --git a/packages/desktop-client/src/components/Modals.tsx b/packages/desktop-client/src/components/Modals.tsx
index 8f4fadba8363db7cf2a811cab79c938aa14386a9..5da4049a2bb22409c4345dc9959a639b3e2e2bbe 100644
--- a/packages/desktop-client/src/components/Modals.tsx
+++ b/packages/desktop-client/src/components/Modals.tsx
@@ -9,6 +9,8 @@ import useCategories from '../hooks/useCategories';
 import useSyncServerStatus from '../hooks/useSyncServerStatus';
 import { type CommonModalProps } from '../types/modals';
 
+import CategoryGroupMenu from './modals/CategoryGroupMenu';
+import CategoryMenu from './modals/CategoryMenu';
 import CloseAccount from './modals/CloseAccount';
 import ConfirmCategoryDelete from './modals/ConfirmCategoryDelete';
 import ConfirmTransactionEdit from './modals/ConfirmTransactionEdit';
@@ -24,6 +26,7 @@ import ImportTransactions from './modals/ImportTransactions';
 import LoadBackup from './modals/LoadBackup';
 import ManageRulesModal from './modals/ManageRulesModal';
 import MergeUnusedPayees from './modals/MergeUnusedPayees';
+import Notes from './modals/Notes';
 import PlaidExternalMsg from './modals/PlaidExternalMsg';
 import ReportBudgetSummary from './modals/ReportBudgetSummary';
 import RolloverBudgetSummary from './modals/RolloverBudgetSummary';
@@ -241,7 +244,7 @@ export default function Modals() {
             <SingleInput
               modalProps={modalProps}
               title="New Category"
-              inputPlaceholder="Name"
+              inputPlaceholder="Category name"
               buttonText="Add"
               onValidate={options.onValidate}
               onSubmit={options.onSubmit}
@@ -253,7 +256,7 @@ export default function Modals() {
             <SingleInput
               modalProps={modalProps}
               title="New Category Group"
-              inputPlaceholder="Name"
+              inputPlaceholder="Category group name"
               buttonText="Add"
               onValidate={options.onValidate}
               onSubmit={options.onSubmit}
@@ -326,6 +329,45 @@ export default function Modals() {
             />
           );
 
+        case 'category-menu':
+          return (
+            <CategoryMenu
+              key={name}
+              modalProps={modalProps}
+              categoryId={options.categoryId}
+              onSave={options.onSave}
+              onEditNotes={options.onEditNotes}
+              onDelete={options.onDelete}
+              onClose={options.onClose}
+            />
+          );
+
+        case 'category-group-menu':
+          return (
+            <CategoryGroupMenu
+              key={name}
+              modalProps={modalProps}
+              groupId={options.groupId}
+              onSave={options.onSave}
+              onAddCategory={options.onAddCategory}
+              onEditNotes={options.onEditNotes}
+              onSaveNotes={options.onSaveNotes}
+              onDelete={options.onDelete}
+              onClose={options.onClose}
+            />
+          );
+
+        case 'notes':
+          return (
+            <Notes
+              key={name}
+              modalProps={modalProps}
+              id={options.id}
+              name={options.name}
+              onSave={options.onSave}
+            />
+          );
+
         default:
           console.error('Unknown modal:', name);
           return null;
diff --git a/packages/desktop-client/src/components/Notes.tsx b/packages/desktop-client/src/components/Notes.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..818d77a0188e4438c0fc1e75c49aab6726c1ac31
--- /dev/null
+++ b/packages/desktop-client/src/components/Notes.tsx
@@ -0,0 +1,133 @@
+import React, { useEffect, useRef } from 'react';
+import ReactMarkdown from 'react-markdown';
+
+import { css } from 'glamor';
+import remarkGfm from 'remark-gfm';
+
+import { useResponsive } from '../ResponsiveProvider';
+import { type CSSProperties, theme } from '../style';
+import { remarkBreaks, sequentialNewlinesPlugin } from '../util/markdown';
+
+import Text from './common/Text';
+
+const remarkPlugins = [sequentialNewlinesPlugin, remarkGfm, remarkBreaks];
+
+const markdownStyles = css({
+  display: 'block',
+  maxWidth: 350,
+  padding: 8,
+  overflowWrap: 'break-word',
+  '& p': {
+    margin: 0,
+    ':not(:first-child)': {
+      marginTop: '0.25rem',
+    },
+  },
+  '& ul, & ol': {
+    listStylePosition: 'inside',
+    margin: 0,
+    paddingLeft: 0,
+  },
+  '&>* ul, &>* ol': {
+    marginLeft: '1.5rem',
+  },
+  '& li>p': {
+    display: 'contents',
+  },
+  '& blockquote': {
+    paddingLeft: '0.75rem',
+    borderLeft: '3px solid ' + theme.markdownDark,
+    margin: 0,
+  },
+  '& hr': {
+    borderTop: 'none',
+    borderLeft: 'none',
+    borderRight: 'none',
+    borderBottom: '1px solid ' + theme.markdownNormal,
+  },
+  '& code': {
+    backgroundColor: theme.markdownLight,
+    padding: '0.1rem 0.5rem',
+    borderRadius: '0.25rem',
+  },
+  '& pre': {
+    padding: '0.5rem',
+    backgroundColor: theme.markdownLight,
+    borderRadius: '0.5rem',
+    margin: 0,
+    ':not(:first-child)': {
+      marginTop: '0.25rem',
+    },
+    '& code': {
+      background: 'inherit',
+      padding: 0,
+      borderRadius: 0,
+    },
+  },
+  '& table, & th, & td': {
+    border: '1px solid ' + theme.markdownNormal,
+  },
+  '& table': {
+    borderCollapse: 'collapse',
+    wordBreak: 'break-word',
+  },
+  '& td': {
+    padding: '0.25rem 0.75rem',
+  },
+});
+
+type NotesProps = {
+  notes: string;
+  editable?: boolean;
+  focused?: boolean;
+  onChange?: (value: string) => void;
+  onBlur?: (value: string) => void;
+  getStyle?: (editable: boolean) => CSSProperties;
+};
+
+export default function Notes({
+  notes,
+  editable,
+  focused,
+  onChange,
+  onBlur,
+  getStyle,
+}: NotesProps) {
+  const { isNarrowWidth } = useResponsive();
+  const _onChange = value => {
+    onChange?.(value);
+  };
+
+  const textAreaRef = useRef<HTMLTextAreaElement>();
+
+  useEffect(() => {
+    if (focused && editable) {
+      textAreaRef.current.focus();
+    }
+  }, [focused, editable]);
+
+  return editable ? (
+    <textarea
+      ref={textAreaRef}
+      className={`${css({
+        border: '1px solid ' + theme.buttonNormalBorder,
+        padding: 7,
+        ...(!isNarrowWidth && { minWidth: 350, minHeight: 120 }),
+        outline: 'none',
+        backgroundColor: theme.tableBackground,
+        color: theme.tableText,
+        ...getStyle?.(editable),
+      })}`}
+      value={notes || ''}
+      onChange={e => _onChange(e.target.value)}
+      onBlur={e => onBlur?.(e.target.value)}
+      placeholder="Notes (markdown supported)"
+    />
+  ) : (
+    <Text {...markdownStyles} style={{ ...getStyle?.(editable) }}>
+      <ReactMarkdown remarkPlugins={remarkPlugins} linkTarget="_blank">
+        {notes}
+      </ReactMarkdown>
+    </Text>
+  );
+}
diff --git a/packages/desktop-client/src/components/NotesButton.tsx b/packages/desktop-client/src/components/NotesButton.tsx
index bbb1e14b67eef8508196b55c5721386ef7ae81d7..3e43538993eb8509f87dac6e08e8b7d524c08020 100644
--- a/packages/desktop-client/src/components/NotesButton.tsx
+++ b/packages/desktop-client/src/components/NotesButton.tsx
@@ -1,8 +1,4 @@
-import React, { createRef, useState, useEffect } from 'react';
-import ReactMarkdown from 'react-markdown';
-
-import { css } from 'glamor';
-import remarkGfm from 'remark-gfm';
+import React, { useState } from 'react';
 
 import q from 'loot-core/src/client/query-helpers';
 import { useLiveQuery } from 'loot-core/src/client/query-hooks';
@@ -10,79 +6,12 @@ import { send } from 'loot-core/src/platform/client/fetch';
 
 import CustomNotesPaper from '../icons/v2/CustomNotesPaper';
 import { type CSSProperties, theme } from '../style';
-import { remarkBreaks, sequentialNewlinesPlugin } from '../util/markdown';
 
 import Button from './common/Button';
-import Text from './common/Text';
 import View from './common/View';
+import Notes from './Notes';
 import { Tooltip, type TooltipPosition, useTooltip } from './tooltips';
 
-const remarkPlugins = [sequentialNewlinesPlugin, remarkGfm, remarkBreaks];
-
-const markdownStyles = css({
-  display: 'block',
-  maxWidth: 350,
-  padding: 8,
-  overflowWrap: 'break-word',
-  '& p': {
-    margin: 0,
-    ':not(:first-child)': {
-      marginTop: '0.25rem',
-    },
-  },
-  '& ul, & ol': {
-    listStylePosition: 'inside',
-    margin: 0,
-    paddingLeft: 0,
-  },
-  '&>* ul, &>* ol': {
-    marginLeft: '1.5rem',
-  },
-  '& li>p': {
-    display: 'contents',
-  },
-  '& blockquote': {
-    paddingLeft: '0.75rem',
-    borderLeft: '3px solid ' + theme.markdownDark,
-    margin: 0,
-  },
-  '& hr': {
-    borderTop: 'none',
-    borderLeft: 'none',
-    borderRight: 'none',
-    borderBottom: '1px solid ' + theme.markdownNormal,
-  },
-  '& code': {
-    backgroundColor: theme.markdownLight,
-    padding: '0.1rem 0.5rem',
-    borderRadius: '0.25rem',
-  },
-  '& pre': {
-    padding: '0.5rem',
-    backgroundColor: theme.markdownLight,
-    borderRadius: '0.5rem',
-    margin: 0,
-    ':not(:first-child)': {
-      marginTop: '0.25rem',
-    },
-    '& code': {
-      background: 'inherit',
-      padding: 0,
-      borderRadius: 0,
-    },
-  },
-  '& table, & th, & td': {
-    border: '1px solid ' + theme.markdownNormal,
-  },
-  '& table': {
-    borderCollapse: 'collapse',
-    wordBreak: 'break-word',
-  },
-  '& td': {
-    padding: '0.25rem 0.75rem',
-  },
-});
-
 type NotesTooltipProps = {
   editable?: boolean;
   defaultNotes?: string;
@@ -96,39 +25,14 @@ function NotesTooltip({
   onClose,
 }: NotesTooltipProps) {
   const [notes, setNotes] = useState<string>(defaultNotes);
-  const inputRef = createRef<HTMLTextAreaElement>();
-
-  useEffect(() => {
-    if (editable) {
-      inputRef.current.focus();
-    }
-  }, [inputRef, editable]);
-
   return (
     <Tooltip position={position} onClose={() => onClose(notes)}>
-      {editable ? (
-        <textarea
-          ref={inputRef}
-          className={`${css({
-            border: '1px solid ' + theme.buttonNormalBorder,
-            padding: 7,
-            minWidth: 350,
-            minHeight: 120,
-            outline: 'none',
-            backgroundColor: theme.tableBackground,
-            color: theme.tableText,
-          })}`}
-          value={notes || ''}
-          onChange={e => setNotes(e.target.value)}
-          placeholder="Notes (markdown supported)"
-        />
-      ) : (
-        <Text {...markdownStyles}>
-          <ReactMarkdown remarkPlugins={remarkPlugins} linkTarget="_blank">
-            {notes}
-          </ReactMarkdown>
-        </Text>
-      )}
+      <Notes
+        notes={notes}
+        editable={editable}
+        focused={editable}
+        onChange={setNotes}
+      />
     </Tooltip>
   );
 }
diff --git a/packages/desktop-client/src/components/budget/MobileBudget.jsx b/packages/desktop-client/src/components/budget/MobileBudget.jsx
index 8c3e7327c65db14682d8dbb9fd47739ff7656d7c..0408a57560ddda2a1b533d1bfd95f9a918ae8601 100644
--- a/packages/desktop-client/src/components/budget/MobileBudget.jsx
+++ b/packages/desktop-client/src/components/budget/MobileBudget.jsx
@@ -3,16 +3,6 @@ import { useSelector } from 'react-redux';
 
 import { useSpreadsheet } from 'loot-core/src/client/SpreadsheetProvider';
 import { send, listen } from 'loot-core/src/platform/client/fetch';
-import {
-  addCategory,
-  addGroup,
-  deleteCategory,
-  deleteGroup,
-  moveCategory,
-  moveCategoryGroup,
-  updateCategory,
-  updateGroup,
-} from 'loot-core/src/shared/categories';
 import * as monthUtils from 'loot-core/src/shared/months';
 
 import { useActions } from '../../hooks/useActions';
@@ -26,6 +16,9 @@ import SyncRefresh from '../SyncRefresh';
 import { BudgetTable } from './MobileBudgetTable';
 import { prewarmMonth, switchBudgetType } from './util';
 
+const CATEGORY_BUDGET_EDIT_ACTION = 'category-budget';
+const BALANCE_MENU_OPEN_ACTION = 'balance-menu';
+
 class Budget extends Component {
   constructor(props) {
     super(props);
@@ -36,13 +29,13 @@ class Budget extends Component {
       currentMonth,
       initialized: false,
       editMode: false,
-      categoryGroups: [],
+      editingBudgetCategoryId: null,
+      openBalanceActionMenuId: null,
     };
   }
 
   async loadCategories() {
-    const result = await this.props.getCategories();
-    this.setState({ categoryGroups: result.grouped });
+    await this.props.getCategories();
   }
 
   async componentDidMount() {
@@ -50,18 +43,17 @@ class Budget extends Component {
     //   this.setState({ editMode: false });
     // });
 
-    this.loadCategories();
-
     const { start, end } = await send('get-budget-bounds');
-    this.setState({ bounds: { start, end } });
-
     await prewarmMonth(
       this.props.budgetType,
       this.props.spreadsheet,
       this.state.currentMonth,
     );
 
-    this.setState({ initialized: true });
+    this.setState({
+      bounds: { start, end },
+      initialized: true,
+    });
 
     const unlisten = listen('sync-event', ({ type, tables }) => {
       if (
@@ -107,15 +99,7 @@ class Budget extends Component {
     this.props.pushModal('new-category-group', {
       onValidate: name => (!name ? 'Name is required.' : null),
       onSubmit: async name => {
-        const id = await this.props.createGroup(name);
-        this.setState(state => ({
-          categoryGroups: addGroup(state.categoryGroups, {
-            id,
-            name,
-            categories: [],
-            is_income: 0,
-          }),
-        }));
+        await this.props.createGroup(name);
       },
     });
   };
@@ -124,28 +108,19 @@ class Budget extends Component {
     this.props.pushModal('new-category', {
       onValidate: name => (!name ? 'Name is required.' : null),
       onSubmit: async name => {
-        const id = await this.props.createCategory(name, groupId, isIncome);
-        this.setState(state => ({
-          categoryGroups: addCategory(state.categoryGroups, {
-            id,
-            name,
-            cat_group: groupId,
-            is_income: isIncome ? 1 : 0,
-          }),
-        }));
+        this.props.collapseModals('category-group-menu');
+        await this.props.createCategory(name, groupId, isIncome);
       },
     });
   };
 
   onSaveGroup = group => {
     this.props.updateGroup(group);
-    this.setState(state => ({
-      categoryGroups: updateGroup(state.categoryGroups, group),
-    }));
   };
 
   onDeleteGroup = async groupId => {
-    const group = this.state.categoryGroups?.find(g => g.id === groupId);
+    const { categoryGroups } = this.props;
+    const group = categoryGroups?.find(g => g.id === groupId);
 
     if (!group) {
       return;
@@ -163,25 +138,18 @@ class Budget extends Component {
       this.props.pushModal('confirm-category-delete', {
         group: groupId,
         onDelete: transferCategory => {
+          this.props.collapseModals('category-group-menu');
           this.props.deleteGroup(groupId, transferCategory);
-          this.setState(state => ({
-            categoryGroups: deleteGroup(state.categoryGroups, groupId),
-          }));
         },
       });
     } else {
+      this.props.collapseModals('category-group-menu');
       this.props.deleteGroup(groupId);
-      this.setState(state => ({
-        categoryGroups: deleteGroup(state.categoryGroups, groupId),
-      }));
     }
   };
 
   onSaveCategory = category => {
     this.props.updateCategory(category);
-    this.setState(state => ({
-      categoryGroups: updateCategory(state.categoryGroups, category),
-    }));
   };
 
   onDeleteCategory = async categoryId => {
@@ -194,23 +162,19 @@ class Budget extends Component {
         category: categoryId,
         onDelete: transferCategory => {
           if (categoryId !== transferCategory) {
+            this.props.collapseModals('category-menu');
             this.props.deleteCategory(categoryId, transferCategory);
-            this.setState(state => ({
-              categoryGroups: deleteCategory(state.categoryGroups, categoryId),
-            }));
           }
         },
       });
     } else {
+      this.props.collapseModals('category-menu');
       this.props.deleteCategory(categoryId);
-      this.setState(state => ({
-        categoryGroups: deleteCategory(state.categoryGroups, categoryId),
-      }));
     }
   };
 
   onReorderCategory = (id, { inGroup, aroundCategory }) => {
-    const { categoryGroups } = this.state;
+    const { categoryGroups } = this.props;
     let groupId, targetId;
 
     if (inGroup) {
@@ -234,14 +198,10 @@ class Budget extends Component {
     }
 
     this.props.moveCategory(id, groupId, targetId);
-
-    this.setState({
-      categoryGroups: moveCategory(categoryGroups, id, groupId, targetId),
-    });
   };
 
   onReorderGroup = (id, targetId, position) => {
-    const { categoryGroups } = this.state;
+    const { categoryGroups } = this.props;
 
     if (position === 'bottom') {
       const idx = categoryGroups.findIndex(group => group.id === targetId);
@@ -250,10 +210,6 @@ class Budget extends Component {
     }
 
     this.props.moveCategoryGroup(id, targetId);
-
-    this.setState({
-      categoryGroups: moveCategoryGroup(categoryGroups, id, targetId),
-    });
   };
 
   sync = async () => {
@@ -280,16 +236,14 @@ class Budget extends Component {
     this.setState({ currentMonth: month, initialized: true });
   };
 
-  onOpenActionSheet = () => {
+  onOpenMonthActionMenu = () => {
     const { budgetType } = this.props;
 
     const options = [
-      'Edit Categories',
       'Copy last month’s budget',
       'Set budgets to zero',
       'Set budgets to 3 month average',
       budgetType === 'report' && 'Apply to all future budgets',
-      'Cancel',
     ].filter(Boolean);
 
     this.props.showActionSheetWithOptions(
@@ -341,11 +295,90 @@ class Budget extends Component {
     this.setState({ initialized: true });
   };
 
+  onSaveNotes = async (id, notes) => {
+    await send('notes-save', { id, note: notes });
+  };
+
+  onEditGroupNotes = id => {
+    const { categoryGroups } = this.props;
+    const group = categoryGroups.find(g => g.id === id);
+    this.props.pushModal('notes', {
+      id,
+      name: group.name,
+      onSave: this.onSaveNotes,
+    });
+  };
+
+  onEditCategoryNotes = id => {
+    const { categories } = this.props;
+    const category = categories.find(c => c.id === id);
+    this.props.pushModal('notes', {
+      id,
+      name: category.name,
+      onSave: this.onSaveNotes,
+    });
+  };
+
+  onEditGroup = id => {
+    const { categoryGroups } = this.props;
+    const group = categoryGroups.find(g => g.id === id);
+    this.props.pushModal('category-group-menu', {
+      groupId: group.id,
+      onSave: this.onSaveGroup,
+      onAddCategory: this.onAddCategory,
+      onEditNotes: this.onEditGroupNotes,
+      onDelete: this.onDeleteGroup,
+    });
+  };
+
+  onEditCategory = id => {
+    const { categories } = this.props;
+    const category = categories.find(c => c.id === id);
+    this.props.pushModal('category-menu', {
+      categoryId: category.id,
+      onSave: this.onSaveCategory,
+      onEditNotes: this.onEditCategoryNotes,
+      onDelete: this.onDeleteCategory,
+    });
+  };
+
+  onEditCategoryBudget = id => {
+    this.onEdit(CATEGORY_BUDGET_EDIT_ACTION, id);
+  };
+
+  onOpenBalanceActionMenu = id => {
+    this.onEdit(BALANCE_MENU_OPEN_ACTION, id);
+  };
+
+  onEdit = (action, id) => {
+    const { editingBudgetCategoryId, openBalanceActionMenuId } = this.state;
+
+    // Do not allow editing if another field is currently being edited.
+    // Cancel the currently editing field in that case.
+    const currentlyEditing = editingBudgetCategoryId || openBalanceActionMenuId;
+
+    this.setState({
+      editingBudgetCategoryId:
+        action === CATEGORY_BUDGET_EDIT_ACTION && !currentlyEditing ? id : null,
+      openBalanceActionMenuId:
+        action === BALANCE_MENU_OPEN_ACTION && !currentlyEditing ? id : null,
+    });
+
+    return { action, editingId: !currentlyEditing ? id : null };
+  };
+
   render() {
-    const { currentMonth, bounds, editMode, initialized } = this.state;
     const {
-      categories,
+      currentMonth,
+      bounds,
+      editMode,
+      initialized,
+      editingBudgetCategoryId,
+      openBalanceActionMenuId,
+    } = this.state;
+    const {
       categoryGroups,
+      categories,
       prefs,
       savePrefs,
       budgetType,
@@ -379,8 +412,8 @@ class Budget extends Component {
             // This key forces the whole table rerender when the number
             // format changes
             key={numberFormat + hideFraction}
-            categories={categories}
             categoryGroups={categoryGroups}
+            categories={categories}
             type={budgetType}
             month={currentMonth}
             monthBounds={bounds}
@@ -401,12 +434,21 @@ class Budget extends Component {
             onDeleteCategory={this.onDeleteCategory}
             onReorderCategory={this.onReorderCategory}
             onReorderGroup={this.onReorderGroup}
-            onOpenActionSheet={() => {}} //this.onOpenActionSheet}
+            onOpenMonthActionMenu={this.onOpenMonthActionMenu}
             onBudgetAction={applyBudgetAction}
             onRefresh={onRefresh}
             onSwitchBudgetType={this.onSwitchBudgetType}
+            onSaveNotes={this.onSaveNotes}
+            onEditGroupNotes={this.onEditGroupNotes}
+            onEditCategoryNotes={this.onEditCategoryNotes}
             savePrefs={savePrefs}
             pushModal={pushModal}
+            onEditGroup={this.onEditGroup}
+            onEditCategory={this.onEditCategory}
+            editingBudgetCategoryId={editingBudgetCategoryId}
+            onEditCategoryBudget={this.onEditCategoryBudget}
+            openBalanceActionMenuId={openBalanceActionMenuId}
+            onOpenBalanceActionMenu={this.onOpenBalanceActionMenu}
           />
         )}
       </SyncRefresh>
diff --git a/packages/desktop-client/src/components/budget/MobileBudgetTable.jsx b/packages/desktop-client/src/components/budget/MobileBudgetTable.jsx
index eb7905bd8dfc5179fc30ddf194c83c7de17b8233..d326e87ce3a86919a19c0d73ee376d123ee1cd1d 100644
--- a/packages/desktop-client/src/components/budget/MobileBudgetTable.jsx
+++ b/packages/desktop-client/src/components/budget/MobileBudgetTable.jsx
@@ -14,7 +14,6 @@ import { useResponsive } from '../../ResponsiveProvider';
 import { theme, styles } from '../../style';
 import Button from '../common/Button';
 import Card from '../common/Card';
-import InputWithContent from '../common/InputWithContent';
 import Label from '../common/Label';
 import Menu from '../common/Menu';
 import Text from '../common/Text';
@@ -243,71 +242,25 @@ const ExpenseCategory = memo(function ExpenseCategory({
   style,
   month,
   editMode,
-  isEditing,
   onEdit,
   isEditingBudget,
   onEditBudget,
-  onSave,
-  onDelete,
-  isBudgetActionMenuOpen,
-  onOpenBudgetActionMenu,
+  isBalanceActionMenuOpen,
+  onOpenBalanceActionMenu,
   onBudgetAction,
   show3Cols,
   showBudgetedCol,
 }) {
   const opacity = blank ? 0 : 1;
-  const showEditables = editMode || isEditing;
-
-  const [categoryName, setCategoryName] = useState(category.name);
-  const [isHidden, setIsHidden] = useState(category.hidden);
-
-  const tooltip = useTooltip();
   const balanceTooltip = useTooltip();
 
   useEffect(() => {
-    if (isBudgetActionMenuOpen) {
+    if (isBalanceActionMenuOpen) {
       balanceTooltip.open();
     }
-  }, [isBudgetActionMenuOpen, balanceTooltip]);
-
-  useEffect(() => {
-    if (!isEditing && tooltip.isOpen) {
-      tooltip.close();
-    }
-  }, [isEditing, tooltip]);
-
-  const onSubmit = () => {
-    if (categoryName) {
-      onSave?.({
-        ...category,
-        name: categoryName,
-      });
-    } else {
-      setCategoryName(category.name);
-    }
-    onEdit?.(null);
-  };
-
-  const onMenuSelect = type => {
-    onEdit?.(null);
-    switch (type) {
-      case 'toggle-visibility':
-        setIsHidden(!isHidden);
-        onSave?.({
-          ...category,
-          hidden: !isHidden,
-        });
-        break;
-      case 'delete':
-        onDelete?.(category.id);
-        break;
-      default:
-        throw new Error(`Unrecognized category menu type: ${type}`);
-    }
-  };
+  }, [isBalanceActionMenuOpen, balanceTooltip]);
 
   const listItemRef = useRef();
-  const inputRef = useRef();
 
   const _onBudgetAction = (monthIndex, action, arg) => {
     onBudgetAction?.(
@@ -320,90 +273,23 @@ const ExpenseCategory = memo(function ExpenseCategory({
   const content = (
     <ListItem
       style={{
-        backgroundColor: isEditingBudget
-          ? theme.tableTextEditing
-          : 'transparent',
+        backgroundColor: 'transparent',
         borderBottomWidth: 0,
         borderTopWidth: index > 0 ? 1 : 0,
-        opacity: isHidden ? 0.5 : undefined,
+        opacity: !!category.hidden ? 0.5 : undefined,
         ...style,
       }}
       data-testid="row"
       innerRef={listItemRef}
     >
-      <View
-        style={{
-          ...(!showEditables && { display: 'none' }),
-          flexDirection: 'row',
-          flex: 1,
-          justifyContent: 'center',
-          alignItems: 'center',
-          height: ROW_HEIGHT,
-        }}
-      >
-        <InputWithContent
-          focused={isEditing}
-          inputRef={inputRef}
-          rightContent={
-            <>
-              <Button
-                type="bare"
-                aria-label="Menu"
-                style={{ padding: 10 }}
-                {...tooltip.getOpenEvents()}
-              >
-                <DotsHorizontalTriple width={12} height={12} />
-              </Button>
-              {tooltip.isOpen && (
-                <Tooltip
-                  position="bottom-stretch"
-                  offset={1}
-                  style={{ padding: 0 }}
-                  onClose={() => {
-                    tooltip.close();
-                    inputRef.current?.focus();
-                  }}
-                >
-                  <Menu
-                    onMenuSelect={onMenuSelect}
-                    items={[
-                      {
-                        name: 'toggle-visibility',
-                        text: isHidden ? 'Show' : 'Hide',
-                      },
-                      {
-                        name: 'delete',
-                        text: 'Delete',
-                      },
-                    ]}
-                  />
-                </Tooltip>
-              )}
-            </>
-          }
-          style={{ width: '100%' }}
-          placeholder="Category Name"
-          value={categoryName}
-          onUpdate={setCategoryName}
-          onEnter={onSubmit}
-          onBlur={e => {
-            if (!listItemRef.current?.contains(e.relatedTarget)) {
-              onSubmit();
-            }
-          }}
-        />
-      </View>
-      <View
-        role="button"
-        style={{ ...(showEditables && { display: 'none' }), flex: 1 }}
-      >
+      <View role="button" style={{ flex: 1 }}>
         <Text
           style={{
             ...styles.smallText,
             ...styles.underlinedText,
             ...styles.lineClamp(2),
           }}
-          onPointerUp={() => onEdit?.(category.id)}
+          onClick={() => onEdit?.(category.id)}
           data-testid="category-name"
         >
           {category.name}
@@ -411,7 +297,6 @@ const ExpenseCategory = memo(function ExpenseCategory({
       </View>
       <View
         style={{
-          ...(showEditables && { display: 'none' }),
           justifyContent: 'center',
           alignItems: 'center',
           flexDirection: 'row',
@@ -463,7 +348,7 @@ const ExpenseCategory = memo(function ExpenseCategory({
         >
           <span
             role="button"
-            onPointerUp={() => onOpenBudgetActionMenu?.(category.id)}
+            onPointerUp={() => onOpenBalanceActionMenu?.(category.id)}
             onPointerDown={e => e.preventDefault()}
           >
             <BalanceWithCarryover
@@ -485,7 +370,7 @@ const ExpenseCategory = memo(function ExpenseCategory({
                   monthIndex={monthUtils.getMonthIndex(month)}
                   onBudgetAction={_onBudgetAction}
                   onClose={() => {
-                    onOpenBudgetActionMenu?.(null);
+                    onOpenBalanceActionMenu?.(null);
                   }}
                 />
               ) : (
@@ -496,7 +381,7 @@ const ExpenseCategory = memo(function ExpenseCategory({
                   monthIndex={monthUtils.getMonthIndex(month)}
                   onBudgetAction={_onBudgetAction}
                   onClose={() => {
-                    onOpenBudgetActionMenu?.(null);
+                    onOpenBalanceActionMenu?.(null);
                   }}
                 />
               ))}
@@ -546,64 +431,13 @@ const ExpenseGroupTotals = memo(function ExpenseGroupTotals({
   spent,
   balance,
   editMode,
-  isEditing,
   onEdit,
   blank,
-  onAddCategory,
-  onSave,
-  onDelete,
   show3Cols,
   showBudgetedCol,
 }) {
   const opacity = blank ? 0 : 1;
-  const showEditables = editMode || isEditing;
-
-  const [groupName, setGroupName] = useState(group.name);
-  const [isHidden, setIsHidden] = useState(group.hidden);
-
-  const tooltip = useTooltip();
-
-  useEffect(() => {
-    if (!isEditing && tooltip.isOpen) {
-      tooltip.close();
-    }
-  }, [isEditing]);
-
-  const onSubmit = () => {
-    if (groupName) {
-      onSave?.({
-        ...group,
-        name: groupName,
-      });
-    } else {
-      setGroupName(group.name);
-    }
-    onEdit?.(null);
-  };
-
-  const onMenuSelect = type => {
-    onEdit?.(null);
-    switch (type) {
-      case 'add-category':
-        onAddCategory?.(group.id, group.is_income);
-        break;
-      case 'toggle-visibility':
-        setIsHidden(!isHidden);
-        onSave?.({
-          ...group,
-          hidden: !isHidden,
-        });
-        break;
-      case 'delete':
-        onDelete?.(group.id);
-        break;
-      default:
-        throw new Error(`Unrecognized group menu type: ${type}`);
-    }
-  };
-
   const listItemRef = useRef();
-  const inputRef = useRef();
 
   const content = (
     <ListItem
@@ -611,90 +445,20 @@ const ExpenseGroupTotals = memo(function ExpenseGroupTotals({
         flexDirection: 'row',
         alignItems: 'center',
         backgroundColor: theme.tableRowHeaderBackground,
-        opacity: isHidden ? 0.5 : undefined,
+        opacity: !!group.hidden ? 0.5 : undefined,
       }}
       data-testid="totals"
       innerRef={listItemRef}
     >
-      <View
-        style={{
-          ...(!showEditables && { display: 'none' }),
-          flexDirection: 'row',
-          flex: 1,
-          justifyContent: 'center',
-          alignItems: 'center',
-          height: ROW_HEIGHT,
-        }}
-      >
-        <InputWithContent
-          focused={isEditing}
-          inputRef={inputRef}
-          rightContent={
-            <>
-              <Button
-                type="bare"
-                aria-label="Menu"
-                style={{ padding: 10 }}
-                {...tooltip.getOpenEvents()}
-              >
-                <DotsHorizontalTriple width={12} height={12} />
-              </Button>
-              {tooltip.isOpen && (
-                <Tooltip
-                  position="bottom-stretch"
-                  offset={1}
-                  style={{ padding: 0 }}
-                  onClose={() => {
-                    tooltip.close();
-                    inputRef.current?.focus();
-                  }}
-                >
-                  <Menu
-                    onMenuSelect={onMenuSelect}
-                    items={[
-                      {
-                        name: 'add-category',
-                        text: 'Add category',
-                      },
-                      {
-                        name: 'toggle-visibility',
-                        text: isHidden ? 'Show' : 'Hide',
-                      },
-                      {
-                        name: 'delete',
-                        text: 'Delete',
-                      },
-                    ]}
-                  />
-                </Tooltip>
-              )}
-            </>
-          }
-          style={{ width: '100%' }}
-          placeholder="Category Group Name"
-          value={groupName}
-          onUpdate={setGroupName}
-          onEnter={onSubmit}
-          onBlur={e => {
-            if (!listItemRef.current?.contains(e.relatedTarget)) {
-              onSubmit();
-            }
-          }}
-        />
-      </View>
-      <View
-        role="button"
-        style={{ ...(showEditables && { display: 'none' }), flex: 1 }}
-      >
+      <View role="button" style={{ flex: 1 }}>
         <Text
-          tabIndex={-1}
           style={{
             ...styles.smallText,
             ...styles.underlinedText,
             ...styles.lineClamp(2),
             fontWeight: '500',
           }}
-          onPointerUp={() => onEdit?.(group.id)}
+          onClick={() => onEdit?.(group.id)}
           data-testid="name"
         >
           {group.name}
@@ -702,7 +466,6 @@ const ExpenseGroupTotals = memo(function ExpenseGroupTotals({
       </View>
       <View
         style={{
-          ...(showEditables && { display: 'none' }),
           flexDirection: 'row',
           justifyContent: 'center',
           alignItems: 'center',
@@ -804,60 +567,10 @@ const IncomeGroupTotals = memo(function IncomeGroupTotals({
   budgeted,
   balance,
   style,
-  onAddCategory,
-  onSave,
-  onDelete,
   editMode,
-  isEditing,
   onEdit,
 }) {
-  const [groupName, setGroupName] = useState(group.name);
-  const [isHidden, setIsHidden] = useState(group.hidden);
-  const showEditables = editMode || isEditing;
-
-  const tooltip = useTooltip();
-
-  useEffect(() => {
-    if (!isEditing && tooltip.isOpen) {
-      tooltip.close();
-    }
-  }, [isEditing]);
-
-  const onSubmit = () => {
-    if (groupName) {
-      onSave?.({
-        ...group,
-        name: groupName,
-      });
-    } else {
-      setGroupName(group.name);
-    }
-    onEdit?.(null);
-  };
-
-  const onMenuSelect = type => {
-    onEdit?.(null);
-    switch (type) {
-      case 'add-category':
-        onAddCategory?.(group.id, group.is_income);
-        break;
-      case 'toggle-visibility':
-        setIsHidden(!isHidden);
-        onSave?.({
-          ...group,
-          hidden: !isHidden,
-        });
-        break;
-      case 'delete':
-        onDelete?.(group.id);
-        break;
-      default:
-        throw new Error(`Unrecognized group menu type: ${type}`);
-    }
-  };
-
   const listItemRef = useRef();
-  const inputRef = useRef();
 
   return (
     <ListItem
@@ -866,81 +579,14 @@ const IncomeGroupTotals = memo(function IncomeGroupTotals({
         alignItems: 'center',
         padding: 10,
         backgroundColor: theme.tableRowHeaderBackground,
-        opacity: isHidden ? 0.5 : undefined,
+        opacity: !!group.hidden ? 0.5 : undefined,
         ...style,
       }}
       innerRef={listItemRef}
     >
-      <View
-        style={{
-          ...(!showEditables && { display: 'none' }),
-          flexDirection: 'row',
-          flex: 1,
-          justifyContent: 'center',
-          alignItems: 'center',
-          height: ROW_HEIGHT,
-        }}
-      >
-        <InputWithContent
-          focused={isEditing}
-          inputRef={inputRef}
-          rightContent={
-            <>
-              <Button
-                type="bare"
-                aria-label="Menu"
-                style={{ padding: 10 }}
-                {...tooltip.getOpenEvents()}
-              >
-                <DotsHorizontalTriple width={12} height={12} />
-              </Button>
-              {tooltip.isOpen && (
-                <Tooltip
-                  position="bottom-stretch"
-                  offset={1}
-                  style={{ padding: 0 }}
-                  onClose={() => {
-                    tooltip.close();
-                    inputRef.current?.focus();
-                  }}
-                >
-                  <Menu
-                    onMenuSelect={onMenuSelect}
-                    items={[
-                      {
-                        name: 'add-category',
-                        text: 'Add category',
-                      },
-                      {
-                        name: 'toggle-visibility',
-                        text: isHidden ? 'Show' : 'Hide',
-                      },
-                      {
-                        name: 'delete',
-                        text: 'Delete',
-                      },
-                    ]}
-                  />
-                </Tooltip>
-              )}
-            </>
-          }
-          style={{ width: '100%' }}
-          placeholder="Category Group Name"
-          value={groupName}
-          onUpdate={setGroupName}
-          onEnter={onSubmit}
-          onBlur={e => {
-            if (!listItemRef.current?.contains(e.relatedTarget)) {
-              onSubmit();
-            }
-          }}
-        />
-      </View>
       <View
         role="button"
         style={{
-          ...(showEditables && { display: 'none' }),
           flex: 1,
           justifyContent: 'center',
           alignItems: 'flex-start',
@@ -954,7 +600,7 @@ const IncomeGroupTotals = memo(function IncomeGroupTotals({
             ...styles.lineClamp(2),
             fontWeight: '500',
           }}
-          onPointerUp={() => onEdit?.(group.id)}
+          onClick={() => onEdit?.(group.id)}
           data-testid="name"
         >
           {group.name}
@@ -963,7 +609,6 @@ const IncomeGroupTotals = memo(function IncomeGroupTotals({
       {budgeted && (
         <View
           style={{
-            ...(showEditables && { display: 'none' }),
             justifyContent: 'center',
             alignItems: 'flex-end',
             width: 90,
@@ -983,7 +628,6 @@ const IncomeGroupTotals = memo(function IncomeGroupTotals({
       )}
       <View
         style={{
-          ...(showEditables && { display: 'none' }),
           justifyContent: 'center',
           alignItems: 'flex-end',
           width: 90,
@@ -1005,64 +649,19 @@ const IncomeGroupTotals = memo(function IncomeGroupTotals({
 });
 
 const IncomeCategory = memo(function IncomeCategory({
+  index,
   category,
   budgeted,
   balance,
   month,
   style,
-  onSave,
-  onDelete,
   editMode,
-  isEditing,
   onEdit,
   onBudgetAction,
   isEditingBudget,
   onEditBudget,
 }) {
-  const [categoryName, setCategoryName] = useState(category.name);
-  const [isHidden, setIsHidden] = useState(category.hidden);
-  const showEditables = editMode || isEditing;
-
-  const tooltip = useTooltip();
-
-  useEffect(() => {
-    if (!isEditing && tooltip.isOpen) {
-      tooltip.close();
-    }
-  }, [isEditing]);
-
-  const onSubmit = () => {
-    if (categoryName) {
-      onSave?.({
-        ...category,
-        name: categoryName,
-      });
-    } else {
-      setCategoryName(category.name);
-    }
-    onEdit?.(null);
-  };
-
-  const onMenuSelect = type => {
-    onEdit?.(null);
-    switch (type) {
-      case 'toggle-visibility':
-        setIsHidden(!isHidden);
-        onSave?.({
-          ...category,
-          hidden: !isHidden,
-        });
-        break;
-      case 'delete':
-        onDelete?.(category.id);
-        break;
-      default:
-        throw new Error(`Unrecognized category menu type: ${type}`);
-    }
-  };
-
   const listItemRef = useRef();
-  const inputRef = useRef();
 
   return (
     <ListItem
@@ -1071,77 +670,16 @@ const IncomeCategory = memo(function IncomeCategory({
         alignItems: 'center',
         padding: 10,
         backgroundColor: 'transparent',
-        opacity: isHidden ? 0.5 : undefined,
+        borderBottomWidth: 0,
+        borderTopWidth: index > 0 ? 1 : 0,
+        opacity: !!category.hidden ? 0.5 : undefined,
         ...style,
       }}
       innerRef={listItemRef}
     >
-      <View
-        style={{
-          ...(!showEditables && { display: 'none' }),
-          flexDirection: 'row',
-          flex: 1,
-          justifyContent: 'center',
-          alignItems: 'center',
-          height: ROW_HEIGHT,
-        }}
-      >
-        <InputWithContent
-          focused={isEditing}
-          inputRef={inputRef}
-          rightContent={
-            <>
-              <Button
-                type="bare"
-                aria-label="Menu"
-                style={{ padding: 10 }}
-                {...tooltip.getOpenEvents()}
-              >
-                <DotsHorizontalTriple width={12} height={12} />
-              </Button>
-              {tooltip.isOpen && (
-                <Tooltip
-                  position="bottom-stretch"
-                  offset={1}
-                  style={{ padding: 0 }}
-                  onClose={() => {
-                    tooltip.close();
-                    inputRef.current?.focus();
-                  }}
-                >
-                  <Menu
-                    onMenuSelect={onMenuSelect}
-                    items={[
-                      {
-                        name: 'toggle-visibility',
-                        text: isHidden ? 'Show' : 'Hide',
-                      },
-                      {
-                        name: 'delete',
-                        text: 'Delete',
-                      },
-                    ]}
-                  />
-                </Tooltip>
-              )}
-            </>
-          }
-          style={{ width: '100%' }}
-          placeholder="Category Name"
-          value={categoryName}
-          onUpdate={setCategoryName}
-          onEnter={onSubmit}
-          onBlur={e => {
-            if (!listItemRef.current?.contains(e.relatedTarget)) {
-              onSubmit();
-            }
-          }}
-        />
-      </View>
       <View
         role="button"
         style={{
-          ...(showEditables && { display: 'none' }),
           flex: 1,
           justifyContent: 'center',
           alignItems: 'flex-start',
@@ -1149,13 +687,12 @@ const IncomeCategory = memo(function IncomeCategory({
         }}
       >
         <Text
-          tabIndex={-1}
           style={{
             ...styles.smallText,
             ...styles.underlinedText,
             ...styles.lineClamp(2),
           }}
-          onPointerUp={() => onEdit?.(category.id)}
+          onClick={() => onEdit?.(category.id)}
           data-testid="name"
         >
           {category.name}
@@ -1164,7 +701,6 @@ const IncomeCategory = memo(function IncomeCategory({
       {budgeted && (
         <View
           style={{
-            ...(showEditables && { display: 'none' }),
             justifyContent: 'center',
             alignItems: 'flex-end',
             width: 90,
@@ -1196,7 +732,6 @@ const IncomeCategory = memo(function IncomeCategory({
       )}
       <View
         style={{
-          ...(showEditables && { display: 'none' }),
           justifyContent: 'center',
           alignItems: 'flex-end',
           width: 90,
@@ -1259,23 +794,17 @@ const ExpenseGroup = memo(function ExpenseGroup({
   type,
   group,
   editMode,
-  editingGroupId,
   onEditGroup,
-  editingCategoryId,
   onEditCategory,
   editingBudgetCategoryId,
   onEditCategoryBudget,
-  openBudgetActionMenuId,
-  onOpenBudgetActionMenu,
+  openBalanceActionMenuId,
+  onOpenBalanceActionMenu,
   // gestures,
   month,
-  onSaveCategory,
-  onDeleteCategory,
   // onReorderCategory,
   // onReorderGroup,
   onAddCategory,
-  onSave,
-  onDelete,
   onBudgetAction,
   showBudgetedCol,
   show3Cols,
@@ -1338,9 +867,6 @@ const ExpenseGroup = memo(function ExpenseGroup({
         show3Cols={show3Cols}
         editMode={editMode}
         onAddCategory={onAddCategory}
-        onSave={onSave}
-        onDelete={onDelete}
-        isEditing={editingGroupId === group.id}
         onEdit={onEditGroup}
         // onReorderCategory={onReorderCategory}
       />
@@ -1348,16 +874,16 @@ const ExpenseGroup = memo(function ExpenseGroup({
       {group.categories
         .filter(category => !category.hidden || showHiddenCategories)
         .map((category, index) => {
-          const isEditingCategory = editingCategoryId === category.id;
           const isEditingCategoryBudget =
             editingBudgetCategoryId === category.id;
-          const isBudgetActionMenuOpen = openBudgetActionMenuId === category.id;
+          const isBalanceActionMenuOpen =
+            openBalanceActionMenuId === category.id;
           return (
             <ExpenseCategory
               key={category.id}
+              index={index}
               show3Cols={show3Cols}
               type={type}
-              index={index}
               category={category}
               goal={
                 type === 'report'
@@ -1384,23 +910,20 @@ const ExpenseGroup = memo(function ExpenseGroup({
                   ? reportBudget.catCarryover(category.id)
                   : rolloverBudget.catCarryover(category.id)
               }
+              style={{
+                backgroundColor: theme.tableBackground,
+              }}
               showBudgetedCol={showBudgetedCol}
               editMode={editMode}
-              isEditing={isEditingCategory}
               onEdit={onEditCategory}
               isEditingBudget={isEditingCategoryBudget}
               onEditBudget={onEditCategoryBudget}
-              isBudgetActionMenuOpen={isBudgetActionMenuOpen}
-              onOpenBudgetActionMenu={onOpenBudgetActionMenu}
+              isBalanceActionMenuOpen={isBalanceActionMenuOpen}
+              onOpenBalanceActionMenu={onOpenBalanceActionMenu}
               // gestures={gestures}
               month={month}
-              onSave={onSaveCategory}
-              onDelete={onDeleteCategory}
               // onReorder={onReorderCategory}
               onBudgetAction={onBudgetAction}
-              style={{
-                backgroundColor: theme.tableBackground,
-              }}
             />
           );
         })}
@@ -1412,16 +935,10 @@ function IncomeGroup({
   type,
   group,
   month,
-  onSave,
-  onDelete,
   onAddCategory,
-  onSaveCategory,
-  onDeleteCategory,
   showHiddenCategories,
   editMode,
-  editingGroupId,
   onEditGroup,
-  editingCategoryId,
   onEditCategory,
   editingBudgetCategoryId,
   onEditCategoryBudget,
@@ -1458,10 +975,7 @@ function IncomeGroup({
             backgroundColor: theme.tableRowHeaderBackground,
           }}
           onAddCategory={onAddCategory}
-          onSave={onSave}
-          onDelete={onDelete}
           editMode={editMode}
-          isEditing={editingGroupId === group.id}
           onEdit={onEditGroup}
         />
 
@@ -1471,6 +985,7 @@ function IncomeGroup({
             return (
               <IncomeCategory
                 key={category.id}
+                index={index}
                 category={category}
                 month={month}
                 type={type}
@@ -1484,15 +999,11 @@ function IncomeGroup({
                     ? reportBudget.catSumAmount(category.id)
                     : rolloverBudget.catSumAmount(category.id)
                 }
-                index={index}
-                onSave={onSaveCategory}
-                onDelete={onDeleteCategory}
-                editMode={editMode}
-                isEditing={editingCategoryId === category.id}
-                onEdit={onEditCategory}
                 style={{
                   backgroundColor: theme.tableBackground,
                 }}
+                editMode={editMode}
+                onEdit={onEditCategory}
                 onBudgetAction={onBudgetAction}
                 isEditingBudget={editingBudgetCategoryId === category.id}
                 onEditBudget={onEditCategoryBudget}
@@ -1507,14 +1018,12 @@ function IncomeGroup({
 function BudgetGroups({
   type,
   categoryGroups,
-  editingGroupId,
   onEditGroup,
-  editingCategoryId,
   onEditCategory,
   editingBudgetCategoryId,
   onEditCategoryBudget,
-  openBudgetActionMenuId,
-  onOpenBudgetActionMenu,
+  openBalanceActionMenuId,
+  onOpenBalanceActionMenu,
   editMode,
   gestures,
   month,
@@ -1522,14 +1031,13 @@ function BudgetGroups({
   onDeleteCategory,
   onAddCategory,
   onAddGroup,
-  onSaveGroup,
-  onDeleteGroup,
   onReorderCategory,
   onReorderGroup,
   onBudgetAction,
   showBudgetedCol,
   show3Cols,
   showHiddenCategories,
+  pushModal,
 }) {
   const separateGroups = memoizeOne(groups => {
     return {
@@ -1557,24 +1065,21 @@ function BudgetGroups({
               gestures={gestures}
               month={month}
               editMode={editMode}
-              editingGroupId={editingGroupId}
               onEditGroup={onEditGroup}
-              editingCategoryId={editingCategoryId}
               onEditCategory={onEditCategory}
               editingBudgetCategoryId={editingBudgetCategoryId}
               onEditCategoryBudget={onEditCategoryBudget}
-              openBudgetActionMenuId={openBudgetActionMenuId}
-              onOpenBudgetActionMenu={onOpenBudgetActionMenu}
+              openBalanceActionMenuId={openBalanceActionMenuId}
+              onOpenBalanceActionMenu={onOpenBalanceActionMenu}
               onSaveCategory={onSaveCategory}
               onDeleteCategory={onDeleteCategory}
               onAddCategory={onAddCategory}
-              onSave={onSaveGroup}
-              onDelete={onDeleteGroup}
               onReorderCategory={onReorderCategory}
               onReorderGroup={onReorderGroup}
               onBudgetAction={onBudgetAction}
               show3Cols={show3Cols}
               showHiddenCategories={showHiddenCategories}
+              pushModal={pushModal}
             />
           );
         })}
@@ -1585,7 +1090,7 @@ function BudgetGroups({
           justifyContent: 'flex-start',
         }}
       >
-        <Button onPointerUp={onAddGroup} style={{ fontSize: 12, margin: 10 }}>
+        <Button onClick={onAddGroup} style={{ fontSize: 12, margin: 10 }}>
           Add Group
         </Button>
       </View>
@@ -1595,101 +1100,60 @@ function BudgetGroups({
           type={type}
           group={incomeGroup}
           month={month}
-          onSave={onSaveGroup}
-          onDelete={onDeleteGroup}
           onAddCategory={onAddCategory}
           onSaveCategory={onSaveCategory}
           onDeleteCategory={onDeleteCategory}
           showHiddenCategories={showHiddenCategories}
           editMode={editMode}
-          editingGroupId={editingGroupId}
           onEditGroup={onEditGroup}
-          editingCategoryId={editingCategoryId}
           onEditCategory={onEditCategory}
           editingBudgetCategoryId={editingBudgetCategoryId}
           onEditCategoryBudget={onEditCategoryBudget}
           onBudgetAction={onBudgetAction}
+          pushModal={pushModal}
         />
       )}
     </View>
   );
 }
 
-export function BudgetTable(props) {
-  const {
-    type,
-    categoryGroups,
-    month,
-    monthBounds,
-    editMode,
-    // refreshControl,
-    onPrevMonth,
-    onNextMonth,
-    onSaveGroup,
-    onDeleteGroup,
-    onAddGroup,
-    onAddCategory,
-    onSaveCategory,
-    onDeleteCategory,
-    onEditMode,
-    onReorderCategory,
-    onReorderGroup,
-    onShowBudgetSummary,
-    // onOpenActionSheet,
-    onBudgetAction,
-    onRefresh,
-    onSwitchBudgetType,
-    savePrefs,
-    pushModal,
-  } = props;
-
-  const GROUP_EDIT_ACTION = 'group';
-  const [editingGroupId, setEditingGroupId] = useState(null);
-  function onEditGroup(id) {
-    onEdit(GROUP_EDIT_ACTION, id);
-  }
-
-  const CATEGORY_EDIT_ACTION = 'category';
-  const [editingCategoryId, setEditingCategoryId] = useState(null);
-  function onEditCategory(id) {
-    onEdit(CATEGORY_EDIT_ACTION, id);
-  }
-
-  const CATEGORY_BUDGET_EDIT_ACTION = 'category-budget';
-  const [editingBudgetCategoryId, setEditingBudgetCategoryId] = useState(null);
-  function onEditCategoryBudget(id) {
-    onEdit(CATEGORY_BUDGET_EDIT_ACTION, id);
-  }
-
-  const BUDGET_MENU_OPEN_ACTION = 'budget-menu';
-  const [openBudgetActionMenuId, setOpenBudgetActionMenuId] = useState(null);
-  function onOpenBudgetActionMenu(id) {
-    onEdit(BUDGET_MENU_OPEN_ACTION, id);
-  }
-
-  function onEdit(action, id) {
-    // Do not allow editing if another field is currently being edited.
-    // Cancel the currently editing field in that case.
-    const currentlyEditing =
-      editingGroupId ||
-      editingCategoryId ||
-      editingBudgetCategoryId ||
-      openBudgetActionMenuId;
-
-    setEditingGroupId(
-      action === GROUP_EDIT_ACTION && !currentlyEditing ? id : null,
-    );
-    setEditingCategoryId(
-      action === CATEGORY_EDIT_ACTION && !currentlyEditing ? id : null,
-    );
-    setEditingBudgetCategoryId(
-      action === CATEGORY_BUDGET_EDIT_ACTION && !currentlyEditing ? id : null,
-    );
-    setOpenBudgetActionMenuId(
-      action === BUDGET_MENU_OPEN_ACTION && !currentlyEditing ? id : null,
-    );
-  }
-
+export function BudgetTable({
+  type,
+  categoryGroups,
+  categories,
+  month,
+  monthBounds,
+  editMode,
+  // refreshControl,
+  onPrevMonth,
+  onNextMonth,
+  onSaveGroup,
+  onDeleteGroup,
+  onAddGroup,
+  onAddCategory,
+  onSaveCategory,
+  onDeleteCategory,
+  onEditMode,
+  onReorderCategory,
+  onReorderGroup,
+  onShowBudgetSummary,
+  onOpenMonthActionMenu,
+  onBudgetAction,
+  onRefresh,
+  onSwitchBudgetType,
+  onSaveNotes,
+  onEditGroupNotes,
+  onEditCategoryNotes,
+  savePrefs,
+  pushModal,
+  onEditGroup,
+  onEditCategory,
+  editingBudgetCategoryId,
+  onEditCategoryBudget,
+  openBalanceActionMenuId,
+  onOpenBalanceActionMenu,
+  ...props
+}) {
   const { width } = useResponsive();
   const show3Cols = width >= 360;
 
@@ -1748,7 +1212,7 @@ export function BudgetTable(props) {
         }
         headerRightContent={
           !editMode ? (
-            <BudgetMenu
+            <BudgetPageMenu
               onEditMode={onEditMode}
               onToggleHiddenCategories={onToggleHiddenCategories}
               onSwitchBudgetType={_onSwitchBudgetType}
@@ -1925,14 +1389,12 @@ export function BudgetTable(props) {
                 // gestures={gestures}
                 month={month}
                 editMode={editMode}
-                editingGroupId={editingGroupId}
                 onEditGroup={onEditGroup}
-                editingCategoryId={editingCategoryId}
                 onEditCategory={onEditCategory}
                 editingBudgetCategoryId={editingBudgetCategoryId}
                 onEditCategoryBudget={onEditCategoryBudget}
-                openBudgetActionMenuId={openBudgetActionMenuId}
-                onOpenBudgetActionMenu={onOpenBudgetActionMenu}
+                openBalanceActionMenuId={openBalanceActionMenuId}
+                onOpenBalanceActionMenu={onOpenBalanceActionMenu}
                 onSaveCategory={onSaveCategory}
                 onDeleteCategory={onDeleteCategory}
                 onAddCategory={onAddCategory}
@@ -1941,7 +1403,9 @@ export function BudgetTable(props) {
                 onDeleteGroup={onDeleteGroup}
                 onReorderCategory={onReorderCategory}
                 onReorderGroup={onReorderGroup}
+                onOpenMonthActionMenu={onOpenMonthActionMenu}
                 onBudgetAction={onBudgetAction}
+                pushModal={pushModal}
               />
             </View>
           ) : (
@@ -1954,7 +1418,7 @@ export function BudgetTable(props) {
             //     scrollRef,
             //     onScroll
             //   }) => (
-            <View>
+            <View data-testid="budget-table">
               <BudgetGroups
                 type={type}
                 categoryGroups={categoryGroups}
@@ -1963,12 +1427,12 @@ export function BudgetTable(props) {
                 showHiddenCategories={showHiddenCategories}
                 // gestures={gestures}
                 editMode={editMode}
-                editingGroupId={editingGroupId}
                 onEditGroup={onEditGroup}
-                editingCategoryId={editingCategoryId}
                 onEditCategory={onEditCategory}
                 editingBudgetCategoryId={editingBudgetCategoryId}
                 onEditCategoryBudget={onEditCategoryBudget}
+                openBalanceActionMenuId={openBalanceActionMenuId}
+                onOpenBalanceActionMenu={onOpenBalanceActionMenu}
                 onSaveCategory={onSaveCategory}
                 onDeleteCategory={onDeleteCategory}
                 onAddCategory={onAddCategory}
@@ -1977,7 +1441,9 @@ export function BudgetTable(props) {
                 onDeleteGroup={onDeleteGroup}
                 onReorderCategory={onReorderCategory}
                 onReorderGroup={onReorderGroup}
+                onOpenMonthActionMenu={onOpenMonthActionMenu}
                 onBudgetAction={onBudgetAction}
+                pushModal={pushModal}
               />
             </View>
 
@@ -1990,7 +1456,7 @@ export function BudgetTable(props) {
   );
 }
 
-function BudgetMenu({
+function BudgetPageMenu({
   onEditMode,
   onToggleHiddenCategories,
   onSwitchBudgetType,
@@ -2044,7 +1510,8 @@ function BudgetMenu({
           <Menu
             onMenuSelect={onMenuSelect}
             items={[
-              { name: 'edit-mode', text: 'Edit mode' },
+              // Removing for now until we work on mobile category drag and drop.
+              // { name: 'edit-mode', text: 'Edit mode' },
               {
                 name: 'toggle-hidden-categories',
                 text: 'Toggle hidden categories',
diff --git a/packages/desktop-client/src/components/common/Menu.tsx b/packages/desktop-client/src/components/common/Menu.tsx
index eb5efa66c66e3563843758784d30f944a2d0e33c..2f0d002f71c966d66dec4052450f85eceb84ffdb 100644
--- a/packages/desktop-client/src/components/common/Menu.tsx
+++ b/packages/desktop-client/src/components/common/Menu.tsx
@@ -6,7 +6,7 @@ import {
   useState,
 } from 'react';
 
-import { theme } from '../../style';
+import { type CSSProperties, theme } from '../../style';
 
 import Text from './Text';
 import View from './View';
@@ -31,6 +31,7 @@ type MenuItem = {
   iconSize?: number;
   text: string;
   key?: string;
+  style?: CSSProperties;
 };
 
 type MenuProps<T extends MenuItem = MenuItem> = {
@@ -38,6 +39,7 @@ type MenuProps<T extends MenuItem = MenuItem> = {
   footer?: ReactNode;
   items: Array<T | typeof Menu.line>;
   onMenuSelect: (itemName: T['name']) => void;
+  style?: CSSProperties;
 };
 
 export default function Menu<T extends MenuItem>({
@@ -45,6 +47,7 @@ export default function Menu<T extends MenuItem>({
   footer,
   items: allItems,
   onMenuSelect,
+  style,
 }: MenuProps<T>) {
   const elRef = useRef(null);
   const items = allItems.filter(x => x);
@@ -101,7 +104,7 @@ export default function Menu<T extends MenuItem>({
 
   return (
     <View
-      style={{ outline: 'none', borderRadius: 4, overflow: 'hidden' }}
+      style={{ outline: 'none', borderRadius: 4, overflow: 'hidden', ...style }}
       tabIndex={1}
       innerRef={elRef}
     >
@@ -155,6 +158,7 @@ export default function Menu<T extends MenuItem>({
                   backgroundColor: theme.menuItemBackgroundHover,
                   color: theme.menuItemTextHover,
                 }),
+              ...item.style,
             }}
             onMouseEnter={() => setHoveredIndex(idx)}
             onMouseLeave={() => setHoveredIndex(null)}
@@ -168,7 +172,10 @@ export default function Menu<T extends MenuItem>({
                 createElement(item.icon, {
                   width: item.iconSize || 10,
                   height: item.iconSize || 10,
-                  style: { marginRight: 7, width: 10 },
+                  style: {
+                    marginRight: 7,
+                    width: item.iconSize || 10,
+                  },
                 })}
             </Text>
             <Text>{item.text}</Text>
diff --git a/packages/desktop-client/src/components/common/Modal.tsx b/packages/desktop-client/src/components/common/Modal.tsx
index 6f1de3b2f0861803c0cddd5792c2709db88fde6d..4daa0c31f2962fbf2203bcc046c3f5dda7ab25e0 100644
--- a/packages/desktop-client/src/components/common/Modal.tsx
+++ b/packages/desktop-client/src/components/common/Modal.tsx
@@ -3,6 +3,7 @@ import React, {
   useRef,
   useLayoutEffect,
   type ReactNode,
+  useState,
 } from 'react';
 import ReactModal from 'react-modal';
 
@@ -14,18 +15,25 @@ import { type CSSProperties, styles, theme } from '../../style';
 import tokens from '../../tokens';
 
 import Button from './Button';
+import Input from './Input';
 import Text from './Text';
 import View from './View';
 
+type ModalChildrenProps = {
+  isEditingTitle: boolean;
+};
+
 export type ModalProps = {
   title?: string;
   isCurrent?: boolean;
   isHidden?: boolean;
-  children: ReactNode | (() => ReactNode);
-  size?: { width?: number; height?: number };
+  children: ReactNode | ((props: ModalChildrenProps) => ReactNode);
+  size?: { width?: CSSProperties['width']; height?: CSSProperties['height'] };
   padding?: CSSProperties['padding'];
   showHeader?: boolean;
+  leftHeaderContent?: ReactNode;
   showTitle?: boolean;
+  editableTitle?: boolean;
   showClose?: boolean;
   showOverlay?: boolean;
   loading?: boolean;
@@ -34,9 +42,11 @@ export type ModalProps = {
   stackIndex?: number;
   parent?: HTMLElement;
   style?: CSSProperties;
+  titleStyle?: CSSProperties;
   contentStyle?: CSSProperties;
   overlayStyle?: CSSProperties;
   onClose?: () => void;
+  onTitleUpdate?: (title: string) => void;
 };
 
 const Modal = ({
@@ -46,7 +56,9 @@ const Modal = ({
   size,
   padding = 20,
   showHeader = true,
+  leftHeaderContent,
   showTitle = true,
+  editableTitle = false,
   showClose = true,
   showOverlay = true,
   loading = false,
@@ -55,10 +67,12 @@ const Modal = ({
   stackIndex,
   parent,
   style,
+  titleStyle,
   contentStyle,
   overlayStyle,
   children,
   onClose,
+  onTitleUpdate,
 }: ModalProps) => {
   useEffect(() => {
     // This deactivates any key handlers in the "app" scope. Ideally
@@ -69,22 +83,39 @@ const Modal = ({
     return () => hotkeys.setScope(prevScope);
   }, []);
 
+  const [isEditingTitle, setIsEditingTitle] = useState(false);
+  const [_title, setTitle] = useState(title);
+
+  const onTitleClick = () => {
+    setIsEditingTitle(true);
+  };
+
+  const _onTitleUpdate = newTitle => {
+    if (newTitle !== title) {
+      onTitleUpdate?.(newTitle);
+    }
+    setIsEditingTitle(false);
+  };
+
   return (
     <ReactModal
       isOpen={true}
       onRequestClose={onClose}
-      shouldCloseOnOverlayClick={false}
+      shouldCloseOnOverlayClick={true}
       shouldFocusAfterRender={!global.IS_DESIGN_MODE}
       shouldReturnFocusAfterClose={focusAfterClose}
       appElement={document.querySelector('#root') as HTMLElement}
       parentSelector={parent && (() => parent)}
       style={{
         content: {
+          display: 'flex',
+          height: 'fit-content',
+          width: 'fit-content',
+          position: 'absolute',
           top: 0,
           left: 0,
           right: 0,
           bottom: 0,
-          display: 'flex',
           justifyContent: 'center',
           alignItems: 'center',
           overflow: 'visible',
@@ -93,10 +124,11 @@ const Modal = ({
           backgroundColor: 'transparent',
           padding: 0,
           pointerEvents: 'auto',
-          margin: '0 10px',
+          margin: 'auto',
           ...contentStyle,
         },
         overlay: {
+          display: 'flex',
           zIndex: 3000,
           backgroundColor:
             showOverlay && stackIndex === 0 ? 'rgba(0, 0, 0, .1)' : 'none',
@@ -120,7 +152,8 @@ const Modal = ({
         size={size}
         style={{
           willChange: 'opacity, transform',
-          minWidth: '100%',
+          maxWidth: '90vw',
+          minWidth: '90vw',
           minHeight: 0,
           borderRadius: 4,
           //border: '1px solid ' + theme.modalBorder,
@@ -143,6 +176,28 @@ const Modal = ({
               flexShrink: 0,
             }}
           >
+            <View
+              style={{
+                position: 'absolute',
+                left: 0,
+                top: 0,
+                bottom: 0,
+                justifyContent: 'center',
+                alignItems: 'center',
+              }}
+            >
+              <View
+                style={{
+                  flexDirection: 'row',
+                  marginLeft: 15,
+                }}
+              >
+                {leftHeaderContent && !isEditingTitle
+                  ? leftHeaderContent
+                  : null}
+              </View>
+            </View>
+
             {showTitle && (
               <View
                 style={{
@@ -155,17 +210,38 @@ const Modal = ({
                   width: 'calc(100% - 40px)',
                 }}
               >
-                <Text
-                  style={{
-                    fontSize: 25,
-                    fontWeight: 700,
-                    whiteSpace: 'nowrap',
-                    overflow: 'hidden',
-                    textOverflow: 'ellipsis',
-                  }}
-                >
-                  {title}
-                </Text>
+                {isEditingTitle ? (
+                  <Input
+                    style={{
+                      fontSize: 25,
+                      fontWeight: 700,
+                      textAlign: 'center',
+                    }}
+                    value={_title}
+                    onChange={e => setTitle(e.target.value)}
+                    onKeyDown={e => {
+                      if (e.key === 'Enter') {
+                        e.preventDefault();
+                        _onTitleUpdate(e.currentTarget.value);
+                      }
+                    }}
+                    onBlur={e => _onTitleUpdate(e.target.value)}
+                  />
+                ) : (
+                  <Text
+                    style={{
+                      fontSize: 25,
+                      fontWeight: 700,
+                      whiteSpace: 'nowrap',
+                      overflow: 'hidden',
+                      textOverflow: 'ellipsis',
+                      ...titleStyle,
+                    }}
+                    {...(editableTitle && { onPointerUp: onTitleClick })}
+                  >
+                    {_title}
+                  </Text>
+                )}
               </View>
             )}
 
@@ -185,7 +261,7 @@ const Modal = ({
                   marginRight: 15,
                 }}
               >
-                {showClose && (
+                {showClose && !isEditingTitle && (
                   <Button
                     type="bare"
                     onClick={onClose}
@@ -200,7 +276,9 @@ const Modal = ({
           </View>
         )}
         <View style={{ padding, paddingTop: 0, flex: 1 }}>
-          {typeof children === 'function' ? children() : children}
+          {typeof children === 'function'
+            ? children({ isEditingTitle })
+            : children}
         </View>
         {loading && (
           <View
diff --git a/packages/desktop-client/src/components/modals/CategoryGroupMenu.tsx b/packages/desktop-client/src/components/modals/CategoryGroupMenu.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..55eb435c1d3fade549061881139605a0f0dacba0
--- /dev/null
+++ b/packages/desktop-client/src/components/modals/CategoryGroupMenu.tsx
@@ -0,0 +1,261 @@
+import React, { type ComponentProps, useState } from 'react';
+
+import { useLiveQuery } from 'loot-core/src/client/query-hooks';
+import q from 'loot-core/src/shared/query';
+import { type CategoryGroupEntity } from 'loot-core/src/types/models';
+
+import useCategories from '../../hooks/useCategories';
+import { DotsHorizontalTriple } from '../../icons/v1';
+import Add from '../../icons/v1/Add';
+import Trash from '../../icons/v1/Trash';
+import NotesPaper from '../../icons/v2/NotesPaper';
+import ViewHide from '../../icons/v2/ViewHide';
+import ViewShow from '../../icons/v2/ViewShow';
+import { type CSSProperties, styles, theme } from '../../style';
+import { type CommonModalProps } from '../../types/modals';
+import Button from '../common/Button';
+import Menu from '../common/Menu';
+import Modal from '../common/Modal';
+import View from '../common/View';
+import Notes from '../Notes';
+import { Tooltip } from '../tooltips';
+
+const BUTTON_HEIGHT = 40;
+
+type CategoryGroupMenuProps = {
+  modalProps: CommonModalProps;
+  groupId: string;
+  onSave: (group: CategoryGroupEntity) => void;
+  onAddCategory: (groupId: string, isIncome: boolean) => void;
+  onEditNotes: (id: string) => void;
+  onSaveNotes: (id: string, notes: string) => void;
+  onDelete: (groupId: string) => void;
+  onClose?: () => void;
+};
+
+export default function CategoryGroupMenu({
+  modalProps,
+  groupId,
+  onSave,
+  onAddCategory,
+  onEditNotes,
+  onDelete,
+  onClose,
+}: CategoryGroupMenuProps) {
+  const { grouped: categoryGroups } = useCategories();
+  const group = categoryGroups.find(g => g.id === groupId);
+  const data = useLiveQuery(
+    () => q('notes').filter({ id: group.id }).select('*'),
+    [group.id],
+  );
+  const notes = data && data.length > 0 ? data[0].note : null;
+
+  function _onClose() {
+    modalProps?.onClose();
+    onClose?.();
+  }
+
+  function _onRename(newName) {
+    if (newName !== group.name) {
+      onSave?.({
+        ...group,
+        name: newName,
+      });
+    }
+  }
+
+  function _onAddCategory() {
+    onAddCategory?.(group.id, group.is_income);
+  }
+
+  function _onEditNotes() {
+    onEditNotes?.(group.id);
+  }
+
+  function _onToggleVisibility() {
+    onSave?.({
+      ...group,
+      hidden: !!!group.hidden,
+    });
+    _onClose();
+  }
+
+  function _onDelete() {
+    onDelete?.(group.id);
+  }
+
+  function onNameUpdate(newName) {
+    _onRename(newName);
+  }
+
+  const buttonStyle: CSSProperties = {
+    ...styles.mediumText,
+    height: BUTTON_HEIGHT,
+    color: theme.formLabelText,
+    // Adjust based on desired number of buttons per row.
+    flexBasis: '48%',
+    marginLeft: '1%',
+    marginRight: '1%',
+  };
+
+  return (
+    <Modal
+      title={group.name}
+      showHeader
+      focusAfterClose={false}
+      {...modalProps}
+      onClose={_onClose}
+      padding={0}
+      style={{
+        flex: 1,
+        height: '45vh',
+        padding: '0 10px',
+        borderRadius: '6px',
+      }}
+      editableTitle={true}
+      titleStyle={styles.underlinedText}
+      onTitleUpdate={onNameUpdate}
+      leftHeaderContent={
+        <AdditionalCategoryGroupMenu
+          group={group}
+          onDelete={_onDelete}
+          onToggleVisibility={_onToggleVisibility}
+        />
+      }
+    >
+      {({ isEditingTitle }) => (
+        <View
+          style={{
+            flex: 1,
+            flexDirection: 'column',
+          }}
+        >
+          <View
+            style={{
+              overflowY: 'auto',
+              flex: 1,
+            }}
+          >
+            <Notes
+              notes={notes?.length > 0 ? notes : 'No notes'}
+              editable={false}
+              focused={false}
+              getStyle={editable => ({
+                ...styles.mediumText,
+                borderRadius: 6,
+                ...((!notes || notes.length === 0) && {
+                  justifySelf: 'center',
+                  alignSelf: 'center',
+                  color: theme.pageTextSubdued,
+                }),
+              })}
+            />
+          </View>
+          <View
+            style={{
+              flexDirection: 'row',
+              flexWrap: 'wrap',
+              justifyContent: 'space-between',
+              alignContent: 'space-between',
+              paddingTop: 10,
+              paddingBottom: 10,
+            }}
+          >
+            <Button
+              disabled={isEditingTitle}
+              style={{
+                ...buttonStyle,
+                display: isEditingTitle ? 'none' : undefined,
+              }}
+              onClick={_onAddCategory}
+            >
+              <Add width={17} height={17} style={{ paddingRight: 5 }} />
+              Add category
+            </Button>
+            <Button
+              style={{
+                ...buttonStyle,
+                display: isEditingTitle ? 'none' : undefined,
+              }}
+              onClick={_onEditNotes}
+            >
+              <NotesPaper width={20} height={20} style={{ paddingRight: 5 }} />
+              Edit notes
+            </Button>
+          </View>
+        </View>
+      )}
+    </Modal>
+  );
+}
+
+function AdditionalCategoryGroupMenu({ group, onDelete, onToggleVisibility }) {
+  const [menuOpen, setMenuOpen] = useState(false);
+  const itemStyle: CSSProperties = {
+    ...styles.mediumText,
+    height: BUTTON_HEIGHT,
+  };
+
+  return (
+    <View>
+      <Button
+        type="bare"
+        aria-label="Menu"
+        onClick={() => {
+          setMenuOpen(true);
+        }}
+      >
+        <DotsHorizontalTriple
+          width={17}
+          height={17}
+          style={{ color: 'currentColor' }}
+        />
+        {menuOpen && (
+          <Tooltip
+            position="bottom-left"
+            style={{ padding: 0 }}
+            onClose={() => {
+              setMenuOpen(false);
+            }}
+          >
+            <Menu
+              style={{
+                ...styles.mediumText,
+                color: theme.formLabelText,
+              }}
+              items={
+                [
+                  {
+                    name: 'toggleVisibility',
+                    text: group.hidden ? 'Show' : 'Hide',
+                    icon: group.hidden ? ViewShow : ViewHide,
+                    iconSize: 16,
+                    style: itemStyle,
+                  },
+                  ...(!group.is_income && [
+                    Menu.line,
+                    {
+                      name: 'delete',
+                      text: 'Delete',
+                      icon: Trash,
+                      iconSize: 15,
+                      style: itemStyle,
+                    },
+                  ]),
+                ].filter(i => i != null) as ComponentProps<typeof Menu>['items']
+              }
+              onMenuSelect={itemName => {
+                setMenuOpen(false);
+                if (itemName === 'delete') {
+                  onDelete();
+                } else if (itemName === 'toggleVisibility') {
+                  onToggleVisibility();
+                }
+              }}
+            />
+          </Tooltip>
+        )}
+      </Button>
+    </View>
+  );
+}
diff --git a/packages/desktop-client/src/components/modals/CategoryMenu.tsx b/packages/desktop-client/src/components/modals/CategoryMenu.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..5ad9f6fc0199ff9ce6f5274d5af64ca0fd53fc4b
--- /dev/null
+++ b/packages/desktop-client/src/components/modals/CategoryMenu.tsx
@@ -0,0 +1,230 @@
+import React, { useState } from 'react';
+
+import { useLiveQuery } from 'loot-core/src/client/query-hooks';
+import q from 'loot-core/src/shared/query';
+import { type CategoryEntity } from 'loot-core/src/types/models';
+
+import useCategories from '../../hooks/useCategories';
+import { DotsHorizontalTriple } from '../../icons/v1';
+import Trash from '../../icons/v1/Trash';
+import NotesPaper from '../../icons/v2/NotesPaper';
+import ViewHide from '../../icons/v2/ViewHide';
+import ViewShow from '../../icons/v2/ViewShow';
+import { type CSSProperties, styles, theme } from '../../style';
+import { type CommonModalProps } from '../../types/modals';
+import Button from '../common/Button';
+import Menu from '../common/Menu';
+import Modal from '../common/Modal';
+import View from '../common/View';
+import Notes from '../Notes';
+import { Tooltip } from '../tooltips';
+
+const BUTTON_HEIGHT = 40;
+
+type CategoryMenuProps = {
+  modalProps: CommonModalProps;
+  categoryId: string;
+  onSave: (category: CategoryEntity) => void;
+  onEditNotes: (id: string) => void;
+  onDelete: (categoryId: string) => void;
+  onClose?: () => void;
+};
+
+export default function CategoryMenu({
+  modalProps,
+  categoryId,
+  onSave,
+  onEditNotes,
+  onDelete,
+  onClose,
+}: CategoryMenuProps) {
+  const { list: categories } = useCategories();
+  const category = categories.find(c => c.id === categoryId);
+  const data = useLiveQuery(
+    () => q('notes').filter({ id: category.id }).select('*'),
+    [category.id],
+  );
+  const originalNotes = data && data.length > 0 ? data[0].note : null;
+
+  function _onClose() {
+    modalProps?.onClose();
+    onClose?.();
+  }
+
+  function _onRename(newName) {
+    if (newName !== category.name) {
+      onSave?.({
+        ...category,
+        name: newName,
+      });
+    }
+  }
+
+  function _onToggleVisibility() {
+    onSave?.({
+      ...category,
+      hidden: !category.hidden,
+    });
+    _onClose();
+  }
+
+  function _onEditNotes() {
+    onEditNotes?.(category.id);
+  }
+
+  function _onDelete() {
+    onDelete?.(category.id);
+  }
+
+  function onNameUpdate(newName) {
+    _onRename(newName);
+  }
+
+  const buttonStyle: CSSProperties = {
+    ...styles.mediumText,
+    height: BUTTON_HEIGHT,
+    color: theme.formLabelText,
+    // Adjust based on desired number of buttons per row.
+    flexBasis: '100%',
+  };
+
+  return (
+    <Modal
+      title={category.name}
+      titleStyle={styles.underlinedText}
+      showHeader
+      focusAfterClose={false}
+      {...modalProps}
+      onClose={_onClose}
+      padding={0}
+      style={{
+        flex: 1,
+        height: '45vh',
+        padding: '0 10px',
+        borderRadius: '6px',
+      }}
+      editableTitle={true}
+      onTitleUpdate={onNameUpdate}
+      leftHeaderContent={
+        <AdditionalCategoryMenu
+          category={category}
+          onDelete={_onDelete}
+          onToggleVisibility={_onToggleVisibility}
+        />
+      }
+    >
+      {({ isEditingTitle }) => (
+        <View
+          style={{
+            flex: 1,
+            flexDirection: 'column',
+          }}
+        >
+          <View
+            style={{
+              overflowY: 'auto',
+              flex: 1,
+            }}
+          >
+            <Notes
+              notes={originalNotes?.length > 0 ? originalNotes : 'No notes'}
+              editable={false}
+              focused={false}
+              getStyle={editable => ({
+                borderRadius: 6,
+                ...((!originalNotes || originalNotes.length === 0) && {
+                  justifySelf: 'center',
+                  alignSelf: 'center',
+                  color: theme.pageTextSubdued,
+                }),
+              })}
+            />
+          </View>
+          <View
+            style={{
+              flexDirection: 'row',
+              flexWrap: 'wrap',
+              justifyContent: 'space-between',
+              alignContent: 'space-between',
+              margin: '10px 0',
+            }}
+          >
+            <Button
+              style={{
+                ...buttonStyle,
+                display: isEditingTitle ? 'none' : undefined,
+              }}
+              onClick={_onEditNotes}
+            >
+              <NotesPaper width={20} height={20} style={{ paddingRight: 5 }} />
+              Edit notes
+            </Button>
+          </View>
+        </View>
+      )}
+    </Modal>
+  );
+}
+
+function AdditionalCategoryMenu({ category, onDelete, onToggleVisibility }) {
+  const [menuOpen, setMenuOpen] = useState(false);
+  const itemStyle: CSSProperties = {
+    ...styles.mediumText,
+    height: BUTTON_HEIGHT,
+  };
+
+  return (
+    <View>
+      <Button
+        type="bare"
+        aria-label="Menu"
+        onClick={() => {
+          setMenuOpen(true);
+        }}
+      >
+        <DotsHorizontalTriple
+          width={17}
+          height={17}
+          style={{ color: 'currentColor' }}
+        />
+        {menuOpen && (
+          <Tooltip
+            position="bottom-left"
+            style={{ padding: 0 }}
+            onClose={() => {
+              setMenuOpen(false);
+            }}
+          >
+            <Menu
+              items={[
+                {
+                  name: 'toggleVisibility',
+                  text: category.hidden ? 'Show' : 'Hide',
+                  icon: category.hidden ? ViewShow : ViewHide,
+                  iconSize: 16,
+                  style: itemStyle,
+                },
+                Menu.line,
+                {
+                  name: 'delete',
+                  text: 'Delete',
+                  icon: Trash,
+                  iconSize: 15,
+                  style: itemStyle,
+                },
+              ]}
+              onMenuSelect={itemName => {
+                setMenuOpen(false);
+                if (itemName === 'delete') {
+                  onDelete();
+                } else if (itemName === 'toggleVisibility') {
+                  onToggleVisibility();
+                }
+              }}
+            />
+          </Tooltip>
+        )}
+      </Button>
+    </View>
+  );
+}
diff --git a/packages/desktop-client/src/components/modals/EditRule.jsx b/packages/desktop-client/src/components/modals/EditRule.jsx
index 9893fa71f433c9d1c78caae3b850a3d6192ec688..ee0707bad80b4ee48827b25b7a44feb5c2851bb2 100644
--- a/packages/desktop-client/src/components/modals/EditRule.jsx
+++ b/packages/desktop-client/src/components/modals/EditRule.jsx
@@ -768,7 +768,7 @@ export default function EditRule({
       title="Rule"
       padding={0}
       {...modalProps}
-      style={{ ...modalProps.style, flex: 'inherit', maxWidth: '90%' }}
+      style={{ ...modalProps.style, flex: 'inherit' }}
     >
       {() => (
         <View
diff --git a/packages/desktop-client/src/components/modals/ManageRulesModal.tsx b/packages/desktop-client/src/components/modals/ManageRulesModal.tsx
index 59dccf84a5a0bfdae593c639cffa2cf87219b312..cea802135cb6c65d184be2c1f32dfe419a345a28 100644
--- a/packages/desktop-client/src/components/modals/ManageRulesModal.tsx
+++ b/packages/desktop-client/src/components/modals/ManageRulesModal.tsx
@@ -34,8 +34,6 @@ export default function ManageRulesModal({
       {...modalProps}
       style={{
         flex: 1,
-        maxWidth: '90%',
-        maxHeight: '90%',
       }}
     >
       {() => <ManageRules isModal payeeId={payeeId} setLoading={setLoading} />}
diff --git a/packages/desktop-client/src/components/modals/Notes.tsx b/packages/desktop-client/src/components/modals/Notes.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..3ae4cd95cd411288f1fa828c6067aa50b19be30e
--- /dev/null
+++ b/packages/desktop-client/src/components/modals/Notes.tsx
@@ -0,0 +1,99 @@
+import React, { useEffect, useState } from 'react';
+
+import { useLiveQuery } from 'loot-core/src/client/query-hooks';
+import q from 'loot-core/src/shared/query';
+
+import Check from '../../icons/v2/Check';
+import { type CommonModalProps } from '../../types/modals';
+import Button from '../common/Button';
+import Modal from '../common/Modal';
+import View from '../common/View';
+import NotesComponent from '../Notes';
+
+type NotesProps = {
+  modalProps: CommonModalProps;
+  id: string;
+  name: string;
+  onSave: (id: string, notes: string) => void;
+};
+
+export default function Notes({ modalProps, id, name, onSave }: NotesProps) {
+  const data = useLiveQuery(() => q('notes').filter({ id }).select('*'), [id]);
+  const originalNotes = data && data.length > 0 ? data[0].note : null;
+
+  const [notes, setNotes] = useState(originalNotes);
+  useEffect(() => setNotes(originalNotes), [originalNotes]);
+
+  function _onClose() {
+    modalProps?.onClose();
+  }
+
+  function _onSave() {
+    if (notes !== originalNotes) {
+      onSave?.(id, notes);
+    }
+
+    _onClose();
+  }
+
+  return (
+    <Modal
+      title={`Notes: ${name}`}
+      showHeader
+      focusAfterClose={false}
+      {...modalProps}
+      onClose={_onClose}
+      padding={0}
+      style={{
+        flex: 1,
+        height: '50vh',
+        padding: '0 10px',
+        borderRadius: '6px',
+      }}
+    >
+      {() => (
+        <View
+          style={{
+            flex: 1,
+            flexDirection: 'column',
+          }}
+        >
+          <NotesComponent
+            notes={notes}
+            editable={true}
+            focused={true}
+            getStyle={editable => ({
+              borderRadius: 6,
+              flex: 1,
+              minWidth: 0,
+            })}
+            onChange={setNotes}
+          />
+          <View
+            style={{
+              flexDirection: 'column',
+              alignItems: 'center',
+              justifyItems: 'center',
+              width: '100%',
+              paddingTop: 10,
+              paddingBottom: 10,
+            }}
+          >
+            <Button
+              type="primary"
+              style={{
+                fontSize: 17,
+                fontWeight: 400,
+                width: '100%',
+              }}
+              onClick={_onSave}
+            >
+              <Check width={17} height={17} style={{ paddingRight: 5 }} />
+              Save notes
+            </Button>
+          </View>
+        </View>
+      )}
+    </Modal>
+  );
+}
diff --git a/packages/desktop-client/src/components/modals/SingleInput.tsx b/packages/desktop-client/src/components/modals/SingleInput.tsx
index 675a44132609471819a67704d91f5e632bfcfca3..db2ed81a088aeea98b7b7679f01e0dc5fba02fca 100644
--- a/packages/desktop-client/src/components/modals/SingleInput.tsx
+++ b/packages/desktop-client/src/components/modals/SingleInput.tsx
@@ -1,5 +1,6 @@
 import React, { useState } from 'react';
 
+import { styles } from '../../style';
 import { type CommonModalProps } from '../../types/modals';
 import Button from '../common/Button';
 import FormError from '../common/FormError';
@@ -53,6 +54,7 @@ function SingleInput({
               <InitialFocus>
                 <Input
                   placeholder={inputPlaceholder}
+                  style={{ ...styles.mediumText }}
                   onUpdate={setValue}
                   onEnter={e => _onSubmit(e.currentTarget.value)}
                 />
@@ -68,11 +70,20 @@ function SingleInput({
           <View
             style={{
               flexDirection: 'row',
+              alignContent: 'center',
               justifyContent: 'center',
-              paddingBottom: 15,
             }}
           >
-            <Button onPointerUp={e => _onSubmit(value)}>{buttonText}</Button>
+            <Button
+              type="primary"
+              style={{
+                ...styles.mediumText,
+                flexBasis: '50%',
+              }}
+              onPointerUp={e => _onSubmit(value)}
+            >
+              {buttonText}
+            </Button>
           </View>
         </>
       )}
diff --git a/packages/desktop-client/src/components/reports/reports/CustomReport.jsx b/packages/desktop-client/src/components/reports/reports/CustomReport.jsx
index 4a5f019bcc85382e3cbc7f788e1a3dea23e72930..307b630033fd12f8f4c04c72a43f03cbdd387030 100644
--- a/packages/desktop-client/src/components/reports/reports/CustomReport.jsx
+++ b/packages/desktop-client/src/components/reports/reports/CustomReport.jsx
@@ -342,7 +342,7 @@ export default function CustomReport() {
                     months={months}
                   />
                 ) : (
-                  <LoadingIndicator message={'Loading report...'} />
+                  <LoadingIndicator message="Loading report..." />
                 )}
               </View>
               {(viewLegend || viewSummary) && data && (
diff --git a/packages/desktop-client/src/components/reports/reports/CustomReportCard.jsx b/packages/desktop-client/src/components/reports/reports/CustomReportCard.jsx
index 77f5eccdafcc8c324dd9faa456e49d84f2a29b64..ba99695416da4f561cd57907c7bb7a1c2cc17190 100644
--- a/packages/desktop-client/src/components/reports/reports/CustomReportCard.jsx
+++ b/packages/desktop-client/src/components/reports/reports/CustomReportCard.jsx
@@ -52,7 +52,7 @@ function CustomReportCard() {
           data={data}
           compact={true}
           groupBy={groupBy}
-          balanceTypeOp={'totalDebts'}
+          balanceTypeOp="totalDebts"
           style={{ height: 'auto', flex: 1 }}
         />
       ) : (
diff --git a/packages/desktop-client/src/components/util/GenericInput.jsx b/packages/desktop-client/src/components/util/GenericInput.jsx
index e62807b60e2728d1584eea60d0d8648cfd2ebe71..dfa2d95ddcb5a3eeef16560f298142c9b3cffabd 100644
--- a/packages/desktop-client/src/components/util/GenericInput.jsx
+++ b/packages/desktop-client/src/components/util/GenericInput.jsx
@@ -136,7 +136,7 @@ export default function GenericInput({
             <Input
               inputRef={inputRef}
               defaultValue={value || ''}
-              placeholder={'yyyy'}
+              placeholder="yyyy"
               onEnter={e => onChange(e.target.value)}
               onBlur={e => onChange(e.target.value)}
             />
diff --git a/packages/loot-core/src/client/actions/modals.ts b/packages/loot-core/src/client/actions/modals.ts
index 7abf5e78a0eda269080c9753db37305e5d7c4245..879092f454e8bba369126d55aa2eb07bcfd02a7d 100644
--- a/packages/loot-core/src/client/actions/modals.ts
+++ b/packages/loot-core/src/client/actions/modals.ts
@@ -48,3 +48,7 @@ export function popModal(): PopModalAction {
 export function closeModal(): CloseModalAction {
   return { type: constants.CLOSE_MODAL };
 }
+
+export function collapseModals(rootModalName: string) {
+  return { type: constants.COLLAPSE_MODALS, rootModalName };
+}
diff --git a/packages/loot-core/src/client/constants.ts b/packages/loot-core/src/client/constants.ts
index 95a7e8fa9dc9814e38057813d8e60f070ff12d23..c107dea1c823b106d38e4b92193624e126c7e389 100644
--- a/packages/loot-core/src/client/constants.ts
+++ b/packages/loot-core/src/client/constants.ts
@@ -17,6 +17,7 @@ export const SET_APP_STATE = 'SET_APP_STATE';
 export const PUSH_MODAL = 'PUSH_MODAL';
 export const REPLACE_MODAL = 'REPLACE_MODAL';
 export const CLOSE_MODAL = 'CLOSE_MODAL';
+export const COLLAPSE_MODALS = 'COLLAPSE_MODALS';
 export const POP_MODAL = 'POP_MODAL';
 export const ADD_NOTIFICATION = 'ADD_NOTIFICATION';
 export const REMOVE_NOTIFICATION = 'REMOVE_NOTIFICATION';
diff --git a/packages/loot-core/src/client/reducers/modals.ts b/packages/loot-core/src/client/reducers/modals.ts
index eeb743869d6fc2b2782db03f02315424440bc259..57d32784bce12ac3e474de3b456a163375cb6b35 100644
--- a/packages/loot-core/src/client/reducers/modals.ts
+++ b/packages/loot-core/src/client/reducers/modals.ts
@@ -22,7 +22,18 @@ function update(state = initialState, action: Action): ModalsState {
     case constants.POP_MODAL:
       return { ...state, modalStack: state.modalStack.slice(0, -1) };
     case constants.CLOSE_MODAL:
-      return { ...state, modalStack: [] };
+      return {
+        ...state,
+        modalStack: [],
+      };
+    case constants.COLLAPSE_MODALS:
+      const idx = state.modalStack.findIndex(
+        m => m.name === action.rootModalName,
+      );
+      return {
+        ...state,
+        modalStack: idx < 0 ? state.modalStack : state.modalStack.slice(0, idx),
+      };
     case constants.SET_APP_STATE:
       if ('loadingText' in action.state) {
         return {
diff --git a/packages/loot-core/src/client/state-types/modals.d.ts b/packages/loot-core/src/client/state-types/modals.d.ts
index a5b0396c404d4f81fa16f1b5d69448989431571f..36771e2e9844c606ffe41681fe9f0ee54466c289 100644
--- a/packages/loot-core/src/client/state-types/modals.d.ts
+++ b/packages/loot-core/src/client/state-types/modals.d.ts
@@ -1,5 +1,10 @@
 import { type File } from '../../types/file';
-import type { AccountEntity, GoCardlessToken } from '../../types/models';
+import type {
+  AccountEntity,
+  CategoryEntity,
+  CategoryGroupEntity,
+  GoCardlessToken,
+} from '../../types/models';
 import type { RuleEntity } from '../../types/models/rule';
 import type { EmptyObject, StripNever } from '../../types/util';
 import type * as constants from '../constants';
@@ -105,6 +110,27 @@ type FinanceModals = {
 
   'schedule-posts-offline-notification': null;
   'switch-budget-type': { onSwitch: () => void };
+  'category-menu': {
+    category: CategoryEntity;
+    onSave: (category: CategoryEntity) => void;
+    onEditNotes: (id: string) => void;
+    onSaveNotes: (id: string, notes: string) => void;
+    onDelete: (categoryId: string) => void;
+    onClose?: () => void;
+  };
+  'category-group-menu': {
+    group: CategoryGroupEntity;
+    onSave: (group: CategoryGroupEntity) => void;
+    onAddCategory: (groupId: string, isIncome: boolean) => void;
+    onEditNotes: (id: string) => void;
+    onDelete: (groupId: string) => void;
+    onClose?: () => void;
+  };
+  notes: {
+    id: string;
+    name: string;
+    onSave: (id: string, notes: string) => void;
+  };
 };
 
 export type PushModalAction = {
@@ -125,11 +151,17 @@ export type CloseModalAction = {
   type: typeof constants.CLOSE_MODAL;
 };
 
+export type CollapseModalsAction = {
+  type: typeof constants.COLLAPSE_MODALS;
+  rootModalName: string;
+};
+
 export type ModalsActions =
   | PushModalAction
   | ReplaceModalAction
   | PopModalAction
-  | CloseModalAction;
+  | CloseModalAction
+  | CollapseModalsAction;
 
 export type ModalsState = {
   modalStack: Modal[];
diff --git a/upcoming-release-notes/1964.md b/upcoming-release-notes/1964.md
new file mode 100644
index 0000000000000000000000000000000000000000..16d45249fb9a9d64a6f08d249495f5b8e87f8817
--- /dev/null
+++ b/upcoming-release-notes/1964.md
@@ -0,0 +1,6 @@
+---
+category: Enhancements
+authors: [joel-jeremy]
+---
+
+Category and group menu/modal in the mobile budget page to manage categories/groups and their notes.