diff --git a/apps/examples/public/man.png b/apps/examples/public/man.png
new file mode 100644
index 000000000..85554ce6e
Binary files /dev/null and b/apps/examples/public/man.png differ
diff --git a/apps/examples/public/shadow-man.png b/apps/examples/public/shadow-man.png
new file mode 100644
index 000000000..9d892a68a
Binary files /dev/null and b/apps/examples/public/shadow-man.png differ
diff --git a/apps/examples/src/examples/popup-shape/PopupShapeExample.tsx b/apps/examples/src/examples/popup-shape/PopupShapeExample.tsx
new file mode 100644
index 000000000..b05fd8615
--- /dev/null
+++ b/apps/examples/src/examples/popup-shape/PopupShapeExample.tsx
@@ -0,0 +1,25 @@
+import { Tldraw } from 'tldraw'
+import 'tldraw/tldraw.css'
+import { PopupShapeUtil } from './PopupShapeUtil'
+
+const customShapeUtils = [PopupShapeUtil]
+
+export default function PopupShapeExample() {
+ return (
+
+ {
+ for (let i = 0; i < 9; i++) {
+ editor.createShape({
+ type: 'my-popup-shape',
+ x: (i % 3) * 220,
+ y: Math.floor(i / 3) * 220,
+ })
+ }
+ editor.zoomToContent({ duration: 0 })
+ }}
+ />
+
+ )
+}
diff --git a/apps/examples/src/examples/popup-shape/PopupShapeUtil.tsx b/apps/examples/src/examples/popup-shape/PopupShapeUtil.tsx
new file mode 100644
index 000000000..184552e79
--- /dev/null
+++ b/apps/examples/src/examples/popup-shape/PopupShapeUtil.tsx
@@ -0,0 +1,138 @@
+/* eslint-disable react-hooks/rules-of-hooks */
+import { useEffect, useRef, useState } from 'react'
+import {
+ BaseBoxShapeUtil,
+ HTMLContainer,
+ ShapeProps,
+ T,
+ TLBaseShape,
+ stopEventPropagation,
+} from 'tldraw'
+
+type IMyPopupShape = TLBaseShape<
+ 'my-popup-shape',
+ {
+ w: number
+ h: number
+ animal: number
+ }
+>
+
+export class PopupShapeUtil extends BaseBoxShapeUtil {
+ static override type = 'my-popup-shape' as const
+ static override props: ShapeProps = {
+ w: T.number,
+ h: T.number,
+ animal: T.number,
+ }
+
+ getDefaultProps(): IMyPopupShape['props'] {
+ return {
+ w: 200,
+ h: 200,
+ animal: 0,
+ }
+ }
+
+ component(shape: IMyPopupShape) {
+ const [popped, setPopped] = useState(false)
+
+ const ref = useRef(null)
+ const ref2 = useRef(null)
+
+ useEffect(() => {
+ const elm = ref.current
+ if (!elm) return
+ const elm2 = ref2.current
+ if (!elm2) return
+ if (popped) {
+ // man
+ // elm2.style.transform = `rotateX(0deg) translateY(0px) translateZ(0px)`
+ // note
+ elm.style.transform = `rotateX(0deg) translateY(0px) translateZ(0px)`
+ } else {
+ // man
+ // elm.style.transform = `rotateX(-50deg) translateY(5px) translateZ(0px)`
+ // elm2.style.transform = `scaleY(.8)`
+ // note
+ elm.style.transform = `rotateX(20deg)`
+ }
+ }, [popped])
+
+ const vpb = this.editor.getViewportPageBounds()
+ const spb = this.editor.getShapePageBounds(shape)!
+ const px = vpb.midX - spb.midX + spb.w / 2
+ const py = vpb.midY - spb.midY + spb.h / 2
+
+ return (
+ {
+ setPopped((p) => !p)
+ stopEventPropagation(e)
+ }}
+ >
+
+
+ {/* {shape.id.slice(-1).toUpperCase()} */}
+
+
+ )
+ }
+
+ indicator(shape: IMyPopupShape) {
+ return
+ }
+}
diff --git a/apps/examples/src/examples/popup-shape/README.md b/apps/examples/src/examples/popup-shape/README.md
new file mode 100644
index 000000000..138491462
--- /dev/null
+++ b/apps/examples/src/examples/popup-shape/README.md
@@ -0,0 +1,11 @@
+---
+title: Popup shape
+component: ./PopupShapeExample.tsx
+category: shapes/tools
+---
+
+...
+
+---
+
+...