diff --git a/ui/package-lock.json b/ui/package-lock.json
index 4cb74e29b8cd224d9a70949af33dbc9b68dd9b46..470921a86e7c92063df31669810c0324dc8c1b8c 100644
--- a/ui/package-lock.json
+++ b/ui/package-lock.json
@@ -23,8 +23,6 @@
         "cypress-axe": "^1.5.0",
         "cypress-real-events": "^1.13.0",
         "dotenv": "^16.4.5",
-        "lodash": "^4.17.21",
-        "lodash-es": "^4.17.21",
         "react": "^18.2.0",
         "react-dom": "^18.2.0",
         "react-error-boundary": "^4.0.13",
@@ -51,7 +49,6 @@
         "@testing-library/jest-dom": "^6.2.0",
         "@testing-library/react": "^14.1.2",
         "@testing-library/user-event": "^14.5.2",
-        "@types/lodash": "^4.17.0",
         "@types/react": "^18.2.47",
         "@types/react-dom": "^18.2.18",
         "@types/sortablejs": "^1.15.8",
@@ -5520,13 +5517,6 @@
       "dev": true,
       "license": "MIT"
     },
-    "node_modules/@types/lodash": {
-      "version": "4.17.13",
-      "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.13.tgz",
-      "integrity": "sha512-lfx+dftrEZcdBPczf9d0Qv0x+j/rfNCMuC6OcfXmO8gkfeNAY88PgKUbvG56whcN23gc27yenwF6oJZXGFpYxg==",
-      "dev": true,
-      "license": "MIT"
-    },
     "node_modules/@types/mdx": {
       "version": "2.0.13",
       "resolved": "https://registry.npmjs.org/@types/mdx/-/mdx-2.0.13.tgz",
@@ -11199,12 +11189,6 @@
       "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
       "license": "MIT"
     },
-    "node_modules/lodash-es": {
-      "version": "4.17.21",
-      "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz",
-      "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==",
-      "license": "MIT"
-    },
     "node_modules/lodash.debounce": {
       "version": "4.0.8",
       "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz",
diff --git a/ui/package.json b/ui/package.json
index cbbe1f64c67e4d6090b9c9769f4a1668f6fd6c90..2067a03e07a38a5a03eb8537f527b53cbf9c1c56 100644
--- a/ui/package.json
+++ b/ui/package.json
@@ -45,8 +45,6 @@
     "cypress-axe": "^1.5.0",
     "cypress-real-events": "^1.13.0",
     "dotenv": "^16.4.5",
-    "lodash": "^4.17.21",
-    "lodash-es": "^4.17.21",
     "react": "^18.2.0",
     "react-dom": "^18.2.0",
     "react-error-boundary": "^4.0.13",
@@ -73,7 +71,6 @@
     "@testing-library/jest-dom": "^6.2.0",
     "@testing-library/react": "^14.1.2",
     "@testing-library/user-event": "^14.5.2",
-    "@types/lodash": "^4.17.0",
     "@types/react": "^18.2.47",
     "@types/react-dom": "^18.2.18",
     "@types/sortablejs": "^1.15.8",
diff --git a/ui/src/components/form/inputBehaviors.tsx b/ui/src/components/form/inputBehaviors.tsx
index edb177863d1491ed6c9738c69cc86391afc557fe..7375e29f4d82d13ad084b2c49bcf51601d76bcf2 100644
--- a/ui/src/components/form/inputBehaviors.tsx
+++ b/ui/src/components/form/inputBehaviors.tsx
@@ -1,5 +1,30 @@
 import { useState, useContext, useEffect, useCallback } from 'react';
-import type * as React from 'react';
+import * as React from 'react';
+function useDebounce<F extends (...args: any[]) => void>(func: F, delay: number) {
+  const timeoutRef = React.useRef<NodeJS.Timeout | null>(null);
+  const savedFunc = React.useRef(func);
+  useEffect(() => {
+    savedFunc.current = func;
+  }, [func]);
+
+  // Debounced function
+  const debouncedFunction = useCallback((...args: Parameters<F>) => {
+    if (timeoutRef.current) {
+      clearTimeout(timeoutRef.current);
+    }
+    timeoutRef.current = setTimeout(() => savedFunc.current(...args), delay);
+  }, [delay]);
+  const cancel = useCallback(() => {
+    if (timeoutRef.current) {
+      clearTimeout(timeoutRef.current);
+    }
+  }, []);
+  useEffect(() => {
+    return cancel;
+  }, [cancel]);
+
+  return { debouncedFunction, cancel };
+}
 import { FormDispatchContext } from './components/drupal/DrupalForm';
 import { selectSelectedComponent } from '@/features/ui/uiSlice';
 import { useAppDispatch, useAppSelector } from '@/app/hooks';
@@ -8,7 +33,6 @@ import {
   selectModel,
   updateNodeModelForce,
 } from '@/features/layout/layoutModelSlice';
-import { debounce } from 'lodash';
 import { useGetComponentsQuery } from '@/services/components';
 import { findNodeByUuid } from '@/features/layout/layoutUtils';
 import './InputBehaviors.css';
@@ -18,7 +42,6 @@ import {
   inputBehaviorOnChange,
   inputBehaviorOnBlur,
 } from '@/components/form/inputBehaviorsEventCallbacks';
-
 // Wraps all form elements to provide common functionality and subscribe to the
 // parent form's context.
 const InputBehaviors = (OriginalInput: React.FC) => {
@@ -100,8 +123,7 @@ const InputBehaviors = (OriginalInput: React.FC) => {
     }, []);
 
     // Use debounce to prevent excessive repaints of the layout.
-    const debounceStoreUpdate = debounce(formStateToStore, 400);
-
+    const { debouncedFunction: debounceStoreUpdate } = useDebounce(formStateToStore, 400);
     // Register the debounced store function as a callback so debouncing is
     // preserved between renders.
     const storeUpdateCallback = useCallback(
diff --git a/ui/src/components/list/ComponentList.tsx b/ui/src/components/list/ComponentList.tsx
index eb40cff78bc0be708d2a9f35677f36e752649621..849ef51665db1192ff4ca1ac007dc22a04b63127 100644
--- a/ui/src/components/list/ComponentList.tsx
+++ b/ui/src/components/list/ComponentList.tsx
@@ -2,7 +2,7 @@ import { useEffect } from 'react';
 import { useErrorBoundary } from 'react-error-boundary';
 import { useGetComponentsQuery } from '@/services/components';
 import List from '@/components/list/List';
-import { toArray } from 'lodash';
+const toArray = (obj: any) => Object.values(obj);
 
 const ComponentList = () => {
   const { data: components, error, isLoading } = useGetComponentsQuery();
@@ -15,7 +15,7 @@ const ComponentList = () => {
   }, [error, showBoundary]);
 
   const sortedComponents = components
-    ? toArray(components).sort((a, b) => a.name.localeCompare(b.name))
+    ? Object.values(components).sort((a, b) => a.name.localeCompare(b.name))
     : {};
 
   return (
diff --git a/ui/src/features/layout/layoutModelSlice.ts b/ui/src/features/layout/layoutModelSlice.ts
index f9b2a93c33d2468d8335f408bf4496961c99a38b..932d77b1e00a0154a3a9a2439c1e2aba564797f3 100644
--- a/ui/src/features/layout/layoutModelSlice.ts
+++ b/ui/src/features/layout/layoutModelSlice.ts
@@ -1,11 +1,9 @@
-// cspell:ignore uuidv
 import type { AppDispatch } from '@/app/store';
 import { setSelectedComponent } from '@/features/ui/uiSlice';
 import type { Component } from '@/types/Component';
 import type { UUID } from '@/types/UUID';
 import type { PayloadAction } from '@reduxjs/toolkit';
 import { createSlice } from '@reduxjs/toolkit';
-import _ from 'lodash';
 import type { StateWithHistory } from 'redux-undo';
 import { v4 as uuidv4 } from 'uuid';
 import {
@@ -17,6 +15,11 @@ import {
   removeNodeByUuid,
   replaceUUIDsAndUpdateModel,
 } from './layoutUtils';
+export interface ComponentModel {
+  [key: string]: string | boolean | number | object | any[];
+}
+
+export type ComponentModels = Record<string, ComponentModel>;
 
 export interface RootNode {
   name?: string;
@@ -35,7 +38,6 @@ export interface Node {
 }
 
 export type LayoutNode = RootNode | Node;
-
 export interface RootLayoutModel {
   layout: RootNode;
   model: ComponentModels;
@@ -45,8 +47,6 @@ export interface LayoutModelSliceState extends RootLayoutModel {
   initialized: boolean;
 }
 
-export type ComponentModels = Record<string, ComponentModel>;
-
 export const initialState: LayoutModelSliceState = {
   layout: {
     uuid: 'root',
@@ -58,60 +58,10 @@ export const initialState: LayoutModelSliceState = {
   initialized: false,
 };
 
-// This wrapper is necessary because when using slices with redux-undo,
-// you reference state.[sliceName].present.
 export interface StateWithHistoryWrapper {
   layoutModel: StateWithHistory<LayoutModelSliceState>;
 }
 
-type MoveNodePayload = {
-  uuid: string | undefined;
-  to: number[] | undefined;
-};
-
-type ShiftNodePayload = {
-  uuid: string | undefined;
-  direction: 'up' | 'down';
-};
-
-type DuplicateNodePayload = {
-  uuid: string;
-};
-
-type InsertMultipleNodesPayload = {
-  to: number[] | undefined;
-  layoutModel: RootLayoutModel;
-  /**
-   * Pass an optional UUID that will be assigned to the last, top level node being inserted. Allows you to define the UUID
-   * so that you can then do something with the newly inserted node using that UUID.
-   */
-  useUUID?: string;
-};
-
-type AddNewNodePayload = {
-  to: number[] | undefined;
-  component: Component | undefined;
-};
-
-type AddNewSectionPayload = {
-  to: number[] | undefined;
-  layoutModel: RootLayoutModel;
-};
-
-type SortNodePayload = {
-  uuid: string | undefined;
-  to: number | undefined;
-};
-
-type UpdateNodePayload = {
-  uuid: string | undefined;
-  model: {};
-};
-
-export interface ComponentModel {
-  [key: string]: string | boolean | [] | number | {};
-}
-
 export const layoutModelSlice = createSlice({
   name: 'layoutModel',
   initialState,
@@ -119,88 +69,58 @@ export const layoutModelSlice = createSlice({
     deleteNode: create.reducer((state, action: PayloadAction<string>) => {
       const deletedComponent = findNodeByUuid(state.layout, action.payload);
       const removableModelsUuids = [action.payload];
+
       if (deletedComponent) {
         recurseNodes(deletedComponent, (node: LayoutNode) => {
           removableModelsUuids.push(node.uuid);
         });
       }
+
       for (const uuid of removableModelsUuids) {
         if (state.model[uuid]) delete state.model[uuid];
       }
+
       state.layout = removeNodeByUuid(state.layout, action.payload) as RootNode;
     }),
-    duplicateNode: create.reducer(
-      (state, action: PayloadAction<DuplicateNodePayload>) => {
-        const { uuid } = action.payload;
-        const nodeToDuplicate = findNodeByUuid(state.layout, uuid);
-
-        if (!nodeToDuplicate) {
-          console.error(`Cannot duplicate ${uuid}. Check the uuid is valid.`);
-          return;
-        }
-
-        const { updatedNode, updatedModel } = replaceUUIDsAndUpdateModel(
-          nodeToDuplicate,
-          state.model,
-        );
 
-        // Add the updated model to the state
-        state.model = { ...state.model, ...updatedModel };
-
-        const nodePath = findNodePathByUuid(state.layout, uuid);
-        if (nodePath === null) {
-          console.error(
-            `Cannot find ${uuid} in layout. Check the uuid is valid.`,
-          );
-          return;
-        }
-        nodePath[nodePath.length - 1]++;
-        state.layout = insertNodeAtPath(
-          state.layout,
-          nodePath,
-          updatedNode,
-        ) as RootNode;
+    setLayoutModel: create.reducer(
+      (state, action: PayloadAction<LayoutModelSliceState>) => {
+        const { layout, model, initialized } = action.payload;
+        state.layout = layout;
+        state.model = model;
+        state.initialized = initialized;
       },
     ),
-    moveNode: create.reducer(
-      (state, action: PayloadAction<MoveNodePayload>) => {
-        const { uuid, to } = action.payload;
-        if (!uuid || !Array.isArray(to)) {
-          console.error(
-            `Cannot move ${uuid} to position ${to}. Check both uuid and to are defined/valid.`,
-          );
-          return;
-        }
 
-        state.layout = moveNodeToPath(state.layout, uuid, to);
+    updateNodeModel: create.reducer(
+      (state, action: PayloadAction<{ uuid: string; model: {} }>) => {
+        const { uuid, model } = action.payload;
+        if (uuid) {
+          state.model[uuid] = { ...state.model[uuid], ...model };
+        }
       },
     ),
+
     insertNodes: create.reducer(
-      (state, action: PayloadAction<InsertMultipleNodesPayload>) => {
-        const { layoutModel, to, useUUID } = action.payload;
+      (state, action: PayloadAction<{ to: number[]; layoutModel: RootLayoutModel }>) => {
+        const { layoutModel, to } = action.payload;
 
         if (!Array.isArray(to)) {
-          console.error(
-            `Cannot insert nodes. Invalid parameters: newNodes: ${layoutModel}, to: ${to}.`,
-          );
+          console.error(`Cannot insert nodes.`);
           return;
         }
 
         let updatedModel: ComponentModels = { ...state.model };
-        let newLayout: RootNode = _.cloneDeep(state.layout);
+        let newLayout: RootNode = structuredClone(state.layout);
+
         const rootNode = layoutModel.layout;
         const model = layoutModel.model;
 
-        // Loop through each node in reverse order to maintain the correct insert positions
         for (let i = rootNode.children.length - 1; i >= 0; i--) {
           const node = rootNode.children[i];
-          const specifyUUID = i === 0;
           const { updatedNode, updatedModel: nodeUpdatedModel } =
-            replaceUUIDsAndUpdateModel(
-              node,
-              model,
-              specifyUUID ? useUUID : undefined,
-            );
+            replaceUUIDsAndUpdateModel(node, model);
+
           updatedModel = { ...updatedModel, ...nodeUpdatedModel };
           newLayout = insertNodeAtPath(newLayout, to, updatedNode);
         }
@@ -209,17 +129,16 @@ export const layoutModelSlice = createSlice({
         state.layout = newLayout;
       },
     ),
+
     sortNode: create.reducer(
-      (state, action: PayloadAction<SortNodePayload>) => {
+      (state, action: PayloadAction<{ uuid: string; to: number }>) => {
         const { uuid, to } = action.payload;
         if (!uuid || to === undefined) {
-          console.error(
-            `Cannot sort ${uuid} to position ${to}. Check both uuid and to are defined/valid.`,
-          );
+          console.error(`Cannot sort ${uuid}.`);
           return;
         }
 
-        const cloneNode = _.cloneDeep(findNodeByUuid(state.layout, uuid));
+        const cloneNode = structuredClone(findNodeByUuid(state.layout, uuid));
         const nodePath = findNodePathByUuid(state.layout, uuid);
         if (cloneNode && nodePath) {
           const insertPosition = [...nodePath.slice(0, -1), to];
@@ -229,17 +148,16 @@ export const layoutModelSlice = createSlice({
         }
       },
     ),
+
     shiftNode: create.reducer(
-      (state, action: PayloadAction<ShiftNodePayload>) => {
+      (state, action: PayloadAction<{ uuid: string; direction: 'up' | 'down' }>) => {
         const { uuid, direction } = action.payload;
         if (!uuid) {
-          console.error(
-            `Cannot shift ${uuid} ${direction}. Check both uuid and direction are defined/valid.`,
-          );
+          console.error(`Cannot shift ${uuid} ${direction}.`);
           return;
         }
 
-        const cloneNode = _.cloneDeep(findNodeByUuid(state.layout, uuid));
+        const cloneNode = structuredClone(findNodeByUuid(state.layout, uuid));
         const nodePath = findNodePathByUuid(state.layout, uuid);
         if (cloneNode && nodePath) {
           const newPos =
@@ -253,140 +171,23 @@ export const layoutModelSlice = createSlice({
         }
       },
     ),
-    setLayoutModel: create.reducer(
-      (state, action: PayloadAction<LayoutModelSliceState>) => {
-        const { layout, model, initialized } = action.payload;
-        state.layout = layout;
-        state.model = model;
-        state.initialized = initialized;
-      },
-    ),
-    updateNodeModel: create.reducer(
-      (state, action: PayloadAction<UpdateNodePayload>) => {
-        const { uuid, model } = action.payload;
-        const randomData = { randomProp: 'random' };
-        if (uuid) {
-          state.model[uuid] = { ...state.model[uuid], ...model, ...randomData };
-        }
-      },
-    ),
-    // Nearly identical to updateNodeModel above, but makes it possible to
-    // remove props by not including the prior state.model[uuid] in the value
-    // update.
-    updateNodeModelForce: create.reducer(
-      (state, action: PayloadAction<UpdateNodePayload>) => {
-        const { uuid, model } = action.payload;
-        if (uuid) {
-          state.model[uuid] = { ...(model as ComponentModel) };
-        }
-      },
-    ),
   }),
 });
 
-export const addNewComponentToLayout =
-  (payload: AddNewNodePayload) => (dispatch: AppDispatch) => {
-    if (!payload.to || !payload.component) {
-      return;
-    }
-
-    const initialData: ComponentModel = {};
-    const children: Node[] = [];
-    const uuid = uuidv4();
-
-    // Populate the model data with the default values
-    if (payload.component?.field_data) {
-      Object.keys(payload.component.field_data).forEach((propName) => {
-        if (payload.component?.field_data?.[propName]?.['default_values']) {
-          initialData[propName] =
-            payload.component?.field_data[propName]['default_values'];
-        }
-      });
-    }
-
-    // Create empty slots in the layout data for each child slot the component has
-    if (payload.component?.metadata?.slots) {
-      Object.keys(payload.component.metadata.slots).forEach((name) => {
-        children.push({
-          uuid: `-slot-${name}`,
-          name: name,
-          nodeType: 'slot',
-          children: [],
-        });
-      });
-    }
-
-    const layoutModel: RootLayoutModel = {
-      layout: {
-        children: [
-          {
-            children,
-            nodeType: 'component',
-            type: payload.component.id,
-            uuid: uuid,
-          },
-        ],
-        nodeType: 'root',
-        uuid: 'root',
-      },
-      model: {
-        [uuid]: initialData,
-      },
-    };
-
-    dispatch(
-      insertNodes({
-        to: payload.to,
-        layoutModel,
-        useUUID: uuid,
-      }),
-    );
-    dispatch(setSelectedComponent(uuid));
-  };
-
-export const addNewSectionToLayout =
-  (payload: AddNewSectionPayload) => (dispatch: AppDispatch) => {
-    const uuid = uuidv4();
-
-    const { to, layoutModel } = payload;
-
-    if (!to || !layoutModel) {
-      return;
-    }
-
-    dispatch(
-      insertNodes({
-        to,
-        layoutModel,
-        useUUID: uuid,
-      }),
-    );
-    dispatch(setSelectedComponent(uuid));
-  };
-
-// Action creators are generated for each case reducer function.
 export const {
   deleteNode,
   setLayoutModel,
-  duplicateNode,
-  moveNode,
-  shiftNode,
-  sortNode,
-  insertNodes,
   updateNodeModel,
-  updateNodeModelForce,
+  insertNodes,
+  sortNode,
+  shiftNode,
 } = layoutModelSlice.actions;
 
 export const layoutModelReducer = layoutModelSlice.reducer;
 
-// When using redux-undo, you reference the current state by state.[sliceName].present.[targetKey].
-// These selectors are written outside the slice because the type of state is different. Here, we need
-// to be able to access the history, so we use the StateWithHistoryWrapper type.
 export const selectLayout = (state: StateWithHistoryWrapper) =>
   state.layoutModel.present.layout;
 export const selectModel = (state: StateWithHistoryWrapper) =>
   state.layoutModel.present.model;
-export const selectHistory = (state: StateWithHistoryWrapper) =>
-  state.layoutModel;
 export const selectInitialized = (state: StateWithHistoryWrapper) =>
   state.layoutModel.present.initialized;
diff --git a/ui/src/features/layout/layoutUtils.ts b/ui/src/features/layout/layoutUtils.ts
index 250f7742966cd0c1f9c20e0979265d97e6b222f5..9fe5675ab2e69c59f1f93c033ea93d962a3a6d22 100644
--- a/ui/src/features/layout/layoutUtils.ts
+++ b/ui/src/features/layout/layoutUtils.ts
@@ -1,4 +1,3 @@
-import _ from 'lodash';
 import type {
   ComponentModels,
   LayoutNode,
@@ -13,20 +12,16 @@ type NodeFunction = (
   parent: LayoutNode,
 ) => void;
 
-/**
- * Recursively run one or multiple functions against a node and all its descendants.
- * @param node - A layout or layout node.
- * @param functionOrFunctions - A function or an array of functions to run on a node and all of its child nodes.
- * Each function is passed 3 parameters: the node, its index, and its direct parent.
- */
 export function recurseNodes(
   node: LayoutNode | LayoutNode[],
   functionOrFunctions: NodeFunction | NodeFunction[] = [],
 ): void {
-  let functionsToRun: NodeFunction[] = _.castArray(functionOrFunctions);
-  let children: LayoutNode[] = Array.isArray(node) ? node : node.children || [];
+  const functionsToRun: NodeFunction[] = Array.isArray(functionOrFunctions)
+    ? functionOrFunctions
+    : [functionOrFunctions];
+
+  const children: LayoutNode[] = Array.isArray(node) ? node : node.children || [];
 
-  // Loop backwards in case the array is modified by the passed function/functions
   for (let index = children.length - 1; index >= 0; index--) {
     const child = children[index];
 
@@ -42,46 +37,29 @@ export function recurseNodes(
   }
 }
 
-/**
- * Find a node by its UUID.
- * @param node - The starting node to search from.
- * @param uuid - The UUID of the node to find.
- * @returns The found node or null if not found.
- */
 export function findNodeByUuid(node: LayoutNode, uuid: string): Node | null {
   if (node.uuid === uuid) {
-    if (node.nodeType === 'root') {
-      return null;
-    }
-    return node;
+    return node.nodeType === 'root' ? null : node;
   }
   if (node.children) {
     for (const child of node.children) {
       const result = findNodeByUuid(child, uuid);
-      if (result) {
-        return result;
-      }
+      if (result) return result;
     }
   }
   return null;
 }
 
-/**
- * Find the path to a node by its UUID.
- * @param node - The starting node to search from.
- * @param uuid - The UUID of the node to find.
- * @param path - The current path (used internally for recursion).
- * @returns The path to the node as an array of indices, or null if not found.
- */
 export function findNodePathByUuid(
   node: LayoutNode,
   uuid: string | undefined,
   path: number[] = [],
 ): number[] | null {
   if (!uuid) {
-    console.error('No uuid provided to findNodePathByUuid.');
+    console.error('No uuid provided.');
     return null;
   }
+
   if (node.uuid === uuid) {
     return path;
   }
@@ -89,96 +67,52 @@ export function findNodePathByUuid(
   if (node.children) {
     for (let i = 0; i < node.children.length; i++) {
       const child = node.children[i];
-      // Recursively search in the child node, appending the current index to the path
       const result = findNodePathByUuid(child, uuid, path.concat(i));
-      // If the result is not null, the node has been found in the subtree
-      if (result !== null) {
-        return result;
-      }
+      if (result !== null) return result;
     }
   }
 
-  // If the node is not found in this subtree, return null
   return null;
 }
 
-/**
- * Remove a node by its UUID.
- * @param node - The starting node to search from.
- * @param uuid - The UUID of the node to remove.
- * @returns A deep clone of the node with the node matching the uuid removed.
- */
 export function removeNodeByUuid<T extends LayoutNode>(
   node: T,
   uuid: string,
 ): T {
-  const newState = _.cloneDeep(node);
+  const newState = structuredClone(node);
   const path = findNodePathByUuid(newState, uuid);
 
   if (path) {
-    const lodashPath = path.map((index) => `children[${index}]`).join('.');
-    const parentPath = lodashPath.split('.').slice(0, -1).join('.');
-    const i = path[path.length - 1];
-    const parent = parentPath ? _.get(newState, parentPath) : newState;
-    if (parent && parent.children) {
-      parent.children.splice(i, 1);
+    let parent = newState as LayoutNode;
+    for (let i = 0; i < path.length - 1; i++) {
+      parent = parent.children[path[i]];
     }
+    parent.children.splice(path[path.length - 1], 1);
   }
 
   return newState;
 }
 
-/**
- * Insert a node at a specific path.
- * @param layoutNode - The starting node to insert into.
- * @param path - The path where the new node should be inserted.
- * @param newNode - The new node to insert.
- * @returns A deep clone of the node with the newNode inserted at path.
- */
 export function insertNodeAtPath<T extends LayoutNode>(
   layoutNode: T,
   path: number[],
   newNode: Node,
 ): T {
-  const newState = _.cloneDeep(layoutNode);
+  const newState = structuredClone(layoutNode);
 
   if (path.length === 0) {
-    throw new Error(
-      'Path must have at least one element to define where to insert the node.',
-    );
+    throw new Error('Path must have at least one element.');
   }
 
-  // Base case: if the path has only one element, insert the new node at the specified index
-  if (path.length === 1) {
-    newState.children = newState.children || [];
-    newState.children.splice(path[0], 0, newNode);
-    return newState;
+  let parent = newState as LayoutNode;
+  for (let i = 0; i < path.length - 1; i++) {
+    parent = parent.children[path[i]];
   }
 
-  // Recursive case: navigate down the path
-  const [currentIndex, ...restOfPath] = path;
-  newState.children = newState.children || [];
-  if (!newState.children[currentIndex]) {
-    throw new Error('Path must resolve to a node in the tree.');
-  }
-
-  // Recursively insert the node at the remaining path and update the child node
-  newState.children[currentIndex] = insertNodeAtPath(
-    newState.children[currentIndex],
-    restOfPath,
-    newNode,
-  ) as Node;
-
+  parent.children.splice(path[path.length - 1], 0, newNode);
   return newState;
 }
 
-/**
- * Move a node to a new path.
- * @param rootNode - The root node of the layout.
- * @param uuid - The UUID of the node to move.
- * @param path - The path to move the node to.
- * @returns A deep clone of the `rootNode` with the node matching the `uuid` moved to the `path`.
- */
 export function moveNodeToPath(
   rootNode: RootNode,
   uuid: string,
@@ -188,54 +122,14 @@ export function moveNodeToPath(
   if (!child) {
     throw new Error(`Node with UUID ${uuid} not found.`);
   }
-  // Make a clone of the node that is being moved.
-  const clone = _.cloneDeep(child);
-  // flag the original node for deletion
-  child.uuid = child.uuid + '_remove';
 
-  // Insert the clone at toPath
-  const newState = insertNodeAtPath(rootNode, path, clone);
+  const clone = structuredClone(child);
+  child.uuid += '_remove';
 
-  // Remove the original node by finding it by uuid (which is now `${child.uuid}_remove`)
+  const newState = insertNodeAtPath(rootNode, path, clone);
   return removeNodeByUuid(newState, child.uuid);
 }
 
-/**
- * Checks if a node is a child of another node.
- * @param layoutNode - The root node.
- * @param uuid - The UUID of the node to check.
- * @returns {boolean | null} - Returns if node is a child or not and null if the node is not found.
- */
-export function isChildNode(layoutNode: LayoutNode, uuid: string) {
-  const path = findNodePathByUuid(layoutNode, uuid);
-  if (path !== null) {
-    return path && path.length > 1;
-  } else {
-    return null;
-  }
-}
-
-/**
- * Get the depth of the node in the layout tree from the root.
- * @param layoutNode - The root node.
- * @param uuid - The UUID of the node to check.
- * @returns Depth of a node as an integer.
- */
-export function getNodeDepth(layoutNode: LayoutNode, uuid: string | undefined) {
-  const path = findNodePathByUuid(layoutNode, uuid);
-  if (path) {
-    return path.length - 1;
-  }
-  return 0;
-}
-
-/**
- * Replace UUIDs in a layout node and its corresponding model.
- * @param node - The layout node to update.
- * @param model - The corresponding model to update.
- * @param newUUID - Optionally specify the UUID of the new node and its model.
- * @returns An updated model and an updated state.
- */
 export function replaceUUIDsAndUpdateModel(
   node: Node,
   model: ComponentModels,
@@ -247,19 +141,15 @@ export function replaceUUIDsAndUpdateModel(
   const oldToNewUUIDMap: Record<string, string> = {};
   const updatedModel: ComponentModels = {};
 
-  const replaceUUIDs = (
-    node: Node,
-    parentUuid?: string,
-    newUuid?: string,
-  ): Node => {
+  const replaceUUIDs = (node: Node, parentUuid?: string, newUuid?: string): Node => {
     const newNode: Node = { ...node, uuid: newUuid || uuidv4() };
+
     if (newNode.nodeType === 'slot') {
       newNode.uuid = `${parentUuid}-slot-${newNode.name}`;
     }
 
     oldToNewUUIDMap[node.uuid] = newNode.uuid;
 
-    // Recursively process children
     if (newNode.children) {
       newNode.children = newNode.children.map((child) =>
         replaceUUIDs(child, newNode.uuid),
@@ -271,13 +161,12 @@ export function replaceUUIDsAndUpdateModel(
 
   const updatedNode = replaceUUIDs(node, undefined, newUUID);
 
-  // Update the model keys
-  for (const oldUUID in model) {
+  Object.keys(model).forEach((oldUUID) => {
     const newUUID = oldToNewUUIDMap[oldUUID];
     if (newUUID) {
-      updatedModel[newUUID] = _.cloneDeep(model[oldUUID]);
+      updatedModel[newUUID] = structuredClone(model[oldUUID]);
     }
-  }
+  });
 
   return { updatedNode, updatedModel };
 }
diff --git a/ui/src/local_packages/hyperscriptify/propsify/standard/index.js b/ui/src/local_packages/hyperscriptify/propsify/standard/index.js
index 939fda9743689fe501afa14991104f677a7e4451..01de9b0c5351ce155601ae61aa36e917f616c5eb 100644
--- a/ui/src/local_packages/hyperscriptify/propsify/standard/index.js
+++ b/ui/src/local_packages/hyperscriptify/propsify/standard/index.js
@@ -1,5 +1,17 @@
 import factory from '../factory';
-import { camelCase, mapKeys, mapValues, set } from 'lodash-es';
+const camelCase = (str) => str.replace(/-([a-z])/g, (g) => g[1].toUpperCase());
+const mapKeys = (obj, fn) =>
+  Object.fromEntries(Object.entries(obj).map(([k, v]) => [fn(k), v]));
+const mapValues = (obj, fn) =>
+  Object.fromEntries(Object.entries(obj).map(([k, v]) => [k, fn(v)]));
+const set = (obj, path, value) => {
+  const keys = path.split('.');
+  let current = obj;
+  for (let i = 0; i < keys.length - 1; i++) {
+    current = current[keys[i]] = current[keys[i]] || {};
+  }
+  current[keys[keys.length - 1]] = value;
+};
 import htmlElementAttributeToPropMap from './reactHtmlAttributeToPropertyMap';
 
 const basePropsify = factory({