diff --git a/LICENSE b/LICENSE
--- a/LICENSE
+++ b/LICENSE
@@ -1,29 +1,29 @@
-Copyright (c) 2024, Matt Hunzinger
-
-
-Redistribution and use in source and binary forms, with or without
-modification, are permitted provided that the following conditions are met:
-
-    * Redistributions of source code must retain the above copyright
-      notice, this list of conditions and the following disclaimer.
-
-    * Redistributions in binary form must reproduce the above
-      copyright notice, this list of conditions and the following
-      disclaimer in the documentation and/or other materials provided
-      with the distribution.
-
-    * Neither the name of the copyright holder nor the names of its
-      contributors may be used to endorse or promote products derived
-      from this software without specific prior written permission.
-
-THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
-A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
-HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
-SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
-LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
-DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
-THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
-(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+Copyright (c) 2024, Matt Hunzinger
+
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+    * Redistributions of source code must retain the above copyright
+      notice, this list of conditions and the following disclaimer.
+
+    * Redistributions in binary form must reproduce the above
+      copyright notice, this list of conditions and the following
+      disclaimer in the documentation and/or other materials provided
+      with the distribution.
+
+    * Neither the name of the copyright holder nor the names of its
+      contributors may be used to endorse or promote products derived
+      from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/aztecs-transform.cabal b/aztecs-transform.cabal
--- a/aztecs-transform.cabal
+++ b/aztecs-transform.cabal
@@ -1,33 +1,26 @@
 cabal-version: 2.4
 name:          aztecs-transform
-version:       0.2.0.0
+version:       0.3.0
 license:       BSD-3-Clause
 license-file:  LICENSE
 maintainer:    matt@hunzinger.me
 author:        Matt Hunzinger
-synopsis:      A type-safe and friendly Entity-Component-System (ECS) for Haskell
-description:   The Entity-Component-System (ECS) pattern is commonly used in video game develop to represent world objects.
-               .
-               ECS follows the principal of composition over inheritence. Each type of
-               object (e.g. sword, monster, etc), in the game has a unique EntityId. Each
-               entity has various Components associated with it (material, weight, damage, etc).
-               Systems act on entities which have the required Components.
 homepage:      https://github.com/matthunz/aztecs
+synopsis:      Transform components for Aztecs
+description:   Transform components for the Aztecs game engine and ECS
 category:      Game Engine
 
 source-repository head
     type:     git
-    location: https://github.com/matthunz/aztecs.git
+    location: https://github.com/matthunz/aztecs-transform.git
 
 library
-    exposed-modules:
-        Data.Aztecs.Transform
+    exposed-modules:  Aztecs.Transform
     hs-source-dirs:   src
     default-language: Haskell2010
     ghc-options:      -Wall
     build-depends:
         base >=4.6 && <5,
-        aztecs >= 0.5,
-        aztecs-hierarchy >= 0.1,
-        mtl >=2,
-        linear >= 1
+        aztecs >=0.16 && <0.17,
+        containers >=0.6 && <0.9,
+        linear >=1
diff --git a/src/Aztecs/Transform.hs b/src/Aztecs/Transform.hs
new file mode 100644
--- /dev/null
+++ b/src/Aztecs/Transform.hs
@@ -0,0 +1,130 @@
+{-# LANGUAGE FlexibleInstances #-}
+{-# LANGUAGE MultiParamTypeClasses #-}
+{-# LANGUAGE ScopedTypeVariables #-}
+{-# LANGUAGE TypeApplications #-}
+
+-- |
+-- Module      : Aztecs.Transform
+-- Copyright   : (c) Matt Hunzinger, 2025
+-- License     : BSD-style (see the LICENSE file in the distribution)
+--
+-- Maintainer  : matt@hunzinger.me
+-- Stability   : provisional
+-- Portability : non-portable (GHC extensions)
+module Aztecs.Transform
+  ( -- * Components
+
+    -- ** 2D
+    Transform2D,
+    transform2d,
+    GlobalTransform2D,
+    Size2D,
+
+    -- ** Generic
+    Transform (..),
+    GlobalTransform (..),
+    transform,
+    Size (..),
+
+    module Linear
+  )
+where
+
+import Aztecs.ECS
+import Aztecs.Hierarchy
+import Control.Monad
+import Data.Data (Typeable)
+import qualified Data.Set as Set
+import Linear
+import Prelude hiding (lookup)
+
+type GlobalTransform2D = GlobalTransform (V2 Int) (V2 Float) Float
+
+newtype GlobalTransform t s r = GlobalTransform {unGlobalTransform :: Transform t s r}
+  deriving (Show)
+
+instance
+  (Monad m, Typeable t, Typeable s, Typeable r) =>
+  Component m (GlobalTransform t s r)
+
+type Size2D = Size (V2 Float)
+
+newtype Size a = Size {unSize :: a}
+  deriving (Show)
+
+instance (Monad m, Typeable a) => Component m (Size a)
+
+type Transform2D = Transform (V2 Int) (V2 Float) Float
+
+data Transform t s r = Transform
+  { transformTranslation :: t,
+    transformScale :: s,
+    transformRotation :: r
+  }
+  deriving (Show)
+
+instance (Num t, Num s, Num r) => Semigroup (Transform t s r) where
+  Transform t1 s1 r1 <> Transform t2 s2 r2 = Transform (t1 + t2) (s1 * s2) (r1 + r2)
+
+instance (Num t, Num s, Num r) => Monoid (Transform t s r) where
+  mempty = Transform 0 1 0
+
+instance
+  (Monad m, Typeable t, Typeable s, Typeable r, Num t, Num s, Num r) =>
+  Component m (Transform t s r)
+  where
+  componentOnInsert e localT = do
+    globalT <- computeGlobalTransform e localT
+    insertUntracked e . bundle $ GlobalTransform @t @s @r globalT
+    propagateToChildren e globalT
+
+  componentOnChange e _ newLocalT = do
+    globalT <- computeGlobalTransform e newLocalT
+    insertUntracked e . bundle $ GlobalTransform @t @s @r globalT
+    propagateToChildren e globalT
+
+  componentOnRemove e _ = void $ remove @_ @(GlobalTransform t s r) e
+
+-- | Propagate a global transform to all children of an entity.
+propagateToChildren ::
+  forall m t s r.
+  (Monad m, Typeable t, Typeable s, Typeable r, Num t, Num s, Num r) =>
+  EntityID ->
+  Transform t s r ->
+  Access m ()
+propagateToChildren parentE parentGlobal = do
+  maybeChildren <- lookup @_ @Children parentE
+  case maybeChildren of
+    Nothing -> return ()
+    Just children -> mapM_ propagateToChild (Set.toList $ unChildren children)
+  where
+    propagateToChild :: EntityID -> Access m ()
+    propagateToChild childE = do
+      maybeLocalT <- lookup @_ @(Transform t s r) childE
+      case maybeLocalT of
+        Nothing -> return ()
+        Just localT -> do
+          let childGlobal = localT <> parentGlobal
+          insertUntracked childE . bundle $ GlobalTransform @t @s @r childGlobal
+          propagateToChildren childE childGlobal
+
+-- | Compute the global transform for an entity based on its local transform and parent.
+computeGlobalTransform ::
+  forall m t s r.
+  (Monad m, Typeable t, Typeable s, Typeable r, Num t, Num s, Num r) =>
+  EntityID ->
+  Transform t s r ->
+  Access m (Transform t s r)
+computeGlobalTransform e localT = do
+  maybeParent <- lookup @_ @Parent e
+  case maybeParent of
+    Nothing -> return localT
+    Just (Parent parentE) -> do
+      maybeParentGlobal <- lookup @_ @(GlobalTransform t s r) parentE
+      return $ localT <> maybe mempty unGlobalTransform maybeParentGlobal
+
+transform2d :: Transform2D
+transform2d = transform
+
+transform :: (Num t, Num s, Num r) => Transform t s r
+transform = mempty
diff --git a/src/Data/Aztecs/Transform.hs b/src/Data/Aztecs/Transform.hs
deleted file mode 100644
--- a/src/Data/Aztecs/Transform.hs
+++ /dev/null
@@ -1,21 +0,0 @@
-module Data.Aztecs.Transform where
-
-import Data.Aztecs
-import Linear (V2 (..))
-
-data Transform = Transform
-  { transformPosition :: !(V2 Int),
-    transformRotation :: !Float,
-    transformScale :: !(V2 Int)
-  }
-  deriving (Eq, Show)
-
-transform :: Transform
-transform = Transform (V2 0 0) 0 (V2 1 1)
-
-instance Component Transform
-
-
-newtype Size = Size {unSize :: V2 Int}
-
-instance Component Size
