diff --git a/Art/Grammar.hs b/Art/Grammar.hs
--- a/Art/Grammar.hs
+++ b/Art/Grammar.hs
@@ -8,6 +8,7 @@
   = Color String
   | Scale Float
   | Move Vec
+  | Rotate Float
 
 -- | A production rule, including a starting probability of generation,
 --   a list of styles to be applied to sub-grammars, and a non-empty list of
diff --git a/Art/Interpreter.hs b/Art/Interpreter.hs
--- a/Art/Interpreter.hs
+++ b/Art/Interpreter.hs
@@ -13,6 +13,7 @@
 import System.Random
 import Text.Blaze
 import qualified Data.Text as T
+import qualified TextShow as T
 import Text.Blaze.Svg11 ((!))
 import qualified Text.Blaze.Svg11 as S
 import qualified Text.Blaze.Svg11.Attributes as A
@@ -70,9 +71,11 @@
       ! A.cx (toValue x)
       ! A.cy (toValue y))
 
-groupModifier :: Modifier -> Maybe (S.Svg -> S.Svg)
-groupModifier = \case
+groupModifier :: State -> Modifier -> Maybe (S.Svg -> S.Svg)
+groupModifier State { position = (x, y) } = \case
     Color  c -> Just (! A.fill (toValue c))
+    Rotate n -> Just (! A.transform
+      (toValue $ "rotate(" <> T.unwords (T.showt <$> [n, x, y]) <> ")"))
     _        -> Nothing
 
 modifyState :: State -> Modifier -> State
@@ -86,13 +89,16 @@
 
 foldMods :: State -> [Modifier] -> (State, S.Svg -> S.Svg)
 foldMods state mods =
-  let newState = foldl modifyState state mods
-      groupMods = mapMaybe groupModifier mods
+  let (maybeGroupMods, newState) = foldl applyMod ([], state) mods
+      groupMods = catMaybes maybeGroupMods
       maybeLayer =
         if null groupMods
         then id
         else foldl (<&>) S.g groupMods
   in  (newState, maybeLayer)
+  where
+    applyMod (groupMods, state) mod =
+      (groupModifier state mod : groupMods , modifyState state mod)
 
 joinRes :: Res -> Res -> Res
 joinRes (b1, s1) (b2, s2) = (combineBounds [b1, b2], s1 >> s2)
diff --git a/Tests.hs b/Tests.hs
--- a/Tests.hs
+++ b/Tests.hs
@@ -112,12 +112,19 @@
       b = Poly [(-2, 2), (1, -2)]
 
 fill
-  = testRender "another scaled poly" a [-1, -1, 2, 2]
+  = testRender "fill" a [-1, -1, 2, 2]
     $ S.g ! A.fill "green" $ circle 1 (0, 0)
     where
       a = Mod [Color "green"] b
       b = Circle 1
 
+rotate
+  = testRender "rotation" a [-1, -1, 2, 2]
+    $ S.g ! A.transform "rotate(10.0 0.0 0.0)" $ circle 1 (0, 0)
+    where
+      a = Mod [Rotate 10] b
+      b = Circle 1
+
 svgToText = TestCase $ do
   res <- renderSvg <$> interpret (Circle 1)
   assertEqual "svg text generation" res $
@@ -143,6 +150,7 @@
   , rendersPolyScaled
   , rendersPolyScaled2
   , fill
+  , rotate
   ]
 
 main = runTestTT tests
diff --git a/context-free-art.cabal b/context-free-art.cabal
--- a/context-free-art.cabal
+++ b/context-free-art.cabal
@@ -4,7 +4,7 @@
 -- http://haskell.org/cabal/users-guide/
 
 name:                context-free-art
-version:             0.2.0.1
+version:             0.2.0.2
 synopsis:            Generate art from context-free grammars
 description:
     .
@@ -33,12 +33,10 @@
     > -- to turn it into a string we can use one of the `blaze-svg` renderers
     > graphic1 = interpret $ Circle 1
     >
-    > -- Let's create a non-terminal.
-    > -- At every layer, this will have an 85% chance
-    > -- of rendering another circle
-    > a = Mod [Move (2, 0)] b
-    > b = NonTerminal $ (85, c) :| []
-    > c = NonTerminal $ (100, Circle 1) :| [(100, a)]
+    > -- let's create a non-terminal, 'a', which renders a terminal, 'Circle 1'
+    > -- and has an 85% chance of rendering another circle, placed to its right
+    > a = NonTerminal $ (100, Circle 1) :| [(85, b)]
+    > b = Mod [Move (2, 0)] a
 
 homepage:            https://github.com/414owen/context-free-art
 -- bug-reports:
@@ -56,14 +54,14 @@
                        , Art.Geometry
                        , Art.Grammar
                        , Art.Util
-  build-depends:       base ^>= 4.12.0.0
-                       , blaze-svg ^>= 0.3.6
-                       , random ^>= 1.1
+  build-depends:       base >= 4.12 && < 5
+                       , blaze-svg >= 0.3.6
+                       , random >= 1.1
                        , blaze-markup
-                       , bifunctors ^>= 5.5
-                       , text-show ^>= 3.8
-                       , text ^>= 1.2
-                       , HUnit ^>= 1.6
+                       , bifunctors >= 5.5
+                       , text-show >= 3.8
+                       , text >= 1.2
+                       , HUnit >= 1.6
   default-language:    Haskell2010
 
 library
@@ -72,11 +70,11 @@
                        , Art.Util
                        , Art.Interpreter
                        , Art.Grammar
-  build-depends:       base ^>= 4.12.0.0
-                       , blaze-svg ^>= 0.3.6
-                       , random ^>= 1.1
+  build-depends:       base >= 4.12 && < 5
+                       , blaze-svg >= 0.3.6
+                       , random >= 1.1
                        , blaze-markup
-                       , bifunctors ^>= 5.5
-                       , text-show ^>= 3.8
-                       , text ^>= 1.2
+                       , bifunctors >= 5.5
+                       , text-show >= 3.8
+                       , text >= 1.2
   default-language:    Haskell2010
