diff --git a/src/assets/images/loop.png b/src/assets/images/loop.png
new file mode 100644
index 0000000000000000000000000000000000000000..202f18bb75b00da8cc1e9202159f93ec54fafe8d
Binary files /dev/null and b/src/assets/images/loop.png differ
diff --git a/src/assets/main.css b/src/assets/main.css
index 4658c7e602c8f3b22aed0f58fc5fc8c793ca1ce8..b1c4973c8510ba5f10a84e48e6e25fc2b372abcc 100644
--- a/src/assets/main.css
+++ b/src/assets/main.css
@@ -45,6 +45,8 @@
   --node-control-splitter-color-dark: #ff8800;
   --node-control-andmerger-color: #00a2ff;
   --node-control-andmerger-color-dark: #0089d8;
+  --node-control-loop-repeat-color: #3b9743;
+  --node-control-loop-repeat-color-dark: #1f6926;
   --use-gradient: 1;
 
   --edge-scheduled-deletion-max: #ff0000;
diff --git a/src/components/main/editorbar/ControlBar.vue b/src/components/main/editorbar/ControlBar.vue
index f9493f99ae0d61cabd6ed019d367b4d0f8d9c6fd..545d2b3e398c423fb7d468f8825474e40bf8b971 100644
--- a/src/components/main/editorbar/ControlBar.vue
+++ b/src/components/main/editorbar/ControlBar.vue
@@ -35,6 +35,9 @@ export default {
     },
     eventType(): BaseType {
       return this.typeService.getEventType()
+    },
+    integerType(): BaseType {
+      return this.typeService.getIntegerType()
     }
   }
 }
@@ -44,7 +47,7 @@ export default {
   <div id="blocks-bar">
     <div id="block-entries">
       <NodeEntry
-        v-for="entry in controlNodes"
+        v-for="entry in [...controlNodes]"
         :key="entry[0].id.id"
         :color-unselected="getCSSVar(entry[2])"
         :color-selected="getCSSVar(entry[3])"
diff --git a/src/conversion/Cloner.ts b/src/conversion/Cloner.ts
index e72093d90085c0741523e5abeba12c6a64953470..b3306d9163cac6062b8bbf8708793ef6f95ba544 100644
--- a/src/conversion/Cloner.ts
+++ b/src/conversion/Cloner.ts
@@ -1,4 +1,4 @@
-import { type ControlNode, type Node, NodeType, type Parameter, type ParameterNode, type SubSkill } from '@/datatypes'
+import { type ControlNode, type ControlSlottedNode, type Node, NodeType, type Parameter, type ParameterNode, type SubSkill } from '@/datatypes'
 import { v4 as uuidv4 } from 'uuid'
 
 export default class Cloner {
@@ -39,6 +39,20 @@ export default class Cloner {
           })
         }
 
+        if (controlNode.isSlotted) {
+          return {
+            nodeType: node.nodeType,
+            id: { id: uuidv4(), hint: node.name },
+            name: node.name,
+            position: { x: node.position.x, y: node.position.y },
+            description: controlNode.description,
+            parameters: newParams,
+            controlType: controlNode.controlType,
+            isSlotted: controlNode.isSlotted,
+            slot: undefined // do not clone the node in the slot
+          } as ControlSlottedNode
+        }
+
         return {
           nodeType: node.nodeType,
           id: { id: uuidv4(), hint: node.name },
@@ -46,12 +60,16 @@ export default class Cloner {
           position: { x: node.position.x, y: node.position.y },
           description: controlNode.description,
           parameters: newParams,
-          controlType: controlNode.controlType
+          controlType: controlNode.controlType,
+          isSlotted: controlNode.isSlotted
         } as ControlNode
       }
       case NodeType.VALUE: {
         throw new Error('Cloner: cloneNode: cloning of Value Node is not supported yet.')
       }
+      default: {
+        throw new Error('Cloner: cloneNode: unknown node type.')
+      }
     }
   }
 }
diff --git a/src/conversion/Converter.ts b/src/conversion/Converter.ts
index b7881d667310ff1474c35c36c404b300cf647bd2..a687fb65d0ef475200764180f03f594c3288f0ba 100644
--- a/src/conversion/Converter.ts
+++ b/src/conversion/Converter.ts
@@ -6,6 +6,8 @@ import {
   type BaseTypeDataTransfer,
   type ControlNode,
   type ControlNodeDataTransfer,
+  type ControlSlottedNode,
+  ControlType,
   DataIdentifier,
   type DictData,
   type DictDataDataTransfer,
@@ -606,7 +608,8 @@ export default class Converter {
    * @returns - The converted control node.
    */
   public convertControlNodeDataTransferToControlNode(
-    controlNode: ControlNodeDataTransfer
+    controlNode: ControlNodeDataTransfer,
+    skill: Skill
   ): Result<ControlNode, BaseError> {
     const parameters = []
     for (const parameterDT of controlNode.parameters) {
@@ -619,6 +622,45 @@ export default class Converter {
       }
       parameters.push(parameter.result)
     }
+
+    const isSlotted = controlNode.controlType === ControlType.LOOP_REPEAT || controlNode.controlType === ControlType.LOOP_RETRY
+    let slot
+    if (isSlotted && controlNode.slottedNodeId !== undefined && controlNode.slottedNodeId !== null) {
+      if (controlNode.slottedNodeId.id !== '') {
+        slot = { id: controlNode.slottedNodeId.id, parent: { id: skill.id } }
+      }
+    }
+    let name = ''
+
+    switch (controlNode.controlType) {
+      case ControlType.LOOP_REPEAT:
+        name = 'Loop Repeat'
+        break
+      case ControlType.LOOP_RETRY:
+        name = 'Loop Retry'
+        break
+      default:
+        name = 'Unknown Control Node'
+        break
+    }
+
+    if (isSlotted === true) {
+      return {
+        success: true,
+        result: {
+          parameters,
+          description: '',
+          nodeType: controlNode.nodeType,
+          id: { id: controlNode.nodeId },
+          name,
+          position: { x: controlNode.xPos, y: controlNode.yPos },
+          controlType: controlNode.controlType,
+          isSlotted: true,
+          slot
+        } as ControlSlottedNode
+      }
+    }
+
     return {
       success: true,
       result: {
@@ -626,9 +668,10 @@ export default class Converter {
         description: '',
         nodeType: controlNode.nodeType,
         id: { id: controlNode.nodeId },
-        name: '',
+        name,
         position: { x: controlNode.xPos, y: controlNode.yPos },
-        controlType: controlNode.controlType
+        controlType: controlNode.controlType,
+        isSlotted: false
       }
     }
   }
@@ -1263,6 +1306,19 @@ export default class Converter {
   public convertControlNodeToControlNodeDataTransfer(
     controlNode: ControlNode
   ): ControlNodeDataTransfer {
+    const slot = {
+      'py/object': pyTypes.IDENTIFICATOR,
+      id: '',
+      hint: ''
+    }
+
+    if (controlNode.isSlotted) {
+      const controlSlottedNode = controlNode as ControlSlottedNode
+      if (controlSlottedNode.slot) {
+        slot.id = controlSlottedNode.slot.id
+      }
+    }
+
     return {
       'py/object': pyTypes.CONTROL_NODE,
       nodeId: controlNode.id.id,
@@ -1272,7 +1328,8 @@ export default class Converter {
       parameters: controlNode.parameters.map((p) =>
         this.convertParameterToParameterDataTransfer(p)
       ),
-      controlType: controlNode.controlType
+      controlType: controlNode.controlType,
+      slottedNodeId: slot
     }
   }
 
@@ -1457,7 +1514,7 @@ export default class Converter {
         break
       }
       case NodeType.CONTROL: {
-        result = this.convertControlNodeDataTransferToControlNode(node as ControlNodeDataTransfer)
+        result = this.convertControlNodeDataTransferToControlNode(node as ControlNodeDataTransfer, skill)
         if (!result.success) {
           result.error.addContext(`From control node with ID: "${node.nodeId}" `)
           return result
diff --git a/src/conversion/Updater.ts b/src/conversion/Updater.ts
index 5ff2944d26a66f8e6d28f1af6cd1b32267725265..b01cc5d4cb094af1c3e130d03879aaa4c3aba6fd 100644
--- a/src/conversion/Updater.ts
+++ b/src/conversion/Updater.ts
@@ -484,7 +484,7 @@ export default class Updater {
           const newParameter = newNode as ControlNodeDataTransfer
           if (node === undefined) {
             // add
-            const result = this.converter.convertControlNodeDataTransferToControlNode(newParameter)
+            const result = this.converter.convertControlNodeDataTransferToControlNode(newParameter, parentSkill)
             if (!result.success) {
               parentSkill.corrupted = true
               throw result.error
diff --git a/src/datatypes/index.ts b/src/datatypes/index.ts
index 582dd09ac0410b4cccbb922c70db1fc2c2855bfa..1c09cac31eca2f968ca145862df2548b21f12f82 100644
--- a/src/datatypes/index.ts
+++ b/src/datatypes/index.ts
@@ -7,6 +7,7 @@ import type BaseData from './model/BaseData'
 import type BaseType from './model/BaseType'
 import type Cell from './model/Cell'
 import type ControlNode from './model/ControlNode'
+import type ControlSlottedNode from './model/ControlSlottedNode'
 import type DictData from './model/DictData'
 import type DoubleGenericType from './model/DoubleGenericType'
 import type Edge from './model/Edge'
@@ -86,6 +87,7 @@ export type {
   ControlGraphicNode,
   ControlNode,
   ControlNodeDataTransfer,
+  ControlSlottedNode,
   DefaultSubSkillGraphicNode,
   DictData,
   DictDataDataTransfer,
diff --git a/src/datatypes/model/ControlNode.ts b/src/datatypes/model/ControlNode.ts
index b0f64b3999cd51a64c82b7433f81d9e8395dd139..5f71ff4d4ff266329e3ec2fc8e38a38385f2685f 100644
--- a/src/datatypes/model/ControlNode.ts
+++ b/src/datatypes/model/ControlNode.ts
@@ -6,4 +6,5 @@ export default interface ControlNode extends Node {
   controlType: ControlType
   parameters: Parameter[]
   description: string
+  isSlotted: boolean
 }
diff --git a/src/datatypes/model/ControlSlottedNode.ts b/src/datatypes/model/ControlSlottedNode.ts
new file mode 100644
index 0000000000000000000000000000000000000000..bcf120dd7c33e39211166e640f2891502b7afceb
--- /dev/null
+++ b/src/datatypes/model/ControlSlottedNode.ts
@@ -0,0 +1,8 @@
+import type { ControlNode, Identificator } from '@/datatypes'
+
+/**
+ * A control slotted node is a node that can have a slot.
+ */
+export default interface ControlSlottedNode extends ControlNode {
+  slot: Identificator | undefined
+}
diff --git a/src/datatypes/model/ControlType.ts b/src/datatypes/model/ControlType.ts
index 91249a5a2c1ac4d5c357954127a724c1df20d04c..7b4daad6a8dbb2467fc1837d567033b036e9bb7c 100644
--- a/src/datatypes/model/ControlType.ts
+++ b/src/datatypes/model/ControlType.ts
@@ -2,5 +2,7 @@
 // see https://thisthat.dev/const-enum-vs-enum/#:~:text=Because%20there%20is%20no%20JavaScript%20object%20that%20associates%20with%20const%20enum%20is%20generated%20at%20run%20time%2C%20it%20is%20not%20possible%20to%20loop%20over%20the%20const%20enum%20values.
 export enum ControlType {
   SPLITTER = 'SPLITTER',
-  AND_MERGER = 'AND_MERGER'
+  AND_MERGER = 'AND_MERGER',
+  LOOP_REPEAT = 'LOOP_REPEAT',
+  LOOP_RETRY = 'LOOP_RETRY'
 }
diff --git a/src/datatypes/transfer/ControlNodeDataTransfer.ts b/src/datatypes/transfer/ControlNodeDataTransfer.ts
index 0058822912d6cd422ba0d27eab9044483553c0e6..1b9d225b184964b35992e43eb77fb854a64afdcd 100644
--- a/src/datatypes/transfer/ControlNodeDataTransfer.ts
+++ b/src/datatypes/transfer/ControlNodeDataTransfer.ts
@@ -1,8 +1,10 @@
 import type { ControlType } from '../model/ControlType'
+import type IdentificatorDataTransfer from './IdentificatorDataTransfer'
 import type NodeDataTransfer from './NodeDataTransfer'
 import type ParameterDataTransfer from './ParameterDataTransfer'
 
 export default interface ControlNodeDataTransfer extends NodeDataTransfer {
   controlType: ControlType
   parameters: ParameterDataTransfer[]
+  slottedNodeId: IdentificatorDataTransfer
 }
diff --git a/src/render/GraphicNode.ts b/src/render/GraphicNode.ts
index 1d8f258f3ae09cad613f11980f8e711d9b43047a..ac9fe38409aa5c708621b95f0f6d937581618be8 100644
--- a/src/render/GraphicNode.ts
+++ b/src/render/GraphicNode.ts
@@ -2,8 +2,10 @@ import type SessionService from '@/services/SessionService'
 import type SkillService from '@/services/SkillService'
 import type StatechartService from '@/services/StatechartService'
 import type TypeService from '@/services/TypeService'
+import type { Vector2 } from 'three'
 import type Deletable from './Deletable'
 import type GraphicEdge from './GraphicEdge'
+import type ControlSlottedGraphicNode from './graphicnodes/control/slotted/ControlSlottedGraphicNode'
 import type InputEventContext from './input/InputEventContext'
 import type InputEventListener from './input/InputEventListener'
 import {
@@ -48,6 +50,7 @@ export default abstract class GraphicNode
   public subSkillIconSize = 27
   public subSkillIconRightDist = 35
   public subSkillIconTopDist = 25
+  public borderRadius = [10, 10, 10, 10]
 
   public readonly isGraphicNode = true
   public readonly isGraphicPort = false
@@ -56,6 +59,7 @@ export default abstract class GraphicNode
   public readonly clickable = true
   public readonly deletable = true
   public readonly isComposed = true
+  public slottable = false
 
   public skillService: SkillService | undefined
   public statechartService: StatechartService | undefined
@@ -67,6 +71,9 @@ export default abstract class GraphicNode
   private highlightRects: GraphicObject[] = []
   private selectionCorners: GraphicObject[] = []
   private selected = false
+  public slot: ControlSlottedGraphicNode | undefined
+  private hoveredSlot: GraphicObject | undefined
+  private lastSlottedGraphicNode: ControlSlottedGraphicNode | undefined
   public actualPosition: Vector3 = new Vector3()
 
   /**
@@ -118,7 +125,7 @@ export default abstract class GraphicNode
    * @param ctx - The input event context.
    */
   public onMouseUp(ctx: InputEventContext) {
-    if (ctx.mutex === undefined || ctx.mutex === false || ctx.tab === undefined) return
+    if (ctx.mutex === undefined || ctx.mutex === false || ctx.tab === undefined || ctx.graphicEdge !== undefined) return
 
     // if selected and this is the only node selected, deselect it
     if (this.selected === true) {
@@ -127,6 +134,11 @@ export default abstract class GraphicNode
         this.selected = false
       }
     }
+
+    if (this.slottable === true) {
+      this.putInSlot()
+    }
+    this.deleteGhostNode()
   }
 
   /**
@@ -134,8 +146,63 @@ export default abstract class GraphicNode
    * @param ctx - The input event context.
    */
   public onMouseDrag(ctx: InputEventContext) {
-    if (ctx.delta === undefined || ctx.mutex === undefined || ctx.mutex === false) return
-    this.actualPosition.add(new Vector3(ctx.delta.x, ctx.delta.y, 0))
+    if (ctx.delta === undefined || ctx.mutex === undefined || ctx.mutex === false || ctx.position === undefined) return
+
+    this.moveBy(ctx.delta)
+
+    const renderer = this.statechartService?.getRenderer()
+
+    if (this.slot !== undefined) {
+      this.removeFromSlot()
+    } else if (renderer !== undefined) {
+      const intersections = renderer.computeIntersects()
+      const slotIntersection = intersections.find((i) => i.object.name === 'slot')
+      if (slotIntersection !== undefined) {
+        const slottedGraphicNode = GraphicObject.getHighestParent(slotIntersection.object as GraphicObject) as ControlSlottedGraphicNode
+        if (slottedGraphicNode.slottedGraphicNode === undefined && GraphicObject.getHighestParent(slotIntersection.object as GraphicObject) !== this && this.slottable === true && slottedGraphicNode.ghostNode === undefined) {
+          slottedGraphicNode.slotGhostNode(this)
+          this.hoveredSlot = slottedGraphicNode.slotBackground
+
+          if (slottedGraphicNode !== this.lastSlottedGraphicNode) {
+            this.deleteGhostNode()
+          }
+          this.lastSlottedGraphicNode = slottedGraphicNode
+        }
+      } else {
+        this.deleteGhostNode()
+        this.hoveredSlot = undefined
+      }
+    }
+
+    if (this.node.nodeType === NodeType.CONTROL) {
+      const controlNode = this.node as ControlNode
+      if (controlNode.isSlotted === true) {
+        const slottedGraphicNode = this as unknown as ControlSlottedGraphicNode
+        slottedGraphicNode.slottedGraphicNode?.syncNodePos(true)
+        slottedGraphicNode.slottedGraphicNode?.updateGizmos(true)
+      }
+    }
+
+    this.updateGizmos()
+  }
+
+  /**
+   * Move the graphic node to a position. The underlying node is moved as well, and the edges of the graphic ports are updated.
+   * @param position - The position to move the graphic node to.
+   */
+  public moveTo(position: Vector2, z: number | undefined, saveAsWorld = false) {
+    this.actualPosition.set(position.x, position.y, z ?? this.position.z)
+    this.position.set(position.x, position.y, z ?? this.position.z)
+
+    this.syncNodePos(saveAsWorld)
+  }
+
+  /**
+   * Move the graphic node by a delta. The underlying node is moved as well, and the edges of the graphic ports are updated.
+   * @param delta - The delta by which to move this graphic node.
+   */
+  public moveBy(delta: Vector2, saveAsWorld = false) {
+    this.actualPosition.add(new Vector3(delta.x, delta.y, 0))
 
     if (this.sessionService?.getSettings().snapToGrid === true) {
       this.snapToGrid()
@@ -144,16 +211,72 @@ export default abstract class GraphicNode
       this.position.y = this.actualPosition.y
     }
 
+    this.syncNodePos(saveAsWorld)
+  }
+
+  /**
+   * Sync the position of the underlying node with the graphic node.
+   * @param saveAsWorld - Whether to save the position as world position or local position.
+   */
+  public syncNodePos(saveAsWorld = false) {
+    let pos = this.position
+
+    if (saveAsWorld === true) {
+      pos = this.localToWorld(this.position.clone())
+    }
+
     this.node.position = {
-      x: this.position.x,
-      y: this.position.y
+      x: pos.x,
+      y: pos.y
     }
 
     for (const graphicPort of this.graphicPorts) {
       graphicPort.updateEdges()
     }
+  }
 
-    this.updateGizmos()
+  /**
+   * Unslots the node from its slot.
+   */
+  public removeFromSlot() {
+    if (this.slot === undefined) {
+      return
+    }
+
+    this.slot.unslotGraphicNode()
+    this.slot = undefined
+
+    this.borderRadius = [10, 10, 10, 10]
+    this.updateBackground()
+    this.deleteHighlightRects()
+    this.drawHighlightRects()
+  }
+
+  /**
+   * Puts the node in the hovered slot. The node is added as a child of the slot.
+   */
+  public putInSlot() {
+    if (this.hoveredSlot === undefined) {
+      return
+    }
+    const slottedNode = GraphicObject.getHighestParent(this.hoveredSlot) as ControlSlottedGraphicNode
+    this.slot = slottedNode
+    slottedNode.slotGraphicNode(this)
+    this.transferEdgesToSlot()
+
+    this.borderRadius = [0, 0, 10, 10]
+    this.updateBackground()
+    this.deleteHighlightRects()
+    this.drawHighlightRects()
+    this.updateGizmos(true)
+  }
+
+  public deleteGhostNode() {
+    if (this.lastSlottedGraphicNode === undefined) {
+      return
+    }
+    this.lastSlottedGraphicNode.unslotGhostNode()
+    this.lastSlottedGraphicNode = undefined
   }
 
   /**
@@ -211,6 +334,31 @@ export default abstract class GraphicNode
     return false
   }
 
+  public bringForward() {
+    if (this.statechartService === undefined) {
+      return
+    }
+
+    const renderer = this.statechartService.getRenderer()
+
+    if (renderer === undefined || renderer.current === undefined) {
+      return
+    }
+
+    const nodes = renderer.current[0].statechart.graphicNodes.sort((a, b) => {
+      return a.position.z - b.position.z
+    })
+
+    const index = nodes.findIndex((node) => node.node.id.id === this.node.id.id)
+    this.position.z = nodes[nodes.length - 1].position.z
+    this.updateGizmos()
+
+    for (let i = index + 1; i < nodes.length; i++) {
+      nodes[i].position.z -= 20
+      nodes[i].updateGizmos()
+    }
+  }
+
   /**
    * Snaps the graphic node position to the grid.
    */
@@ -412,6 +560,10 @@ export default abstract class GraphicNode
 
           const graphicPort = new GraphicPort(inputPositions[i], port, true, false)
           graphicPort.statechartService = this.statechartService
+          for (let k = 0; k < connectedGraphicEdges.length; k++) {
+            const edge = connectedGraphicEdges[k]
+            edge.toPort = graphicPort
+          }
           result.push(graphicPort)
           this.add(graphicPort.assemble())
         }
@@ -435,7 +587,10 @@ export default abstract class GraphicNode
 
           const graphicPort = new GraphicPort(outputPositions[i], port, true, false)
           graphicPort.statechartService = this.statechartService
-
+          for (let k = 0; k < connectedGraphicEdges.length; k++) {
+            const edge = connectedGraphicEdges[k]
+            edge.fromPort = graphicPort
+          }
           result.push(graphicPort)
           this.add(graphicPort.assemble())
         }
@@ -462,7 +617,14 @@ export default abstract class GraphicNode
           true
         )
         graphicPort.statechartService = this.statechartService
-
+        const edge = connectedGraphicEdges[0]
+        if (edge !== undefined) {
+          if (edge.fromPort !== undefined) {
+            edge.fromPort = graphicPort
+          } else if (edge.toPort !== undefined) {
+            edge.toPort = graphicPort
+          }
+        }
         result.push(graphicPort)
         this.add(graphicPort.assemble())
         break
@@ -493,9 +655,12 @@ export default abstract class GraphicNode
             graphicNode: this
           }
 
-          const graphicPort = new GraphicPort(inputPositions[i], port, !!this.sessionService?.getSettings().debug, false)
+          const graphicPort = new GraphicPort(inputPositions[i], port, !!this.sessionService?.getSettings().debug || node.isSlotted, false)
           graphicPort.statechartService = this.statechartService
-
+          for (let k = 0; k < connectedGraphicEdges.length; k++) {
+            const edge = connectedGraphicEdges[k]
+            edge.toPort = graphicPort
+          }
           result.push(graphicPort)
           this.add(graphicPort.assemble())
         }
@@ -517,9 +682,12 @@ export default abstract class GraphicNode
             graphicNode: this
           }
 
-          const graphicPort = new GraphicPort(outputPositions[i], port, !!this.sessionService?.getSettings().debug, false)
+          const graphicPort = new GraphicPort(outputPositions[i], port, !!this.sessionService?.getSettings().debug || node.isSlotted, false)
           graphicPort.statechartService = this.statechartService
-
+          for (let k = 0; k < connectedGraphicEdges.length; k++) {
+            const edge = connectedGraphicEdges[k]
+            edge.fromPort = graphicPort
+          }
           result.push(graphicPort)
           this.add(graphicPort.assemble())
         }
@@ -637,34 +805,41 @@ export default abstract class GraphicNode
   /**
    * Updates the gizmos.
    */
-  public updateGizmos() {
-    if (this.sessionService === undefined || this.sessionService.getSettings().debug === false)
+  public updateGizmos(convertToWorld = false) {
+    if (this.sessionService === undefined || this.sessionService.getSettings().debug === false) {
       return
+    }
+
+    let pos = this.position
+
+    if (convertToWorld === true) {
+      pos = this.localToWorld(this.position.clone())
+    }
 
     if (this.gizmos.length === 0) {
       const xpos = new TextGraphicObject(
-        new Vector3(this.width / -2, this.height / 2 + 60, 0),
+        new Vector3(this.width / -2, (this.slot === undefined ? this.height / 2 + 60 : this.height / -2 - 20), 0),
         TextJustification.LEFT,
         15,
-        `X ${this.position.x.toFixed(1)}`,
+        `X ${pos.x.toFixed(1)}`,
         new Color(Renderer.getCSSVar('--primary-text-color'))
       )
 
       xpos.assemble()
       const ypos = new TextGraphicObject(
-        new Vector3(this.width / -2, this.height / 2 + 40, 0),
+        new Vector3(this.width / -2, (this.slot === undefined ? this.height / 2 + 40 : this.height / -2 - 40), 0),
         TextJustification.LEFT,
         15,
-        `Y ${this.position.y.toFixed(1)}`,
+        `Y ${pos.y.toFixed(1)}`,
         new Color(Renderer.getCSSVar('--primary-text-color'))
       )
 
       ypos.assemble()
       const zpos = new TextGraphicObject(
-        new Vector3(this.width / -2, this.height / 2 + 20, 0),
+        new Vector3(this.width / -2, (this.slot === undefined ? this.height / 2 + 20 : this.height / -2 - 60), 0),
         TextJustification.LEFT,
         15,
-        `Z ${this.position.z.toFixed(1)}`,
+        `Z ${pos.z.toFixed(1)}`,
         new Color(Renderer.getCSSVar('--primary-text-color'))
       )
 
@@ -685,9 +860,9 @@ export default abstract class GraphicNode
         y.textObject !== undefined &&
         z.textObject !== undefined
       ) {
-        x.textObject.text = `X ${this.position.x.toFixed(1)}`
-        y.textObject.text = `Y ${this.position.y.toFixed(1)}`
-        z.textObject.text = `Z ${this.position.z.toFixed(1)}`
+        x.textObject.text = `X ${pos.x.toFixed(1)}`
+        y.textObject.text = `Y ${pos.y.toFixed(1)}`
+        z.textObject.text = `Z ${pos.z.toFixed(1)}`
 
         x.textObject.sync()
         y.textObject.sync()
@@ -696,6 +871,30 @@ export default abstract class GraphicNode
     }
   }
 
+  public transferEdgesToSlot() {
+    this.transferInputEdgesToSlot()
+  }
+
+  private transferInputEdgesToSlot() {
+    if (this.slot === undefined || this.node.nodeType !== NodeType.SUBSKILL) {
+      return
+    }
+
+    const slotStartPort = this.slot.graphicPorts.find((gp) => gp.port.parameter.name.toLowerCase() === 'start')
+    const selfStartPort = this.graphicPorts.find((gp) => gp.port.parameter.name.toLowerCase() === 'start')
+
+    if (slotStartPort === undefined || selfStartPort === undefined) {
+      NotificationManager.error('Could not find either self start port or slot start port')
+      return
+    }
+
+    for (let i = selfStartPort.port.connectedGraphicEdges.length - 1; i >= 0; i--) {
+      const graphicEdge = selfStartPort.port.connectedGraphicEdges[i]
+      selfStartPort.disconnectEdge(graphicEdge)
+      slotStartPort.connectEdge(graphicEdge)
+    }
+  }
+
   /**
    * Draws the selection corners.
    * @param color - The color of the corners.
@@ -751,20 +950,21 @@ export default abstract class GraphicNode
 
     const n = 20
     const lambda = Math.log(100) / (n + 1)
-    const startRadius = 10
-    const endRadius = 25
     const color = new Color(Renderer.getCSSVar('--node-outline-color'))
     for (let i = 0; i < n; i++) {
       const opacity = 0.5 * Math.E ** (-lambda * i)
-      const t = i / n
+      const radiusTL = this.borderRadius[0] + 7 + i / 2
+      const radiusTR = this.borderRadius[1] + 7 + i / 2
+      const radiusBR = this.borderRadius[2] + 7 + i / 2
+      const radiusBL = this.borderRadius[3] + 7 + i / 2
 
       const rect = new Rectangle(
-        this.width + 10 + i ** 1.1,
-        this.height + 10 + i ** 1.1,
+        this.width + 14 + i,
+        this.height + 14 + i,
         color,
         0,
         color,
-        (1 - t) * startRadius + t * endRadius
+        [radiusTL, radiusTR, radiusBR, radiusBL]
       ).assemble()
 
       rect.materials[0].opacity = opacity
@@ -774,6 +974,30 @@ export default abstract class GraphicNode
     }
   }
 
+  public updateBackground() {
+    const oldBackground = this.getObjectByName('background')
+    if (oldBackground === undefined) {
+      NotificationManager.warning('Trying to update node background but found none')
+      return
+    }
+
+    GraphicObject.free(oldBackground as GraphicObject)
+
+    const background = new Rectangle(
+      this.width,
+      this.height,
+      new Color(Renderer.getCSSVar('--primary-color')),
+      7,
+      new Color(Renderer.getCSSVar('--node-outline-color')),
+      this.borderRadius,
+      false,
+      false
+    ).assemble()
+
+    background.name = 'background'
+    this.add(background)
+  }
+
   /**
    * Deletes the highlight rectangles.
    */
diff --git a/src/render/GraphicPort.ts b/src/render/GraphicPort.ts
index 5a91fd01179a6ee91eb90d6b658a57911ab89813..57c3027498ff314254f7a4753b56d91d8fed2b2b 100644
--- a/src/render/GraphicPort.ts
+++ b/src/render/GraphicPort.ts
@@ -10,8 +10,10 @@ import GraphicEdge from './GraphicEdge'
 import Renderer from './Renderer'
 import TextGraphicObject from './shapes/TextGraphicObject'
 import { TextJustification } from './shapes/TextJustification'
+
 /**
- * Points that are part of the GraghicNode.
+ * Graphic ports represent the input/output ports of a graphic node. They consist of a circle, which is instanced for performance and thus needs
+ * to be handled differently to regular meshes. They also consist of text that represents the parameter's name.
  */
 export default class GraphicPort extends GraphicObject {
   public currentEdge: GraphicEdge | undefined
@@ -219,16 +221,6 @@ export default class GraphicPort extends GraphicObject {
       ctx.graphicEdge.fromPort.port.connectedGraphicEdges.forEach((ge) => ge.setScheduledDeletion(false))
   }
 
-  /**
-   * Handles the double click event.
-   */
-  public onMouseDbClick(): void {}
-
-  /**
-   * Handles the mouse move event.
-   */
-  public onMouseMove(): void {}
-
   /**
    * Updates the position of the "from" and "to" of all edges.
    */
@@ -250,6 +242,19 @@ export default class GraphicPort extends GraphicObject {
    * @param graphicEdge - The edge to connect.
    */
   public connectEdge(graphicEdge: GraphicEdge) {
+    if (this.isInput() === true) {
+      graphicEdge.toPort = this
+      if (graphicEdge.edge !== undefined) {
+        graphicEdge.edge.toNode = this.port.graphicNode.node
+        graphicEdge.edge.toParameter = this.port.parameter
+      }
+    } else {
+      graphicEdge.fromPort = this
+      if (graphicEdge.edge !== undefined) {
+        graphicEdge.edge.fromNode = this.port.graphicNode.node
+        graphicEdge.edge.fromParameter = this.port.parameter
+      }
+    }
     if (graphicEdge.fromPort !== undefined && graphicEdge.toPort !== undefined) {
       graphicEdge.update(
         graphicEdge.fromPort.getSnapPosition(),
@@ -270,11 +275,13 @@ export default class GraphicPort extends GraphicObject {
    * @param graphicEdge - The graphic edge to disconnect.
    */
   public disconnectEdge(graphicEdge: GraphicEdge) {
-    const index = this.port.connectedGraphicEdges.indexOf(graphicEdge)
+    const index = this.port.connectedGraphicEdges.findIndex((ge) =>
+      ge.fromPort?.port.parameter.id.id === graphicEdge.fromPort?.port.parameter.id.id &&
+      ge.toPort?.port.parameter.id.id === graphicEdge.toPort?.port.parameter.id.id)
 
     if (index === -1) {
       NotificationManager.error('Could not find edge to disconnect')
-      return
+        return
     }
 
     this.port.connectedGraphicEdges.splice(index, 1)
@@ -286,16 +293,8 @@ export default class GraphicPort extends GraphicObject {
    * @returns A Vector3.
    */
   public getSnapPosition(): Vector3 {
-    const snap = this.getObjectByName('snap')
     const pos = new Vector3()
-
-    if (snap === undefined) {
-      this.getWorldPosition(pos)
-      return pos
-    }
-
-    snap.getWorldPosition(pos)
-    return pos
+    return this.getWorldPosition(pos)
   }
 
   /**
@@ -480,4 +479,14 @@ export default class GraphicPort extends GraphicObject {
       this.edgeExits(portA.port, portB.port)
     )
   }
+
+  /**
+   * Handles the double click event.
+   */
+  public onMouseDbClick(): void {}
+
+  /**
+   * Handles the mouse move event.
+   */
+  public onMouseMove(): void {}
 }
diff --git a/src/render/Renderer.ts b/src/render/Renderer.ts
index b527d9aa0de26e4602a1e4efe3aaa84d4497e434..adfa6bb6de46c5f4bdd0834f364f0c900ad48192 100644
--- a/src/render/Renderer.ts
+++ b/src/render/Renderer.ts
@@ -6,11 +6,12 @@ import type StateService from '@/services/StateService'
 import type TypeService from '@/services/TypeService'
 import type GraphicEdge from './GraphicEdge'
 import type GraphicNode from './GraphicNode'
+import type ControlSlottedGraphicNode from './graphicnodes/control/slotted/ControlSlottedGraphicNode'
 import type GraphicPort from './GraphicPort'
 import type InputEventContext from './input/InputEventContext'
 import type LoadedResources from './LoadedResources'
 import type Tool from './tools/Tool'
-import { GraphicObject, NodeType, type Tab } from '@/datatypes'
+import { type ControlNode, type ControlSlottedNode, GraphicObject, NodeType, type Tab } from '@/datatypes'
 import NotificationManager from '@/notification/NotificationManager'
 import {
   Color,
@@ -242,12 +243,32 @@ export default class Renderer {
     const elapsed = this.now - this.lastFrame
 
     this.stats.begin()
+    let skippedRender = false
+    let edgeOnFrameRequestRender = false
+    if (this.current !== undefined) {
+      for (let i = 0; i < this.current[0].statechart.graphicEdges.length; i++) {
+        const edge = this.current[0].statechart.graphicEdges[i]
+        edge.onFrame()
+        if (edge.scheduledDeletion === true) {
+          edgeOnFrameRequestRender = true
+        }
+      }
+    }
+
     if (elapsed > this.fpsInterval - this.fpsTolerance && this.loaded === true && this.current !== undefined && this.renderRequested === true) {
       this.lastFrame = this.now - (elapsed % this.fpsInterval)
       this.composer.render()
       this.frameCount++
 
       this.renderRequested = false
+    } else {
+      skippedRender = true
+    }
+
+    if (elapsed > this.fpsInterval - this.fpsTolerance && this.loaded === true && this.current !== undefined && skippedRender === true && edgeOnFrameRequestRender === true) {
+      this.lastFrame = this.now - (elapsed % this.fpsInterval)
+      this.composer.render()
+      this.frameCount++
     }
     this.stats.end()
   }
@@ -509,12 +530,6 @@ export default class Renderer {
       .getRenderStrategy()
       .generateGraphicEdges(tab.skill, tab.statechart.graphicNodes, this.statechartService)
 
-    for (const graphicEdge of tab.statechart.graphicEdges) {
-      scene.add(graphicEdge.assemble())
-      graphicEdge.fromPort?.updateColor()
-      graphicEdge.toPort?.updateColor()
-    }
-
     if (tab.skill.native === true) {
       return
     }
@@ -524,6 +539,47 @@ export default class Renderer {
         graphicPort.updateImplicitEdge()
       }
     }
+
+    // assign slots for slotted graphic nodes
+    for (let i = 0; i < tab.statechart.graphicNodes.length; i++) {
+      const graphicNode = tab.statechart.graphicNodes[i]
+      if (graphicNode.node.nodeType !== NodeType.CONTROL) {
+        continue
+      }
+
+      const controlNode = graphicNode.node as ControlNode
+      if (controlNode.isSlotted === false) {
+        continue
+      }
+
+      const slottedControlNode = controlNode as ControlSlottedNode
+      if (slottedControlNode.slot === undefined) {
+        continue
+      }
+
+      const idToCheck = slottedControlNode.slot.id
+      if (idToCheck === undefined) {
+        continue
+      }
+
+      const slot = tab.statechart.graphicNodes.find((graphicNode) => {
+        return graphicNode.node.id.id === idToCheck
+      })
+
+      if (slot === undefined) {
+        NotificationManager.error(`Could not find slotted node with id ${idToCheck}`)
+        continue
+      }
+
+      const slottedGraphicNode = graphicNode as ControlSlottedGraphicNode
+      slottedGraphicNode.slotGraphicNode(slot)
+    }
+
+    for (const graphicEdge of tab.statechart.graphicEdges) {
+      scene.add(graphicEdge.assemble())
+      graphicEdge.fromPort?.updateColor()
+      graphicEdge.toPort?.updateColor()
+    }
   }
 
   /**
@@ -552,10 +608,10 @@ export default class Renderer {
    * Computes the intersections on the pointer position.
    * @returns The intersections.
    */
-  private computeIntersects(): Intersection<Object3D>[] {
+  public computeIntersects(pos?: Vector2): Intersection<Object3D>[] {
     if (this.current === undefined) return []
 
-    this.raycaster.setFromCamera(this.pointer, this.camera)
+    this.raycaster.setFromCamera(pos ?? this.pointer, this.camera)
 
     const toCheck: Object3D[] = []
     // setup an instance of Frustum based on the projection and view matrix of camera
@@ -763,7 +819,7 @@ export default class Renderer {
       const c = candidate as GraphicObject
 
       if (c.name === 'instancedCircles' || c.name === 'instancedOutlines') break
-      if (c.name === 'background' || c.name === 'button-background') {
+      if (c.name === 'background' || c.name === 'button-background' || c.name === 'slot' || c.name === 'slotBackground') {
         this.hoveredPort = undefined
         return
       }
@@ -773,9 +829,8 @@ export default class Renderer {
     let closestPort: [GraphicObject, number] | undefined
 
     this.current[1].traverse((obj) => {
-      if ((obj as GraphicObject).isGraphicPort === true) {
-        const port = obj as GraphicObject
-
+      const port = obj as GraphicObject
+      if (port.isGraphicPort === true) {
         // get world position of port
         const portPos = new Vector3()
         port.getWorldPosition(portPos)
@@ -858,9 +913,10 @@ export default class Renderer {
     if (candidate) {
       while (candidate.parent && candidate.parent !== this.current[1]) {
         candidate = candidate.parent
+        const candidateGraphicsObj = candidate as GraphicObject
 
-        if ((candidate as GraphicObject).clickable) {
-          ;(candidate as GraphicObject).onMouseDbClick({
+        if (candidateGraphicsObj.clickable) {
+          candidateGraphicsObj.onMouseDbClick({
             button: event.button
           })
         }
diff --git a/src/render/graphicnodes/ControlFlowSubSkillGraphicNode.ts b/src/render/graphicnodes/ControlFlowSubSkillGraphicNode.ts
index e2444eed728c1a9a8b419646798b0fd3bc4554ae..a79f2dc753a649f3e56d6e6fe0301ef62b6bd795 100644
--- a/src/render/graphicnodes/ControlFlowSubSkillGraphicNode.ts
+++ b/src/render/graphicnodes/ControlFlowSubSkillGraphicNode.ts
@@ -15,6 +15,7 @@ import { TextJustification } from '../shapes/TextJustification'
 export default class ControlFlowSubSkillGraphicNode extends GraphicNode {
   private tempHeight = this.height
   private tempWidth = this.width
+  public slottable = true
 
   /**
    * Constructor for a default sub skill graphic node.
@@ -82,7 +83,9 @@ export default class ControlFlowSubSkillGraphicNode extends GraphicNode {
       new Color(Renderer.getCSSVar('--primary-color')),
       7,
       new Color(Renderer.getCSSVar('--node-outline-color')),
-      10
+      this.borderRadius,
+      false,
+      false
     ).assemble()
 
     background.name = 'background'
@@ -131,8 +134,8 @@ export default class ControlFlowSubSkillGraphicNode extends GraphicNode {
     const size = new Vector3()
     bounds.getSize(size)
 
-    const left = -size.x / 2 - 3.5
-    const right = size.x / 2 + 3.5
+    const left = -size.x / 2 + 3.5
+    const right = size.x / 2 - 3.5
     const top = size.y / 2
 
     for (let i = 0; i < this.inputs.length; i++) {
diff --git a/src/render/graphicnodes/DataFlowSubSkillNode.ts b/src/render/graphicnodes/DataFlowSubSkillNode.ts
index e5bece0c9afebc96cc588519b0f818a296b7df9f..5d6f4c47942824c286224f0cfa7254b9d50e7a39 100644
--- a/src/render/graphicnodes/DataFlowSubSkillNode.ts
+++ b/src/render/graphicnodes/DataFlowSubSkillNode.ts
@@ -15,6 +15,7 @@ import { TextJustification } from '../shapes/TextJustification'
 export default class DataFlowSubSkillNode extends GraphicNode {
   private tempHeight = this.height
   private tempWidth = this.width
+  public slottable = true
 
   /**
    * Constructor for a default sub skill graphic node.
@@ -85,7 +86,9 @@ export default class DataFlowSubSkillNode extends GraphicNode {
       new Color(Renderer.getCSSVar('--primary-color')),
       7,
       new Color(Renderer.getCSSVar('--node-outline-color')),
-      10
+      this.borderRadius,
+      false,
+      false
     ).assemble()
 
     background.name = 'background'
@@ -134,8 +137,8 @@ export default class DataFlowSubSkillNode extends GraphicNode {
     const size = new Vector3()
     bounds.getSize(size)
 
-    const left = -size.x / 2 - 3.5
-    const right = size.x / 2 + 3.5
+    const left = -size.x / 2 + 3.5
+    const right = size.x / 2 - 3.5
     const top = size.y / 2
 
     for (let i = 0; i < this.inputs.length; i++) {
diff --git a/src/render/graphicnodes/DefaultSubSkillGraphicNode.ts b/src/render/graphicnodes/DefaultSubSkillGraphicNode.ts
index d677f182a1f303c44ce6172e5453c799167b8360..98b63a43ecd292714cded99fdf0551165c372df7 100644
--- a/src/render/graphicnodes/DefaultSubSkillGraphicNode.ts
+++ b/src/render/graphicnodes/DefaultSubSkillGraphicNode.ts
@@ -16,6 +16,7 @@ import { TextJustification } from '../shapes/TextJustification'
 export default class DefaultSubSkillGraphicNode extends GraphicNode {
   private tempHeight = this.height
   private tempWidth = this.width
+  public slottable = true
 
   /**
    * Constructor for a default sub skill graphic node.
@@ -82,7 +83,9 @@ export default class DefaultSubSkillGraphicNode extends GraphicNode {
       new Color(Renderer.getCSSVar('--primary-color')),
       7,
       new Color(Renderer.getCSSVar('--node-outline-color')),
-      10
+      this.borderRadius,
+      false,
+      false
     ).assemble()
 
     background.name = 'background'
diff --git a/src/render/graphicnodes/ParameterGraphicNode.ts b/src/render/graphicnodes/ParameterGraphicNode.ts
index c0080d962d9a98cfb11816cd917b7aff867b6090..55a17ed853203dd82112318ec1473f26e7a2f3f1 100644
--- a/src/render/graphicnodes/ParameterGraphicNode.ts
+++ b/src/render/graphicnodes/ParameterGraphicNode.ts
@@ -91,7 +91,9 @@ export default class ParameterGraphicNode extends GraphicNode {
       this.color,
       7,
       new Color(Renderer.getCSSVar('--node-outline-color')),
-      10
+      this.borderRadius,
+      false,
+      false
     ).assemble()
 
     background.name = 'background'
@@ -122,8 +124,8 @@ export default class ParameterGraphicNode extends GraphicNode {
     const size = new Vector3()
     bounds.getSize(size)
 
-    const left = -size.x / 2 - 1.5
-    const right = size.x / 2 + 1.5
+    const left = -size.x / 2 + 3.5
+    const right = size.x / 2 - 3.5
 
     if (this.parameter.isInput) {
       result.inputs.push(new Vector3(right, 0, 10))
diff --git a/src/render/graphicnodes/control/AndMergerGraphicNode.ts b/src/render/graphicnodes/control/AndMergerGraphicNode.ts
index 4f6479734dd908b9c2249cb97a798ff60b64185e..d6278c716ef681f95e4a6c5270a6a82a023c04a3 100644
--- a/src/render/graphicnodes/control/AndMergerGraphicNode.ts
+++ b/src/render/graphicnodes/control/AndMergerGraphicNode.ts
@@ -1,6 +1,5 @@
 import {
   type ControlNode,
-  ControlType,
   GraphicObject,
   type Parameter,
   type Port
@@ -28,7 +27,7 @@ export default class AndMergerGraphicNode extends ControlGraphicNode {
    * @param node - The node this graphic node represents.
    */
   public constructor(node: ControlNode) {
-    super(node, ControlType.AND_MERGER)
+    super(node)
 
     this.color = new Color(Renderer.getCSSVar('--node-control-andmerger-color'))
     this.inputs = node.parameters.filter((p) => p.isInput === true)
@@ -90,7 +89,7 @@ export default class AndMergerGraphicNode extends ControlGraphicNode {
     reduceButton.assemble()
     this.add(reduceButton)
 
-    this.outline = background.outline
+    this.outline = background.outline as GraphicObject
     this.text = text
     this.expandButton = expandButton
     this.reduceButton = reduceButton
diff --git a/src/render/graphicnodes/control/ControlGraphicNode.ts b/src/render/graphicnodes/control/ControlGraphicNode.ts
index 7c80386163b21e3471747adfc3cda62d54e7c22b..af7748b39760f333ecf4641665d3f587332a42fe 100644
--- a/src/render/graphicnodes/control/ControlGraphicNode.ts
+++ b/src/render/graphicnodes/control/ControlGraphicNode.ts
@@ -1,7 +1,7 @@
 import type GraphicEdge from '@/render/GraphicEdge'
 import type GraphicPort from '@/render/GraphicPort'
 import type InputEventContext from '@/render/input/InputEventContext'
-import { type ControlNode, type ControlType, GraphicObject } from '@/datatypes'
+import { type ControlNode, GraphicObject } from '@/datatypes'
 import Rectangle from '@/render/shapes/Rectangle'
 import { Color, type Material, Vector3 } from 'three'
 import GraphicNode from '../../GraphicNode'
@@ -10,7 +10,6 @@ import GraphicNode from '../../GraphicNode'
  *
  */
 export default abstract class ControlGraphicNode extends GraphicNode {
-  public controlType: ControlType
   public width = 200
   public isButtonHovered = false
   protected portGap = 40
@@ -23,11 +22,9 @@ export default abstract class ControlGraphicNode extends GraphicNode {
   /**
    * Constructor for a control graphic node.
    * @param node - The node this graphic node represents.
-   * @param controlType - The control type of this control graphic node.
    */
-  public constructor(node: ControlNode, controlType: ControlType) {
+  public constructor(node: ControlNode) {
     super(node)
-    this.controlType = controlType
   }
 
   /**
@@ -75,7 +72,7 @@ export default abstract class ControlGraphicNode extends GraphicNode {
     )
 
     newBackground.assemble()
-    this.outline = newBackground.outline
+    this.outline = newBackground.outline as GraphicObject
     newBackground.name = 'background'
     // some fuckery to make the inside of the rectangle transparent
     ;(newBackground.inner!.material as Material).opacity = 0.0
diff --git a/src/render/graphicnodes/control/SplitterGraphicNode.ts b/src/render/graphicnodes/control/SplitterGraphicNode.ts
index 33eff08a692155d133027d87164f16a12a944e90..1e0449f5452a65b5ac99cd4889940bb6d6e99285 100644
--- a/src/render/graphicnodes/control/SplitterGraphicNode.ts
+++ b/src/render/graphicnodes/control/SplitterGraphicNode.ts
@@ -1,6 +1,5 @@
 import {
   type ControlNode,
-  ControlType,
   GraphicObject,
   type Parameter,
   type Port
@@ -28,7 +27,7 @@ export default class SplitterGraphicNode extends ControlGraphicNode {
    * @param node - The node that this graphic node represents.
    */
   public constructor(node: ControlNode) {
-    super(node, ControlType.SPLITTER)
+    super(node)
 
     this.color = new Color(Renderer.getCSSVar('--node-control-splitter-color'))
     this.inputs = node.parameters.filter((p) => p.isInput === true)
@@ -90,7 +89,7 @@ export default class SplitterGraphicNode extends ControlGraphicNode {
 
     this.add(reduceButton)
 
-    this.outline = background.outline
+    this.outline = background.outline as GraphicObject
     this.text = text
     this.expandButton = expandButton
     this.reduceButton = reduceButton
diff --git a/src/render/graphicnodes/control/slotted/ControlSlottedGraphicNode.ts b/src/render/graphicnodes/control/slotted/ControlSlottedGraphicNode.ts
new file mode 100644
index 0000000000000000000000000000000000000000..31adf10bc7c13dae5e3a5bf9fb8db0a6c3070ea8
--- /dev/null
+++ b/src/render/graphicnodes/control/slotted/ControlSlottedGraphicNode.ts
@@ -0,0 +1,33 @@
+import type { ControlSlottedNode, GraphicObject } from '@/datatypes'
+import type Rectangle from '@/render/shapes/Rectangle'
+import GraphicNode from '@/render/GraphicNode'
+import { Vector3 } from 'three'
+
+export default abstract class ControlSlottedGraphicNode extends GraphicNode {
+  public width = 354 // for highlight rects
+  public titleHeight = 40
+  public baseHeight = 80
+  public baseWidth = 354
+  public tempSlotHeight = 230
+  public slotHeight = 230
+  public slotPosition: Vector3 = new Vector3(0, 0, 0)
+  public slottedGraphicNode: GraphicNode | undefined
+  public ghostNode: Rectangle | undefined
+  public slotBackground: GraphicObject | undefined
+
+  public constructor(node: ControlSlottedNode) {
+    super(node)
+  }
+
+  public abstract assemble(): GraphicObject
+  public abstract slotGraphicNode(graphicNode: GraphicNode): void
+  public abstract slotGhostNode(graphicNode: GraphicNode): void
+  public abstract unslotGraphicNode(): void
+  public abstract unslotGhostNode(): void
+  public abstract resizeToFitSlotted(): void
+  public abstract getGraphicPortsPositions(): {
+    inputs: Vector3[]
+    outputs: Vector3[]
+  }
+  public abstract onMouseDbClick(): void
+}
diff --git a/src/render/graphicnodes/control/slotted/LoopRepeatGraphicNode.ts b/src/render/graphicnodes/control/slotted/LoopRepeatGraphicNode.ts
new file mode 100644
index 0000000000000000000000000000000000000000..c8911895c448a39f31196c8c298802f8f03caf5b
--- /dev/null
+++ b/src/render/graphicnodes/control/slotted/LoopRepeatGraphicNode.ts
@@ -0,0 +1,350 @@
+import type GraphicNode from '@/render/GraphicNode'
+import { type ControlSlottedNode, GraphicObject, type Port } from '@/datatypes'
+import NotificationManager from '@/notification/NotificationManager'
+import GraphicEdge from '@/render/GraphicEdge'
+import Renderer from '@/render/Renderer'
+import Rectangle from '@/render/shapes/Rectangle'
+import TextGraphicObject from '@/render/shapes/TextGraphicObject'
+import { TextJustification } from '@/render/shapes/TextJustification'
+import { Box3, Color, Vector2, Vector3 } from 'three'
+import ControlSlottedGraphicNode from './ControlSlottedGraphicNode'
+
+export default class LoopRepeatGraphicNode extends ControlSlottedGraphicNode {
+  public borderRadius: number[] = [10, 10, 0, 0]
+
+  // all components that need to be redrawn on slot/unslot
+  private title: GraphicObject | undefined
+  private titleBackground: GraphicObject | undefined
+  private titleSeparator: GraphicObject | undefined
+  private ghostEdge: GraphicEdge | undefined
+
+  public constructor(node: ControlSlottedNode) {
+    super(node)
+    this.inputs = node.parameters.filter((p) => p.isInput === true)
+    this.outputs = node.parameters.filter((p) => p.isInput === false)
+    this.color = new Color(Renderer.getCSSVar('--node-control-loop-repeat-color'))
+  }
+
+  public assemble(existingPorts?: Port[]): GraphicObject {
+    const title = new TextGraphicObject(
+      new Vector3(),
+      TextJustification.CENTER,
+      20,
+      this.node.name,
+      new Color(Renderer.getCSSVar('--node-text-color'))
+    )
+
+    title.onSyncAlways = () => {
+      this.statechartService?.getRenderer()?.requestRender()
+    }
+
+    title.assemble()
+    this.title = title
+
+    this.width = this.baseWidth
+    if (this.slottedGraphicNode !== undefined) {
+      this.width = this.slottedGraphicNode.width + 14
+    } else if (this.ghostNode !== undefined) {
+      this.width = this.ghostNode.width + 14
+    }
+
+    this.height = Math.max(this.inputs.length, this.outputs.length) * this.subSkillPortGap + this.baseHeight // used for highlight rects
+    this.titleHeight = this.height
+    this.tempSlotHeight = Math.max(this.titleHeight, this.slottedGraphicNode?.height ?? 0, this.ghostNode?.height ?? 0)
+
+    const titleBackground = new Rectangle(
+      this.width,
+      this.titleHeight,
+      new Color(Renderer.getCSSVar('--node-control-loop-repeat-color')),
+      7,
+      new Color(Renderer.getCSSVar('--node-outline-color')),
+      [10, 10, 0, 0],
+      false,
+      false
+    ).assemble()
+
+    this.titleBackground = titleBackground
+    titleBackground.name = 'background'
+
+    const titleSeparator = new Rectangle(
+      this.width - 42,
+      4,
+      new Color(Renderer.getCSSVar('--node-control-loop-repeat-color-dark')),
+      0,
+      new Color(),
+      2,
+      true
+    ).assemble()
+
+    this.titleSeparator = titleSeparator
+
+    const slotBackground = new Rectangle(
+      this.width,
+      this.tempSlotHeight + 14,
+      new Color(Renderer.getCSSVar('--node-control-loop-repeat-color')),
+      7,
+      new Color(Renderer.getCSSVar('--node-control-loop-repeat-color-dark')),
+      [0, 0, 17, 17],
+      false,
+      false
+    ).assemble()
+
+    this.slotBackground = slotBackground
+
+    slotBackground.children[0].name = 'slot'
+    slotBackground.name = 'slotBackground'
+    this.add(title, titleBackground, slotBackground)
+
+    slotBackground.materials.forEach((m) => m.opacity = 0.5)
+
+    title.position.set(0, this.titleHeight / 2 - this.subSkillTitleTopDist, 3)
+
+    titleSeparator.position.set(0, this.titleHeight / 2 - this.subSkillTitleSeparatorTopDist, 0.1)
+    this.add(title, titleSeparator)
+
+    const bounds = new Box3().setFromObject(titleBackground)
+    const size = new Vector3()
+    bounds.getSize(size)
+
+    const bottom = size.y / 2
+    slotBackground.position.set(0, -bottom - this.tempSlotHeight / 2 - 14, 3)
+
+    this.generateGraphicPorts(existingPorts)
+
+    for (let i = 0; i < this.graphicPorts.length; i++) {
+      const port = this.graphicPorts[i]
+      port.updateEdges()
+    }
+
+    return this
+  }
+
+  public slotGraphicNode(graphicNode: GraphicNode): void {
+    // assign slots
+    this.slottedGraphicNode = graphicNode
+
+    this.resizeToFitSlotted()
+    this.unslotGhostNode()
+
+    const node = this.node as ControlSlottedNode
+    node.slot = graphicNode.node.id
+    graphicNode.slot = this
+
+    const slot = this.slotBackground
+
+    if (slot === undefined) {
+      return
+    }
+
+    slot.add(graphicNode)
+
+    graphicNode.moveTo(new Vector2(0, 0), 5 - 1.5, true) // slotbackground itself is offset by 3 in z
+    this.slottedGraphicNode = graphicNode
+  }
+
+  public slotGhostNode(graphicNode: GraphicNode): void {
+    let slot = this.slotBackground
+    const renderer = this.statechartService?.getRenderer()
+    if (slot === undefined || renderer === undefined) {
+      return
+    }
+
+    const current = renderer.current
+
+    if (current === undefined) {
+      return
+    }
+
+    this.ghostNode = new Rectangle(
+      graphicNode.width,
+      graphicNode.height,
+      graphicNode.color,
+      7,
+      new Color(Renderer.getCSSVar('--node-outline-color')),
+      [0, 0, 10, 10],
+      true,
+      false
+    )
+
+    this.resizeToFitSlotted()
+
+    slot = this.slotBackground
+
+    if (slot === undefined) {
+      return
+    }
+
+    this.ghostNode.assemble()
+
+    const title = new TextGraphicObject(
+      new Vector3(),
+      TextJustification.CENTER,
+      20,
+      graphicNode.node.name,
+      new Color(Renderer.getCSSVar('--node-text-color'))
+    ).assemble()
+
+    const titleSeparator = new Rectangle(
+      graphicNode.width - 42,
+      4,
+      new Color(Renderer.getCSSVar('--primary-color-dark')),
+      0,
+      new Color(),
+      2,
+      true
+    ).assemble()
+
+    this.ghostNode.add(title, titleSeparator)
+    title.materials.forEach((m) => {
+      m.transparent = true
+      m.opacity = 0.5
+    })
+    title.position.set(0, graphicNode.height / 2 - graphicNode.subSkillTitleTopDist, 3)
+    titleSeparator.position.set(0, graphicNode.height / 2 - graphicNode.subSkillTitleSeparatorTopDist, 0.1)
+    titleSeparator.materials.forEach((m) => {
+      m.transparent = true
+      m.opacity = 0.5
+    })
+
+    this.ghostNode.materials.forEach((m) => {
+      m.transparent = true
+      m.opacity = 0.5
+    })
+    slot.add(this.ghostNode)
+    this.ghostNode.position.set(0, 0, slot.position.z)
+
+    // if there is a connection to the start port of the slotted node, create a ghost edge going from the original port to the start port of the loop
+    const startPort = graphicNode.graphicPorts.find((p) => p.port.parameter.name.toLowerCase() === 'start') // Scuffed?
+    if (startPort === undefined) {
+      console.error('Could not find start port')
+      return
+    }
+
+    if (startPort.port.connectedGraphicEdges.length === 0) {
+      return
+    }
+
+    const endPort = this.graphicPorts.find((p) => p.port.parameter.name.toLowerCase() === 'start')
+
+    if (endPort === undefined) {
+      console.error('Could not find end port')
+      return
+    }
+
+    const connectedGRaphicEdge = startPort.port.connectedGraphicEdges[0]
+
+    const startPortPos = connectedGRaphicEdge.fromPort?.getSnapPosition()
+    const endPortPos = endPort.getSnapPosition()
+
+    if (startPortPos === undefined || endPortPos === undefined) {
+      return
+    }
+
+    const startColor = startPort.port.graphicNode.color.clone()
+    const endColor = endPort.port.graphicNode.color.clone()
+
+    this.ghostEdge = new GraphicEdge(startPortPos, endPortPos, startColor, endColor)
+
+    const scene = current[1]
+    scene.add(this.ghostEdge.assemble())
+
+    this.ghostEdge.line!.material.transparent = true
+    this.ghostEdge.line!.material.opacity = 0.1
+
+    renderer.requestRender()
+  }
+
+  public unslotGraphicNode(): void {
+    if (this.slottedGraphicNode === undefined || this.slotBackground === undefined) {
+      return
+    }
+
+    this.width = this.baseWidth
+    this.tempSlotHeight = this.baseHeight
+
+    const position = this.localToWorld(this.slotBackground.position.clone())
+    this.slottedGraphicNode.removeFromParent()
+    const ctrlSlottedNode = this.node as ControlSlottedNode
+    ctrlSlottedNode.slot = undefined
+
+    const renderer = this.statechartService?.getRenderer()
+    if (renderer !== undefined && renderer.current !== undefined) {
+      renderer.current[1].add(this.slottedGraphicNode)
+      this.slottedGraphicNode.moveTo(new Vector2(position.x, position.y), renderer.getNextNodeZ())
+    }
+
+    this.slottedGraphicNode = undefined
+    this.resizeToFitSlotted()
+    this.statechartService?.getRenderer()?.requestRender()
+  }
+
+  public unslotGhostNode(): void {
+    if (this.ghostNode === undefined) {
+      return
+    }
+    GraphicObject.free(this.ghostNode)
+    this.ghostNode = undefined
+    if (this.ghostEdge !== undefined) {
+      this.ghostEdge.removeFromParent()
+      this.ghostEdge.remove()
+      GraphicObject.free(this.ghostEdge)
+      this.ghostEdge = undefined
+    }
+    this.resizeToFitSlotted()
+  }
+
+  public resizeToFitSlotted() {
+    if (this.titleBackground === undefined || this.titleSeparator === undefined || this.slotBackground === undefined || this.title === undefined) {
+      return
+    }
+
+    GraphicObject.free(this.title)
+    GraphicObject.free(this.titleBackground)
+    GraphicObject.free(this.titleSeparator)
+    GraphicObject.free(this.slotBackground)
+
+    this.deleteInstancedCircles()
+
+    for (let i = this.graphicPorts.length - 1; i >= 0; i--) {
+      const gp = this.graphicPorts[i]
+      GraphicObject.free(gp)
+    }
+
+    this.assemble(this.graphicPorts.flatMap((p) => p.port))
+  }
+
+  public getGraphicPortsPositions(): { inputs: Vector3[], outputs: Vector3[] } {
+    const result: { inputs: Vector3[], outputs: Vector3[] } = { inputs: [], outputs: [] }
+
+    const background = this.getObjectByName('background')
+    if (background === undefined) {
+      NotificationManager.error('Could not find bounds')
+      return result
+    }
+    const bounds = new Box3().setFromObject(background)
+    const size = new Vector3()
+    bounds.getSize(size)
+
+    const left = -size.x / 2 + 3.5
+    const right = size.x / 2 - 3.5
+    const top = size.y / 2
+
+    for (let i = 0; i < this.inputs.length; i++) {
+      const pos = new Vector3(left, top - this.subSkillPortTopDist - this.subSkillPortGap * i, 10)
+
+      result.inputs.push(pos)
+    }
+
+    for (let i = 0; i < this.outputs.length; i++) {
+      const pos = new Vector3(right, top - this.subSkillPortTopDist - this.subSkillPortGap * i, 10)
+
+      result.outputs.push(pos)
+    }
+
+    return result
+  }
+
+  public onMouseDbClick(): void {
+    throw new Error('Method not implemented.')
+  }
+}
diff --git a/src/render/shapes/Circle.ts b/src/render/shapes/Circle.ts
index 62c494680fe1c48f47a8521cb5a046ea6c2620d9..ea9026c871b24db02f614c1098a34361fac556bb 100644
--- a/src/render/shapes/Circle.ts
+++ b/src/render/shapes/Circle.ts
@@ -62,7 +62,7 @@ export default class Circle extends GraphicObject {
    * @returns This graphic object.
    */
   public assemble(): GraphicObject {
-    const geometry = new CircleGeometry(this.radius, 16)
+    const geometry = new CircleGeometry(this.radius, 32)
     const material = new MeshBasicMaterial({ color: this.color })
     const circle = new Mesh(geometry, material)
 
diff --git a/src/render/shapes/Rectangle.ts b/src/render/shapes/Rectangle.ts
index 0ecfebeea92d678c3c1e16ed17c4e68453681941..cd27db3b41f20c945dcb46831bb5fddbdd1990c8 100644
--- a/src/render/shapes/Rectangle.ts
+++ b/src/render/shapes/Rectangle.ts
@@ -1,5 +1,6 @@
 import { Color, Mesh, MeshBasicMaterial, Shape, ShapeGeometry } from 'three'
 import GraphicObject from '../GraphicObject'
+import Outline from './Outline'
 
 /**
  * A simple rectangle.
@@ -14,7 +15,7 @@ export default class Rectangle extends GraphicObject {
   public readonly isComposed = false
 
   public inner: Mesh | undefined
-  public outline: Rectangle | undefined
+  public outline: Mesh | GraphicObject | undefined
 
   /**
    * Constructor for a rectangle. The object is not immediately assembled and requires
@@ -24,7 +25,7 @@ export default class Rectangle extends GraphicObject {
    * @param color - The color of the rectangle.
    * @param outlineWidth - The width of the outline.
    * @param outlineColor - The color of the outline.
-   * @param radius - The radius of the corners.
+   * @param radius - The radius of the corners. It can be a single number or an array of 4 numbers. If it is an array, the order is top-left, top-right, bottom-right, bottom-left.
    */
   public constructor(
     public readonly width: number,
@@ -32,8 +33,9 @@ export default class Rectangle extends GraphicObject {
     public readonly color: Color,
     public readonly outlineWidth = 0,
     public readonly outlineColor = new Color('white'),
-    public readonly radius = 0,
-    public readonly ignoreRaycast = false
+    public readonly radius: number | number[] = 0,
+    public readonly ignoreRaycast = false,
+    public readonly legacyOutline = true
   ) {
     super()
     this.name = 'rectangle'
@@ -73,63 +75,111 @@ export default class Rectangle extends GraphicObject {
    * @returns This graphic object.
    */
   public assemble(): GraphicObject {
+    const rectangle = this.generateRectangle(this.width, this.height, this.getRadius(), this.color)
+
+    this.inner = rectangle[0]
+    this.meshes.push(rectangle[0])
+
+    if (Array.isArray(rectangle[0].material)) {
+      this.materials.push(...rectangle[0].material)
+    } else {
+      this.materials.push(rectangle[0].material)
+    }
+
+    if (this.outlineWidth > 0) {
+      if (this.legacyOutline === false) {
+        const radius = this.getRadius()
+        const outline = this.generateRectangle(this.width + 2 * this.outlineWidth, this.height + 2 * this.outlineWidth, radius.map((r) => r > 0 ? r + this.outlineWidth : 0), this.outlineColor)
+
+        this.outline = outline[0]
+        this.meshes.push(outline[0])
+
+        if (Array.isArray(outline[0].material)) {
+          this.materials.push(...outline[0].material)
+        } else {
+          this.materials.push(outline[0].material)
+        }
+
+        this.add(outline[0])
+        outline[0].translateZ(-1)
+      } else {
+        const outline = new Outline(
+          rectangle[1].getPoints(),
+          this.position.z - 1.1,
+          this.outlineWidth,
+          this.outlineColor
+        )
+        outline.assemble()
+        this.add(outline)
+
+        this.outline = outline
+      }
+    }
+
+    this.add(rectangle[0])
+
+    return this
+  }
+
+  private generateRectangle(width: number, height: number, radius: number[], color: Color): [Mesh, Shape] {
     const rectShape = new Shape()
     const x = this.position.x
     const y = this.position.y
-    const width = this.width
-    const height = this.height
-    const radius = this.radius
 
     // Adjust x and y to be the center of the rectangle
     const centerX = x - width / 2
     const centerY = y - height / 2
-    rectShape.moveTo(centerX, centerY + radius)
-    rectShape.lineTo(centerX, centerY + height - radius)
-    rectShape.quadraticCurveTo(centerX, centerY + height, centerX + radius, centerY + height)
-    rectShape.lineTo(centerX + width - radius, centerY + height)
-    rectShape.quadraticCurveTo(
-      centerX + width,
-      centerY + height,
-      centerX + width,
-      centerY + height - radius
-    )
-    rectShape.lineTo(centerX + width, centerY + radius)
-    rectShape.quadraticCurveTo(centerX + width, centerY, centerX + width - radius, centerY)
-    rectShape.lineTo(centerX + radius, centerY)
-    rectShape.quadraticCurveTo(centerX, centerY, centerX, centerY + radius)
+
+    rectShape.moveTo(centerX, centerY + radius[3]) // Start at bottom left corner
+
+    // Top-left corner
+    rectShape.lineTo(centerX, centerY + height - radius[0])
+    if (radius[0] > 0) {
+      rectShape.quadraticCurveTo(centerX, centerY + height, centerX + radius[0], centerY + height)
+    }
+
+    // Top edge
+    if (radius[1] > 0) {
+      rectShape.lineTo(centerX + width - radius[1], centerY + height)
+      rectShape.quadraticCurveTo(centerX + width, centerY + height, centerX + width, centerY + height - radius[1])
+    } else {
+      rectShape.lineTo(centerX + width, centerY + height)
+    }
+
+    // Right edge
+    rectShape.lineTo(centerX + width, centerY + radius[2])
+    if (radius[2] > 0) {
+      rectShape.quadraticCurveTo(centerX + width, centerY, centerX + width - radius[2], centerY)
+    }
+
+    // Bottom edge
+    rectShape.lineTo(centerX + radius[3], centerY)
+    if (radius[3] > 0) {
+      rectShape.quadraticCurveTo(centerX, centerY, centerX, centerY + radius[3])
+    }
 
     const geometry = new ShapeGeometry(rectShape)
     geometry.computeBoundingBox()
     geometry.center()
-    const material = new MeshBasicMaterial({ color: this.color, transparent: true })
+    const material = new MeshBasicMaterial({ color, transparent: true })
     const mesh = new Mesh(geometry, material)
 
     if (this.ignoreRaycast === true) {
       mesh.layers.set(1)
     }
 
-    if (this.outlineWidth > 0) {
-      const outline = new Rectangle(
-        this.width + this.outlineWidth * 2,
-        this.height + this.outlineWidth * 2,
-        this.outlineColor,
-        0,
-        this.outlineColor,
-        this.radius + this.outlineWidth
-      )
-      outline.assemble()
-      this.add(outline)
-      outline.position.set(0, 0, -1)
-
-      this.outline = outline
-    }
-
-    this.meshes.push(mesh)
-    this.materials.push(material)
-    this.add(mesh)
-
-    this.inner = mesh
+    return [mesh, rectShape]
+  }
 
-    return this
+  private getRadius(): number[] {
+    if (Array.isArray(this.radius)) {
+      if (this.radius.length === 4) {
+        return this.radius
+      } else {
+        return [this.radius[0], this.radius[0], this.radius[0], this.radius[0]]
+      }
+    } else {
+      return [this.radius, this.radius, this.radius, this.radius]
+    }
   }
 }
diff --git a/src/render/strategies/ControlRenderStrategy.ts b/src/render/strategies/ControlRenderStrategy.ts
index 75754b728ca6ec23c84fcc5e2c750f80f4137526..b011fbe28ce8adebb9e41ac89b3e199f4e0ff24f 100644
--- a/src/render/strategies/ControlRenderStrategy.ts
+++ b/src/render/strategies/ControlRenderStrategy.ts
@@ -2,6 +2,7 @@ import type GraphicNode from '../GraphicNode'
 import type IRenderStrategy from './IRenderStrategy'
 import {
   type ControlNode,
+  type ControlSlottedNode,
   ControlType,
   type Node,
   NodeType,
@@ -15,9 +16,9 @@ import NotificationManager from '@/notification/NotificationManager'
 import { Vector3 } from 'three'
 import GraphicEdge from '../GraphicEdge'
 import AndMergerGraphicNode from '../graphicnodes/control/AndMergerGraphicNode'
+import LoopRepeatGraphicNode from '../graphicnodes/control/slotted/LoopRepeatGraphicNode'
 import SplitterGraphicNode from '../graphicnodes/control/SplitterGraphicNode'
 import ControlFlowSubSkillGraphicNode from '../graphicnodes/ControlFlowSubSkillGraphicNode'
-import DefaultSubSkillGraphicNode from '../graphicnodes/DefaultSubSkillGraphicNode'
 import ParameterGraphicNode from '../graphicnodes/ParameterGraphicNode'
 
 /**
@@ -62,6 +63,7 @@ export class ControlRenderStrategy implements IRenderStrategy {
    * Generate a graphic node for the control render strategy.
    * @param node - The node that the generated graphic node represents.
    * @returns The generated graphic node.
+   * @throws Error if the node type is not supported.
    */
   public generateGraphicNode(node: Node): GraphicNode {
     switch (node.nodeType) {
@@ -78,12 +80,14 @@ export class ControlRenderStrategy implements IRenderStrategy {
             return new SplitterGraphicNode(control)
           case ControlType.AND_MERGER:
             return new AndMergerGraphicNode(control)
+          case ControlType.LOOP_REPEAT:
+            return new LoopRepeatGraphicNode(control as ControlSlottedNode)
           default:
-            return new SplitterGraphicNode(control)
+            throw new Error('Unsupported control type.')
         }
       }
       default: {
-        return new DefaultSubSkillGraphicNode(node as SubSkill)
+        throw new Error('Unsupported node type.')
       }
     }
   }
diff --git a/src/render/strategies/DataRenderStrategy.ts b/src/render/strategies/DataRenderStrategy.ts
index 8dcd3282974a724d7ae4087ab247e05387c4475b..d774439e1382e648c16abda3736b3f410801410d 100644
--- a/src/render/strategies/DataRenderStrategy.ts
+++ b/src/render/strategies/DataRenderStrategy.ts
@@ -2,6 +2,7 @@ import type GraphicNode from '../GraphicNode'
 import type IRenderStrategy from './IRenderStrategy'
 import {
   type ControlNode,
+  type ControlSlottedNode,
   ControlType,
   type Node,
   NodeType,
@@ -15,6 +16,7 @@ import NotificationManager from '@/notification/NotificationManager'
 import { Vector3 } from 'three'
 import GraphicEdge from '../GraphicEdge'
 import AndMergerGraphicNode from '../graphicnodes/control/AndMergerGraphicNode'
+import LoopRepeatGraphicNode from '../graphicnodes/control/slotted/LoopRepeatGraphicNode'
 import SplitterGraphicNode from '../graphicnodes/control/SplitterGraphicNode'
 import DataFlowSubSkillNode from '../graphicnodes/DataFlowSubSkillNode'
 import ParameterGraphicNode from '../graphicnodes/ParameterGraphicNode'
@@ -62,6 +64,7 @@ export class DataRenderStrategy implements IRenderStrategy {
    * Generate a graphic node for the control render strategy.
    * @param node - The node that the generated graphic node represents.
    * @returns The generated graphic node.
+   * @throws Error if the node type is not supported.
    */
   public generateGraphicNode(node: Node): GraphicNode {
     switch (node.nodeType) {
@@ -78,12 +81,14 @@ export class DataRenderStrategy implements IRenderStrategy {
             return new SplitterGraphicNode(control)
           case ControlType.AND_MERGER:
             return new AndMergerGraphicNode(control)
+          case ControlType.LOOP_REPEAT:
+            return new LoopRepeatGraphicNode(control as ControlSlottedNode)
           default:
-            return new SplitterGraphicNode(control)
+            throw new Error('Unsupported control type.')
         }
       }
       default: {
-        return new DataFlowSubSkillNode(node as SubSkill)
+        throw new Error('Unsupported node type.')
       }
     }
   }
diff --git a/src/render/strategies/DefaultRenderStrategy.ts b/src/render/strategies/DefaultRenderStrategy.ts
index d2320e5d044d8da8f0d4c423a6292864e4637f06..9cf24a301262a101e48dc66f96c3db120a5310f4 100644
--- a/src/render/strategies/DefaultRenderStrategy.ts
+++ b/src/render/strategies/DefaultRenderStrategy.ts
@@ -2,6 +2,7 @@ import type GraphicNode from '../GraphicNode'
 import type IRenderStrategy from './IRenderStrategy'
 import {
   type ControlNode,
+  type ControlSlottedNode,
   ControlType,
   type Node,
   NodeType,
@@ -15,6 +16,7 @@ import NotificationManager from '@/notification/NotificationManager'
 import { Vector3 } from 'three'
 import GraphicEdge from '../GraphicEdge'
 import AndMergerGraphicNode from '../graphicnodes/control/AndMergerGraphicNode'
+import LoopRepeatGraphicNode from '../graphicnodes/control/slotted/LoopRepeatGraphicNode'
 import SplitterGraphicNode from '../graphicnodes/control/SplitterGraphicNode'
 import DefaultSubSkillGraphicNode from '../graphicnodes/DefaultSubSkillGraphicNode'
 import ParameterGraphicNode from '../graphicnodes/ParameterGraphicNode'
@@ -55,6 +57,7 @@ export class DefaultRenderStrategy implements IRenderStrategy {
    * Generate a graphic node for the default render strategy.
    * @param node - The node that the generated graphic node represents.
    * @returns The generated graphic node.
+   * @throws Error if the node type is not supported.
    */
   public generateGraphicNode(node: Node): GraphicNode {
     switch (node.nodeType) {
@@ -71,12 +74,14 @@ export class DefaultRenderStrategy implements IRenderStrategy {
             return new SplitterGraphicNode(control)
           case ControlType.AND_MERGER:
             return new AndMergerGraphicNode(control)
+          case ControlType.LOOP_REPEAT:
+            return new LoopRepeatGraphicNode(control as ControlSlottedNode)
           default:
-            return new SplitterGraphicNode(control)
+            throw new Error('Unsupported control type.')
         }
       }
       default: {
-        return new DefaultSubSkillGraphicNode(node as SubSkill)
+        throw new Error('Unsupported node type.')
       }
     }
   }
diff --git a/src/render/tools/DefaultTool.ts b/src/render/tools/DefaultTool.ts
index 6691a4ddb7f17a5e7ae8b2adaee54066e5c575c6..c7b822ba3a6246f9a6a7500d9069045b2a27650a 100644
--- a/src/render/tools/DefaultTool.ts
+++ b/src/render/tools/DefaultTool.ts
@@ -70,25 +70,13 @@ export default class DefaultTool extends Tool {
 
     const highest = GraphicObject.getHighestParent(ctx.object)
 
-    // sort all nodes by z
-    const nodes = this.renderer.current[0].statechart.graphicNodes.sort((a, b) => {
-      return a.position.z - b.position.z
-    })
-
     if (highest.isGraphicNode === true) {
-      // find index of highest
-      const index = nodes.findIndex((node) => node.node.id.id === (highest as GraphicNode).node.id.id)
-      const greatestZ = nodes[nodes.length - 1].position.z
-      nodes[index].position.z = greatestZ
-      nodes[index].updateGizmos()
-
-      // reduce z of all nodes after index by 20
-      for (let i = index + 1; i < nodes.length; i++) {
-        nodes[i].position.z -= 20
-        nodes[i].updateGizmos()
-      }
+      const graphicNode = highest as GraphicNode
+      graphicNode.bringForward()
     }
 
+    const nodes = this.renderer.current[0].statechart.graphicNodes
+
     if (ctx.object.isGraphicNode === true) {
       const node = ctx.object as GraphicNode
       this.selectedNodes = []
@@ -102,7 +90,8 @@ export default class DefaultTool extends Tool {
         node.setSelected(true, true)
       }
 
-      for (const node of nodes) {
+      for (let i = 0; i < nodes.length; i++) {
+        const node = nodes[i]
         if (node.isSelected() === true) {
           this.selectedNodes.push(node)
         }
diff --git a/src/services/StatechartService.ts b/src/services/StatechartService.ts
index b009f6a18e9aac141d3d3febb25d47c982ace5fc..5e946e80013ff8d4a708d02fa2836d3fe234f786 100644
--- a/src/services/StatechartService.ts
+++ b/src/services/StatechartService.ts
@@ -46,6 +46,7 @@ import TypeService from './TypeService'
 export default class StatechartService {
   private draggedNode: Node | undefined
 
+  // Node, icon, color unhovered, color hovered
   private controlNodes: [ControlNode, string, string, string][] = []
 
   /**
@@ -100,7 +101,8 @@ export default class StatechartService {
               isInput: false
             }
           ],
-          description: 'Splits an incoming Event connection into two.'
+          description: 'Splits an incoming Event connection into two.',
+          isSlotted: false
         },
         'split',
         '--node-control-splitter-color',
@@ -142,11 +144,82 @@ export default class StatechartService {
               isInput: false
             }
           ],
-          description: 'Merges two Event type connections into one.'
+          description: 'Merges two Event type connections into one.',
+          isSlotted: false
         },
         'merge',
         '--node-control-andmerger-color',
         '--node-control-andmerger-color-dark'
+      ],
+      [
+        {
+          nodeType: NodeType.CONTROL,
+          id: { id: 'LOOP_REPEAT' },
+          name: 'Loop Repeat',
+          position: { x: 0, y: 0 },
+          controlType: ControlType.LOOP_REPEAT,
+          parameters: [
+            {
+              id: { id: '0' },
+              name: 'Start',
+              description: 'Start',
+              type: this.typeService.getEventType(),
+              required: true,
+              values: [],
+              isInput: true
+            },
+            {
+              id: { id: '1' },
+              name: 'Iterations',
+              description: 'Number of iterations',
+              type: this.typeService.getIntegerType(),
+              required: true,
+              values: [],
+              isInput: true
+            },
+            {
+              id: { id: '2' },
+              name: 'Break',
+              description: 'Break',
+              type: this.typeService.getEventType(),
+              required: false,
+              values: [],
+              isInput: true
+            },
+            {
+              id: { id: '3' },
+              name: 'Success',
+              description: 'Success',
+              type: this.typeService.getEventType(),
+              required: false,
+              values: [],
+              isInput: false
+            },
+            {
+              id: { id: '4' },
+              name: 'Failure',
+              description: 'Failure',
+              type: this.typeService.getEventType(),
+              required: false,
+              values: [],
+              isInput: false
+            },
+            {
+              id: { id: '5' },
+              name: 'Abort',
+              description: 'Abort',
+              type: this.typeService.getEventType(),
+              required: false,
+              values: [],
+              isInput: false
+            }
+          ],
+          description: 'Repeats the contained subskill a given number of times.',
+          isSlotted: true
+        },
+        'loop',
+        '--node-control-loop-repeat-color',
+        '--node-control-loop-repeat-color-dark'
       ]
     ]
   }
diff --git a/src/services/TypeService.ts b/src/services/TypeService.ts
index 5bb39815d4318e4ae938b21d45d92f51f17e787c..95384467206a6e3b0d77c003f27d6ae130a42a6d 100644
--- a/src/services/TypeService.ts
+++ b/src/services/TypeService.ts
@@ -518,6 +518,16 @@ export default class TypeService {
     return stringType
   }
 
+  public getIntegerType(): BaseType {
+    const integerType = this.dataCollector
+      .getTypes()
+      .find((type) => type.name.toUpperCase() === 'INTEGER')
+    if (!integerType) {
+      throw new Error('Integer type not found')
+    }
+    return integerType
+  }
+
   /**
    * Getter for the description of a type.
    * @param skillId - The skill id.