diff --git a/packages/codemirror/src/extensions/custom_tab.ts b/packages/codemirror/src/extensions/custom_tab.ts
index 9ba602010..2b93d7678 100644
--- a/packages/codemirror/src/extensions/custom_tab.ts
+++ b/packages/codemirror/src/extensions/custom_tab.ts
@@ -2,20 +2,41 @@ import { indentLess, indentMore } from "@codemirror/commands";
 import { EditorSelection, type ChangeSpec } from "@codemirror/state";
 import type { KeyBinding } from "@codemirror/view";
 
+/**
+ * Custom key binding for indentation:
+ *
+ * - Tab while at the beginning of a line will indent the line.
+ * - Tab while not at the beginning of a line will insert a tab character.
+ * - Tab while not at the beginning of a line while text is selected will replace the txt with a tab character.
+ * - Shift+Tab will always unindent.
+ */
 const smartIndentWithTab: KeyBinding[] = [
     {
         key: "Tab",
         run({ state, dispatch }) {
             const { selection } = state;
-
-            // Handle selection indenting normally
-            if (selection.ranges.some(range => !range.empty)) {
-                return indentMore({ state, dispatch });
-            }
-
             const changes = [];
             const newSelections = [];
 
+            // Step 1: Handle non-empty selections → replace with tab
+            if (selection.ranges.some(range => !range.empty)) {
+                for (let range of selection.ranges) {
+                    changes.push({ from: range.from, to: range.to, insert: "\t" });
+                    newSelections.push(EditorSelection.cursor(range.from + 1));
+                }
+
+                dispatch(
+                    state.update({
+                        changes,
+                        selection: EditorSelection.create(newSelections),
+                        scrollIntoView: true,
+                        userEvent: "input"
+                    })
+                );
+                return true;
+            }
+
+            // Step 2: Handle empty selections
             for (let range of selection.ranges) {
                 const line = state.doc.lineAt(range.head);
                 const beforeCursor = state.doc.sliceString(line.from, range.head);
@@ -24,7 +45,7 @@ const smartIndentWithTab: KeyBinding[] = [
                     // Only whitespace before cursor → indent line
                     return indentMore({ state, dispatch });
                 } else {
-                    // Insert a tab character at cursor
+                    // Insert tab character at cursor
                     changes.push({ from: range.head, to: range.head, insert: "\t" });
                     newSelections.push(EditorSelection.cursor(range.head + 1));
                 }