diff --git a/resources/js/wysiwyg/nodes/custom-list-item.ts b/resources/js/wysiwyg/nodes/custom-list-item.ts
index 2b4d74146..659a55a15 100644
--- a/resources/js/wysiwyg/nodes/custom-list-item.ts
+++ b/resources/js/wysiwyg/nodes/custom-list-item.ts
@@ -3,6 +3,7 @@ import {EditorConfig} from "lexical/LexicalEditor";
 import {DOMExportOutput, LexicalEditor, LexicalNode} from "lexical";
 
 import {el} from "../utils/dom";
+import {$isCustomListNode} from "./custom-list";
 
 function updateListItemChecked(
     dom: HTMLElement,
@@ -38,6 +39,10 @@ export class CustomListItemNode extends ListItemNode {
 
         element.value = this.__value;
 
+        if ($hasNestedListWithoutLabel(this)) {
+            element.style.listStyle = 'none';
+        }
+
         return element;
     }
 
@@ -86,8 +91,28 @@ export class CustomListItemNode extends ListItemNode {
     }
 }
 
+function $hasNestedListWithoutLabel(node: CustomListItemNode): boolean {
+    const children = node.getChildren();
+    let hasLabel = false;
+    let hasNestedList = false;
+
+    for (const child of children) {
+        if ($isCustomListNode(child)) {
+            hasNestedList = true;
+        } else if (child.getTextContent().trim().length > 0) {
+            hasLabel = true;
+        }
+    }
+
+    return hasNestedList && !hasLabel;
+}
+
 export function $isCustomListItemNode(
     node: LexicalNode | null | undefined,
 ): node is CustomListItemNode {
     return node instanceof CustomListItemNode;
+}
+
+export function $createCustomListItemNode(): CustomListItemNode {
+    return new CustomListItemNode();
 }
\ No newline at end of file
diff --git a/resources/js/wysiwyg/nodes/custom-list.ts b/resources/js/wysiwyg/nodes/custom-list.ts
index 953bcb8cd..a6c473999 100644
--- a/resources/js/wysiwyg/nodes/custom-list.ts
+++ b/resources/js/wysiwyg/nodes/custom-list.ts
@@ -5,7 +5,8 @@ import {
     Spread
 } from "lexical";
 import {EditorConfig} from "lexical/LexicalEditor";
-import {ListNode, ListType, SerializedListNode} from "@lexical/list";
+import {$isListItemNode, ListItemNode, ListNode, ListType, SerializedListNode} from "@lexical/list";
+import {$createCustomListItemNode} from "./custom-list-item";
 
 
 export type SerializedCustomListNode = Spread<{
@@ -30,7 +31,7 @@ export class CustomListNode extends ListNode {
     }
 
     static clone(node: CustomListNode) {
-        const newNode = new CustomListNode(node.__listType, 0, node.__key);
+        const newNode = new CustomListNode(node.__listType, node.__start, node.__key);
         newNode.__id = node.__id;
         return newNode;
     }
@@ -67,6 +68,11 @@ export class CustomListNode extends ListNode {
             if (element.id && baseResult?.node) {
                 (baseResult.node as CustomListNode).setId(element.id);
             }
+
+            if (baseResult) {
+                baseResult.after = $normalizeChildren;
+            }
+
             return baseResult;
         };
 
@@ -83,8 +89,34 @@ export class CustomListNode extends ListNode {
     }
 }
 
+/*
+ * This function is a custom normalization function to allow nested lists within list item elements.
+ * Original taken from https://github.com/facebook/lexical/blob/6e10210fd1e113ccfafdc999b1d896733c5c5bea/packages/lexical-list/src/LexicalListNode.ts#L284-L303
+ * With modifications made.
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ * MIT license
+ */
+function $normalizeChildren(nodes: Array<LexicalNode>): Array<ListItemNode> {
+    const normalizedListItems: Array<ListItemNode> = [];
+
+    for (const node of nodes) {
+        if ($isListItemNode(node)) {
+            normalizedListItems.push(node);
+        } else {
+            normalizedListItems.push($wrapInListItem(node));
+        }
+    }
+
+    return normalizedListItems;
+}
+
+function $wrapInListItem(node: LexicalNode): ListItemNode {
+    const listItemWrapper = $createCustomListItemNode();
+    return listItemWrapper.append(node);
+}
+
 export function $createCustomListNode(type: ListType): CustomListNode {
-    return new CustomListNode(type, 0);
+    return new CustomListNode(type, 1);
 }
 
 export function $isCustomListNode(node: LexicalNode | null | undefined): node is CustomListNode {
diff --git a/resources/js/wysiwyg/services/keyboard-handling.ts b/resources/js/wysiwyg/services/keyboard-handling.ts
index 65a8e4254..791fb0bed 100644
--- a/resources/js/wysiwyg/services/keyboard-handling.ts
+++ b/resources/js/wysiwyg/services/keyboard-handling.ts
@@ -1,10 +1,11 @@
 import {EditorUiContext} from "../ui/framework/core";
 import {
+    $getSelection,
     $isDecoratorNode,
     COMMAND_PRIORITY_LOW,
     KEY_BACKSPACE_COMMAND,
     KEY_DELETE_COMMAND,
-    KEY_ENTER_COMMAND,
+    KEY_ENTER_COMMAND, KEY_TAB_COMMAND,
     LexicalEditor,
     LexicalNode
 } from "lexical";
@@ -13,6 +14,8 @@ import {$isMediaNode} from "../nodes/media";
 import {getLastSelection} from "../utils/selection";
 import {$getNearestNodeBlockParent} from "../utils/nodes";
 import {$createCustomParagraphNode} from "../nodes/custom-paragraph";
+import {$isCustomListItemNode} from "../nodes/custom-list-item";
+import {$setInsetForSelection} from "../utils/lists";
 
 function isSingleSelectedNode(nodes: LexicalNode[]): boolean {
     if (nodes.length === 1) {
@@ -55,6 +58,17 @@ function insertAfterSingleSelectedNode(editor: LexicalEditor, event: KeyboardEve
     return false;
 }
 
+function handleInsetOnTab(editor: LexicalEditor, event: KeyboardEvent|null) {
+    const change = event?.shiftKey ? -40 : 40;
+    editor.update(() => {
+        const selection = $getSelection();
+        const nodes = selection?.getNodes() || [];
+        if (nodes.length > 1 || (nodes.length === 1 && $isCustomListItemNode(nodes[0].getParent()))) {
+            $setInsetForSelection(editor, change);
+        }
+    });
+}
+
 export function registerKeyboardHandling(context: EditorUiContext): () => void {
     const unregisterBackspace = context.editor.registerCommand(KEY_BACKSPACE_COMMAND, (): boolean => {
         deleteSingleSelectedNode(context.editor);
@@ -70,9 +84,14 @@ export function registerKeyboardHandling(context: EditorUiContext): () => void {
         return insertAfterSingleSelectedNode(context.editor, event);
     }, COMMAND_PRIORITY_LOW);
 
+    const unregisterTab = context.editor.registerCommand(KEY_TAB_COMMAND, (event): boolean => {
+        return handleInsetOnTab(context.editor, event);
+    }, COMMAND_PRIORITY_LOW);
+
     return () => {
         unregisterBackspace();
         unregisterDelete();
         unregisterEnter();
+        unregisterTab();
     };
 }
\ No newline at end of file
diff --git a/resources/js/wysiwyg/todo.md b/resources/js/wysiwyg/todo.md
index 34367a36b..2662350af 100644
--- a/resources/js/wysiwyg/todo.md
+++ b/resources/js/wysiwyg/todo.md
@@ -6,8 +6,8 @@
 
 ## Main Todo
 
-- Align list nesting with old editor
 - Mac: Shortcut support via command.
+- RTL/LTR support
 
 ## Secondary Todo
 
@@ -18,4 +18,5 @@
 
 ## Bugs
 
-//
\ No newline at end of file
+- Focus/click area reduced to content area, single line on initial access
+- List selection can get lost on nesting/unnesting
\ No newline at end of file
diff --git a/resources/js/wysiwyg/ui/defaults/buttons/lists.ts b/resources/js/wysiwyg/ui/defaults/buttons/lists.ts
index 0857fb70a..87630eb27 100644
--- a/resources/js/wysiwyg/ui/defaults/buttons/lists.ts
+++ b/resources/js/wysiwyg/ui/defaults/buttons/lists.ts
@@ -13,12 +13,14 @@ import indentIncreaseIcon from "@icons/editor/indent-increase.svg";
 import indentDecreaseIcon from "@icons/editor/indent-decrease.svg";
 import {
     $getBlockElementNodesInSelection,
-    $selectionContainsNodeType,
+    $selectionContainsNodeType, $selectNodes, $selectSingleNode,
     $toggleSelection,
     getLastSelection
 } from "../../../utils/selection";
 import {toggleSelectionAsList} from "../../../utils/formats";
 import {nodeHasInset} from "../../../utils/nodes";
+import {$isCustomListItemNode, CustomListItemNode} from "../../../nodes/custom-list-item";
+import {$nestListItem, $setInsetForSelection, $unnestListItem} from "../../../utils/lists";
 
 
 function buildListButton(label: string, type: ListType, icon: string): EditorButtonDefinition {
@@ -40,28 +42,12 @@ export const bulletList: EditorButtonDefinition = buildListButton('Bullet list',
 export const numberList: EditorButtonDefinition = buildListButton('Numbered list', 'number', listNumberedIcon);
 export const taskList: EditorButtonDefinition = buildListButton('Task list', 'check', listCheckIcon);
 
-
-function setInsetForSelection(editor: LexicalEditor, change: number): void {
-    const selection = getLastSelection(editor);
-
-    const elements = $getBlockElementNodesInSelection(selection);
-    for (const node of elements) {
-        if (nodeHasInset(node)) {
-            const currentInset = node.getInset();
-            const newInset = Math.min(Math.max(currentInset + change, 0), 500);
-            node.setInset(newInset)
-        }
-    }
-
-    $toggleSelection(editor);
-}
-
 export const indentIncrease: EditorButtonDefinition = {
     label: 'Increase indent',
     icon: indentIncreaseIcon,
     action(context: EditorUiContext) {
         context.editor.update(() => {
-            setInsetForSelection(context.editor, 40);
+            $setInsetForSelection(context.editor, 40);
         });
     },
     isActive() {
@@ -74,7 +60,7 @@ export const indentDecrease: EditorButtonDefinition = {
     icon: indentDecreaseIcon,
     action(context: EditorUiContext) {
         context.editor.update(() => {
-            setInsetForSelection(context.editor, -40);
+            $setInsetForSelection(context.editor, -40);
         });
     },
     isActive() {
diff --git a/resources/js/wysiwyg/utils/lists.ts b/resources/js/wysiwyg/utils/lists.ts
new file mode 100644
index 000000000..edde994e5
--- /dev/null
+++ b/resources/js/wysiwyg/utils/lists.ts
@@ -0,0 +1,123 @@
+import {$createCustomListItemNode, $isCustomListItemNode, CustomListItemNode} from "../nodes/custom-list-item";
+import {$createCustomListNode, $isCustomListNode} from "../nodes/custom-list";
+import {BaseSelection, LexicalEditor} from "lexical";
+import {$getBlockElementNodesInSelection, $selectNodes, $toggleSelection, getLastSelection} from "./selection";
+import {nodeHasInset} from "./nodes";
+
+
+export function $nestListItem(node: CustomListItemNode) {
+    const list = node.getParent();
+    if (!$isCustomListNode(list)) {
+        return;
+    }
+
+    const listItems = list.getChildren() as CustomListItemNode[];
+    const nodeIndex = listItems.findIndex((n) => n.getKey() === node.getKey());
+    const isFirst = nodeIndex === 0;
+
+    const newListItem = $createCustomListItemNode();
+    const newList = $createCustomListNode(list.getListType());
+    newList.append(newListItem);
+    newListItem.append(...node.getChildren());
+
+    if (isFirst) {
+        node.append(newList);
+    } else  {
+        const prevListItem = listItems[nodeIndex - 1];
+        prevListItem.append(newList);
+        node.remove();
+    }
+}
+
+export function $unnestListItem(node: CustomListItemNode) {
+    const list = node.getParent();
+    const parentListItem = list?.getParent();
+    const outerList = parentListItem?.getParent();
+    if (!$isCustomListNode(list) || !$isCustomListNode(outerList) || !$isCustomListItemNode(parentListItem)) {
+        return;
+    }
+
+    parentListItem.insertAfter(node);
+    if (list.getChildren().length === 0) {
+        list.remove();
+    }
+
+    if (parentListItem.getChildren().length === 0) {
+        parentListItem.remove();
+    }
+}
+
+function getListItemsForSelection(selection: BaseSelection|null): (CustomListItemNode|null)[] {
+    const nodes = selection?.getNodes() || [];
+    const listItemNodes = [];
+
+    outer: for (const node of nodes) {
+        if ($isCustomListItemNode(node)) {
+            listItemNodes.push(node);
+            continue;
+        }
+
+        const parents = node.getParents();
+        for (const parent of parents) {
+            if ($isCustomListItemNode(parent)) {
+                listItemNodes.push(parent);
+                continue outer;
+            }
+        }
+
+        listItemNodes.push(null);
+    }
+
+    return listItemNodes;
+}
+
+function $reduceDedupeListItems(listItems: (CustomListItemNode|null)[]): CustomListItemNode[] {
+    const listItemMap: Record<string, CustomListItemNode> = {};
+
+    for (const item of listItems) {
+        if (item === null) {
+            continue;
+        }
+
+        const key = item.getKey();
+        if (typeof listItemMap[key] === 'undefined') {
+            listItemMap[key] = item;
+        }
+    }
+
+    return Object.values(listItemMap);
+}
+
+export function $setInsetForSelection(editor: LexicalEditor, change: number): void {
+    const selection = getLastSelection(editor);
+
+    const listItemsInSelection = getListItemsForSelection(selection);
+    const isListSelection = listItemsInSelection.length > 0 && !listItemsInSelection.includes(null);
+
+    if (isListSelection) {
+        const listItems = $reduceDedupeListItems(listItemsInSelection);
+        if (change > 0) {
+            for (const listItem of listItems) {
+                $nestListItem(listItem);
+            }
+        } else if (change < 0) {
+            for (const listItem of [...listItems].reverse()) {
+                $unnestListItem(listItem);
+            }
+        }
+
+        $selectNodes(listItems);
+        return;
+    }
+
+    const elements = $getBlockElementNodesInSelection(selection);
+    for (const node of elements) {
+        if (nodeHasInset(node)) {
+            const currentInset = node.getInset();
+            const newInset = Math.min(Math.max(currentInset + change, 0), 500);
+            node.setInset(newInset)
+        }
+    }
+
+    $toggleSelection(editor);
+}
\ No newline at end of file
diff --git a/resources/js/wysiwyg/utils/selection.ts b/resources/js/wysiwyg/utils/selection.ts
index 4aa21045f..2110ea4be 100644
--- a/resources/js/wysiwyg/utils/selection.ts
+++ b/resources/js/wysiwyg/utils/selection.ts
@@ -10,7 +10,7 @@ import {
     ElementFormatType,
     ElementNode, LexicalEditor,
     LexicalNode,
-    TextFormatType
+    TextFormatType, TextNode
 } from "lexical";
 import {$findMatchingParent, $getNearestBlockElementAncestorOrThrow} from "@lexical/utils";
 import {LexicalElementNodeCreator, LexicalNodeMatcher} from "../nodes";
@@ -106,6 +106,57 @@ export function $selectSingleNode(node: LexicalNode) {
     $setSelection(nodeSelection);
 }
 
+function getFirstTextNodeInNodes(nodes: LexicalNode[]): TextNode|null {
+    for (const node of nodes) {
+        if ($isTextNode(node)) {
+            return node;
+        }
+
+        if ($isElementNode(node)) {
+            const children = node.getChildren();
+            const textNode = getFirstTextNodeInNodes(children);
+            if (textNode !== null) {
+                return textNode;
+            }
+        }
+    }
+
+    return null;
+}
+
+function getLastTextNodeInNodes(nodes: LexicalNode[]): TextNode|null {
+    const revNodes = [...nodes].reverse();
+    for (const node of revNodes) {
+        if ($isTextNode(node)) {
+            return node;
+        }
+
+        if ($isElementNode(node)) {
+            const children = [...node.getChildren()].reverse();
+            const textNode = getLastTextNodeInNodes(children);
+            if (textNode !== null) {
+                return textNode;
+            }
+        }
+    }
+
+    return null;
+}
+
+export function $selectNodes(nodes: LexicalNode[]) {
+    if (nodes.length === 0) {
+        return;
+    }
+
+    const selection = $createRangeSelection();
+    const firstText = getFirstTextNodeInNodes(nodes);
+    const lastText = getLastTextNodeInNodes(nodes);
+    if (firstText && lastText) {
+        selection.setTextNodeRange(firstText, 0, lastText, lastText.getTextContentSize() || 0)
+        $setSelection(selection);
+    }
+}
+
 export function $toggleSelection(editor: LexicalEditor) {
     const lastSelection = getLastSelection(editor);