diff --git a/invokeai/frontend/web/src/features/controlLayers/components/ToolChooser.tsx b/invokeai/frontend/web/src/features/controlLayers/components/ToolChooser.tsx
index 706d51b74c..b9b6c8ca84 100644
--- a/invokeai/frontend/web/src/features/controlLayers/components/ToolChooser.tsx
+++ b/invokeai/frontend/web/src/features/controlLayers/components/ToolChooser.tsx
@@ -5,6 +5,7 @@ import { BrushToolButton } from 'features/controlLayers/components/BrushToolButt
 import { EraserToolButton } from 'features/controlLayers/components/EraserToolButton';
 import { MoveToolButton } from 'features/controlLayers/components/MoveToolButton';
 import { RectToolButton } from 'features/controlLayers/components/RectToolButton';
+import { TransformToolButton } from 'features/controlLayers/components/TransformToolButton';
 import { ViewToolButton } from 'features/controlLayers/components/ViewToolButton';
 import { useCanvasDeleteLayerHotkey } from 'features/controlLayers/hooks/useCanvasDeleteLayerHotkey';
 import { useCanvasResetLayerHotkey } from 'features/controlLayers/hooks/useCanvasResetLayerHotkey';
@@ -21,6 +22,7 @@ export const ToolChooser: React.FC = () => {
         <EraserToolButton />
         <RectToolButton />
         <MoveToolButton />
+        <TransformToolButton />
         <ViewToolButton />
         <BboxToolButton />
       </ButtonGroup>
diff --git a/invokeai/frontend/web/src/features/controlLayers/components/TransformToolButton.tsx b/invokeai/frontend/web/src/features/controlLayers/components/TransformToolButton.tsx
new file mode 100644
index 0000000000..e8ac2e2577
--- /dev/null
+++ b/invokeai/frontend/web/src/features/controlLayers/components/TransformToolButton.tsx
@@ -0,0 +1,35 @@
+import { IconButton } from '@invoke-ai/ui-library';
+import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
+import { toolChanged } from 'features/controlLayers/store/canvasV2Slice';
+import { memo, useCallback } from 'react';
+import { useHotkeys } from 'react-hotkeys-hook';
+import { useTranslation } from 'react-i18next';
+import { PiResizeBold } from 'react-icons/pi';
+
+export const TransformToolButton = memo(() => {
+  const { t } = useTranslation();
+  const dispatch = useAppDispatch();
+  const isSelected = useAppSelector((s) => s.canvasV2.tool.selected === 'transform');
+  const isDisabled = useAppSelector(
+    (s) => s.canvasV2.selectedEntityIdentifier === null || s.canvasV2.session.isStaging
+  );
+
+  const onClick = useCallback(() => {
+    dispatch(toolChanged('transform'));
+  }, [dispatch]);
+
+  useHotkeys(['ctrl+t', 'meta+t'], onClick, { enabled: !isDisabled }, [isDisabled, onClick]);
+
+  return (
+    <IconButton
+      aria-label={`${t('unifiedCanvas.transform')} (Ctrl+T)`}
+      tooltip={`${t('unifiedCanvas.transform')} (Ctrl+T)`}
+      icon={<PiResizeBold />}
+      variant={isSelected ? 'solid' : 'outline'}
+      onClick={onClick}
+      isDisabled={isDisabled}
+    />
+  );
+});
+
+TransformToolButton.displayName = 'TransformToolButton';
diff --git a/invokeai/frontend/web/src/features/controlLayers/konva/CanvasLayer.ts b/invokeai/frontend/web/src/features/controlLayers/konva/CanvasLayer.ts
index df8392c4f5..e3073df65b 100644
--- a/invokeai/frontend/web/src/features/controlLayers/konva/CanvasLayer.ts
+++ b/invokeai/frontend/web/src/features/controlLayers/konva/CanvasLayer.ts
@@ -14,6 +14,7 @@ export class CanvasLayer {
   static NAME_PREFIX = 'layer';
   static LAYER_NAME = `${CanvasLayer.NAME_PREFIX}_layer`;
   static TRANSFORMER_NAME = `${CanvasLayer.NAME_PREFIX}_transformer`;
+  static INTERACTION_RECT_NAME = `${CanvasLayer.NAME_PREFIX}_interaction-rect`;
   static GROUP_NAME = `${CanvasLayer.NAME_PREFIX}_group`;
   static OBJECT_GROUP_NAME = `${CanvasLayer.NAME_PREFIX}_object-group`;
   static BBOX_NAME = `${CanvasLayer.NAME_PREFIX}_bbox`;
@@ -26,13 +27,15 @@ export class CanvasLayer {
 
   konva: {
     layer: Konva.Layer;
-    bbox: Konva.Rect;
     group: Konva.Group;
+    bbox: Konva.Rect;
+
     objectGroup: Konva.Group;
     transformer: Konva.Transformer;
+    interactionRect: Konva.Rect;
   };
   objects: Map<string, CanvasBrushLine | CanvasEraserLine | CanvasRect | CanvasImage>;
-  bbox: Rect | null;
+  bbox: Rect;
 
   getBbox = debounce(this._getBbox, 300);
 
@@ -41,38 +44,63 @@ export class CanvasLayer {
     this.manager = manager;
     this.konva = {
       layer: new Konva.Layer({ id: this.id, name: CanvasLayer.LAYER_NAME, listening: false }),
-      group: new Konva.Group({ name: CanvasLayer.GROUP_NAME, listening: false, draggable: true }),
+      group: new Konva.Group({ name: CanvasLayer.GROUP_NAME, listening: true, draggable: true }),
       bbox: new Konva.Rect({
         listening: false,
+        draggable: false,
         name: CanvasLayer.BBOX_NAME,
         stroke: 'hsl(200deg 76% 59%)', // invokeBlue.400
-        fill: '',
         perfectDrawEnabled: false,
         strokeHitEnabled: false,
       }),
       objectGroup: new Konva.Group({ name: CanvasLayer.OBJECT_GROUP_NAME, listening: false }),
       transformer: new Konva.Transformer({
         name: CanvasLayer.TRANSFORMER_NAME,
-        shouldOverdrawWholeArea: true,
         draggable: false,
-        dragDistance: 0,
         enabledAnchors: ['top-left', 'top-right', 'bottom-left', 'bottom-right'],
         rotateEnabled: false,
         flipEnabled: false,
         listening: false,
       }),
+      interactionRect: new Konva.Rect({
+        name: CanvasLayer.INTERACTION_RECT_NAME,
+        listening: false,
+        draggable: false,
+        fill: 'rgba(255,0,0,0.5)',
+      }),
     };
 
-    this.konva.group.add(this.konva.objectGroup);
-    this.konva.group.add(this.konva.bbox);
     this.konva.layer.add(this.konva.group);
+    this.konva.layer.add(this.konva.transformer);
+    this.konva.group.add(this.konva.objectGroup);
+    this.konva.group.add(this.konva.interactionRect);
+    this.konva.group.add(this.konva.bbox);
 
+    this.konva.transformer.on('transform', () => {
+      console.log(this.konva.interactionRect.position());
+      this.konva.objectGroup.setAttrs({
+        scaleX: this.konva.interactionRect.scaleX(),
+        scaleY: this.konva.interactionRect.scaleY(),
+        // rotation: this.konva.interactionRect.rotation(),
+        x: this.konva.interactionRect.x(),
+        t: this.konva.interactionRect.y(),
+      });
+    });
     this.konva.transformer.on('transformend', () => {
+      console.log(this.bbox);
+      this.bbox = {
+        x: this.konva.interactionRect.x(),
+        y: this.konva.interactionRect.y(),
+        width: this.konva.interactionRect.width() * this.konva.interactionRect.scaleX(),
+        height: this.konva.interactionRect.height() * this.konva.interactionRect.scaleY(),
+      };
+      console.log(this.bbox);
+      this.renderBbox();
       this.manager.stateApi.onScaleChanged(
         {
           id: this.id,
-          scale: this.konva.group.scaleX(),
-          position: { x: this.konva.group.x(), y: this.konva.group.y() },
+          scale: this.konva.objectGroup.scaleX(),
+          position: { x: this.konva.objectGroup.x(), y: this.konva.objectGroup.y() },
         },
         'layer'
       );
@@ -83,12 +111,15 @@ export class CanvasLayer {
         'layer'
       );
     });
-    this.konva.layer.add(this.konva.transformer);
 
     this.objects = new Map();
     this.drawingBuffer = null;
     this.state = state;
-    this.bbox = null;
+    this.bbox = CanvasLayer.DEFAULT_BBOX_RECT;
+  }
+
+  private static get DEFAULT_BBOX_RECT() {
+    return { x: 0, y: 0, width: 0, height: 0 };
   }
 
   destroy(): void {
@@ -235,48 +266,50 @@ export class CanvasLayer {
       if (this.objects.size > 0) {
         this.getBbox();
       } else {
-        this.bbox = null;
+        this.bbox = CanvasLayer.DEFAULT_BBOX_RECT;
         this.renderBbox();
       }
     }
 
     this.konva.layer.visible(true);
-    this.konva.group.opacity(this.state.opacity);
+    this.konva.objectGroup.opacity(this.state.opacity);
     const isSelected = this.manager.stateApi.getIsSelected(this.id);
     const selectedTool = this.manager.stateApi.getToolState().selected;
 
-    const transformerListening = selectedTool === 'transform' && isSelected;
-    const bboxListening = selectedTool === 'move' && isSelected;
+    const isTransforming = selectedTool === 'transform' && isSelected;
+    const isMoving = selectedTool === 'move' && isSelected;
 
-    this.konva.layer.listening(transformerListening || bboxListening);
-    this.konva.transformer.listening(transformerListening);
-    this.konva.group.listening(bboxListening);
-    this.konva.bbox.listening(bboxListening);
+    this.konva.layer.listening(isTransforming || isMoving);
+    this.konva.transformer.listening(isTransforming);
+    this.konva.bbox.visible(isMoving);
+    this.konva.interactionRect.listening(isMoving);
 
     if (this.objects.size === 0) {
       // If the layer is totally empty, reset the cache and bail out.
       this.konva.transformer.nodes([]);
-      if (this.konva.group.isCached()) {
-        this.konva.group.clearCache();
+      if (this.konva.objectGroup.isCached()) {
+        this.konva.objectGroup.clearCache();
       }
     } else if (isSelected && selectedTool === 'transform') {
       // When the layer is selected and being moved, we should always cache it.
       // We should update the cache if we drew to the layer.
-      if (!this.konva.group.isCached() || didDraw) {
-        // this.konva.group.cache();
+      if (!this.konva.objectGroup.isCached() || didDraw) {
+        // this.konva.objectGroup.cache();
       }
       // Activate the transformer
-      this.konva.transformer.nodes([this.konva.group]);
+      this.konva.transformer.nodes([this.konva.interactionRect]);
+      this.konva.transformer.enabledAnchors(['top-left', 'top-right', 'bottom-left', 'bottom-right']);
       this.konva.transformer.forceUpdate();
       this.konva.transformer.visible(true);
     } else if (selectedTool === 'move') {
       // When the layer is selected and being moved, we should always cache it.
       // We should update the cache if we drew to the layer.
-      if (!this.konva.group.isCached() || didDraw) {
-        // this.konva.group.cache();
+      if (!this.konva.objectGroup.isCached() || didDraw) {
+        // this.konva.objectGroup.cache();
       }
       // Activate the transformer
-      this.konva.transformer.nodes([]);
+      this.konva.transformer.nodes([this.konva.interactionRect]);
+      this.konva.transformer.enabledAnchors([]);
       this.konva.transformer.forceUpdate();
       this.konva.transformer.visible(false);
     } else if (isSelected) {
@@ -286,14 +319,14 @@ export class CanvasLayer {
       if (isDrawingTool(selectedTool)) {
         // We are using a drawing tool (brush, eraser, rect). These tools change the layer's rendered appearance, so we
         // should never be cached.
-        if (this.konva.group.isCached()) {
-          this.konva.group.clearCache();
+        if (this.konva.objectGroup.isCached()) {
+          this.konva.objectGroup.clearCache();
         }
       } else {
         // We are using a non-drawing tool (move, view, bbox), so we should cache the layer.
         // We should update the cache if we drew to the layer.
-        if (!this.konva.group.isCached() || didDraw) {
-          // this.konva.group.cache();
+        if (!this.konva.objectGroup.isCached() || didDraw) {
+          // this.konva.objectGroup.cache();
         }
       }
     } else if (!isSelected) {
@@ -301,8 +334,8 @@ export class CanvasLayer {
       // The transformer also does not need to be active.
       this.konva.transformer.nodes([]);
       // Update the layer's cache if it's not already cached or we drew to it.
-      if (!this.konva.group.isCached() || didDraw) {
-        // this.konva.group.cache();
+      if (!this.konva.objectGroup.isCached() || didDraw) {
+        // this.konva.objectGroup.cache();
       }
     }
   }
@@ -310,17 +343,33 @@ export class CanvasLayer {
   renderBbox() {
     const isSelected = this.manager.stateApi.getIsSelected(this.id);
     const selectedTool = this.manager.stateApi.getToolState().selected;
+    const hasBbox = this.bbox.width !== 0 && this.bbox.height !== 0;
+
+    this.konva.bbox.visible(hasBbox);
+    this.konva.interactionRect.visible(hasBbox);
 
     this.konva.bbox.setAttrs({
-      ...this.bbox,
+      x: this.bbox.x,
+      y: this.bbox.y,
+      width: this.bbox.width,
+      height: this.bbox.height,
+      scaleX: 1,
+      scaleY: 1,
       strokeWidth: 1 / this.manager.stage.scaleX(),
-      visible: this.bbox !== null && selectedTool === 'move' && isSelected,
+    });
+    this.konva.interactionRect.setAttrs({
+      x: this.bbox.x,
+      y: this.bbox.y,
+      width: this.bbox.width,
+      height: this.bbox.height,
+      scaleX: 1,
+      scaleY: 1,
     });
   }
 
   private _getBbox() {
     if (this.objects.size === 0) {
-      this.bbox = null;
+      this.bbox = CanvasLayer.DEFAULT_BBOX_RECT;
       this.renderBbox();
       return;
     }
@@ -338,7 +387,7 @@ export class CanvasLayer {
 
     if (!needsPixelBbox) {
       if (rect.width === 0 || rect.height === 0) {
-        this.bbox = null;
+        this.bbox = CanvasLayer.DEFAULT_BBOX_RECT;
       } else {
         this.bbox = rect;
       }
@@ -370,13 +419,13 @@ export class CanvasLayer {
         if (extents) {
           const { minX, minY, maxX, maxY } = extents;
           this.bbox = {
-            x: minX + rect.x - Math.floor(this.konva.layer.x()),
-            y: minY + rect.y - Math.floor(this.konva.layer.y()),
+            x: rect.x + minX,
+            y: rect.y + minY,
             width: maxX - minX,
             height: maxY - minY,
           };
         } else {
-          this.bbox = null;
+          this.bbox = CanvasLayer.DEFAULT_BBOX_RECT;
         }
         this.renderBbox();
         clone.destroy();