diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,9 @@
+## 2.9.9
+
+* Add `commuteHtmlT` to commute `HtmlT m a` into `m (HtmlT n a)`.
+* Add `MonadError e m => MonadError e (HtmlT m)` and
+  `MonadWriter w m => MonadWriter w (HtmlT m)` instances
+
 ## 2.9.8.1
 
 * Improve performance by adding `INLINE` pragmas to `Monad` etc. combinators.
@@ -8,15 +14,21 @@
 * Add `classes_` smart attribute constructor
 * Add `ToHtml (HtmlT m a)` instance
 
+## 2.9.7
+
+* Add `Semigroup (HtmlT m a)` instance
+* Add `MonadState` and `MonadReader` instances
+
 ## 2.9.6
 
 * Fix compilation of benchmarks
-* Add @athanclark's version of relaxHtmlT
-* Add a utility to generalize the underlying monad from Identity
+* Add @athanclark's version of `relaxHtmlT`
+* Add a utility to generalize the underlying monad from Identity: `relaxHtmlT`
 
 ## 2.9.5
 
 * Add ToHtml instance for ByteString (both)
+* Add `MFunctor HtmlT` instance, i.e. `hoist` from @mmorph@.
 
 ## 2.9.1
 
diff --git a/README.md b/README.md
--- a/README.md
+++ b/README.md
@@ -5,6 +5,8 @@
 
 [Documentation](http://chrisdone.github.io/lucid/)
 
+[lucid-from-html](https://github.com/dbaynard/lucid-from-html) will convert html to the `lucid` DSL, though it is experimental.
+
 ## Introduction
 
 HTML terms in Lucid are written with a postfix ‘`_`’ to indicate data
diff --git a/lucid.cabal b/lucid.cabal
--- a/lucid.cabal
+++ b/lucid.cabal
@@ -1,5 +1,5 @@
 name:                lucid
-version:             2.9.8.1
+version:             2.9.9
 synopsis:            Clear to write, read and edit DSL for HTML
 description:         Clear to write, read and edit DSL for HTML. See the 'Lucid' module
                      for description and documentation.
@@ -13,7 +13,7 @@
 build-type:          Simple
 cabal-version:       >=1.8
 extra-source-files:  README.md, CHANGELOG.md
-tested-with:         GHC==7.4.2,GHC==7.6.3,GHC==7.8.4,GHC==7.10.2
+tested-with:         GHC==7.4.2,GHC==7.6.3,GHC==7.8.4,GHC==7.10.3,GHC==8.0.2,GHC==8.2.1
 
 library
   hs-source-dirs:    src/
diff --git a/src/Lucid.hs b/src/Lucid.hs
--- a/src/Lucid.hs
+++ b/src/Lucid.hs
@@ -5,6 +5,10 @@
 --
 -- See "Lucid.Base" for lower level functions like
 -- `makeElement`, `makeAttribute`, 'termRaw', etc.
+--
+-- To convert html to the lucid DSL, use the (experimental) program
+-- <https://github.com/dbaynard/lucid-from-html lucid-from-html>
+-- which may eventually be integrated into lucid itself.
 
 module Lucid
   (-- * Intro
diff --git a/src/Lucid/Base.hs b/src/Lucid/Base.hs
--- a/src/Lucid/Base.hs
+++ b/src/Lucid/Base.hs
@@ -22,6 +22,7 @@
   ,evalHtmlT
   ,runHtmlT
   ,relaxHtmlT
+  ,commuteHtmlT
   -- * Combinators
   ,makeElement
   ,makeElementNoEnd
@@ -45,7 +46,9 @@
 import           Control.Monad
 import           Control.Monad.Morph
 import           Control.Monad.Reader
+import           Control.Monad.Error.Class (MonadError(..))
 import           Control.Monad.State.Class (MonadState(..))
+import           Control.Monad.Writer.Class (MonadWriter(..))
 import           Data.ByteString.Lazy (ByteString)
 import qualified Data.ByteString.Lazy as L
 import qualified Data.ByteString as S
@@ -98,9 +101,11 @@
   deriving (Typeable)
 #endif
 
+-- | @since 2.9.5
 instance MFunctor HtmlT where
   hoist f (HtmlT xs) = HtmlT (f xs)
 
+-- | @since 2.9.7
 instance (a ~ (),Monad m) => Semigroup (HtmlT m a) where
   (<>) = liftM2 mappend
 
@@ -159,18 +164,33 @@
     HtmlT (do a <- m
               return (\_ -> mempty,a))
 
--- MonadReader / MonadState instances need UndecidableInstances,
+-- MonadReader, MonadState etc instances need UndecidableInstances,
 -- because they do not satisfy the coverage condition.
 
+-- | @since 2.9.7
 instance MonadReader r m => MonadReader r (HtmlT m) where
   ask = lift ask
   local f (HtmlT a) = HtmlT (local f a)
 
+-- | @since 2.9.7
 instance MonadState s m => MonadState s (HtmlT m) where
   get = lift get
   put = lift . put
   state = lift . state
 
+-- | @since 2.9.9
+instance MonadError e m => MonadError e (HtmlT m) where
+    throwError = lift . throwError
+    catchError (HtmlT m) h = HtmlT $ catchError m (runHtmlT . h)
+
+-- | @since 2.9.9
+instance MonadWriter w m => MonadWriter w (HtmlT m) where
+    tell             = lift . tell
+    listen (HtmlT x) = HtmlT $ liftM reassoc $ listen x
+      where reassoc ((a, b), c) = (a, (b, c))
+    pass (HtmlT p)   = HtmlT $ pass $ liftM assoc p
+      where assoc (a, (b, c)) = ((a, b), c)
+
 -- | If you want to use IO in your HTML generation.
 instance MonadIO m => MonadIO (HtmlT m) where
   liftIO = lift . liftIO
@@ -186,9 +206,12 @@
 
 -- | Can be converted to HTML.
 class ToHtml a where
+  -- | Convert to HTML, doing HTML escaping.
   toHtml :: Monad m => a -> HtmlT m ()
+  -- | Convert to HTML without any escaping.
   toHtmlRaw :: Monad m => a -> HtmlT m ()
 
+-- | @since 2.9.8
 instance (a ~ (), m ~ Identity) => ToHtml (HtmlT m a) where
   toHtml = relaxHtmlT
   toHtmlRaw = relaxHtmlT
@@ -208,6 +231,8 @@
 -- | This instance requires the ByteString to contain UTF-8 encoded
 -- text, for the 'toHtml' method. The 'toHtmlRaw' method doesn't care,
 -- but the overall HTML rendering methods in this module assume UTF-8.
+--
+-- @since 2.9.5
 instance ToHtml S.ByteString where
   toHtml    = build . Blaze.fromHtmlEscapedText . T.decodeUtf8
   toHtmlRaw = build . Blaze.fromByteString
@@ -215,6 +240,8 @@
 -- | This instance requires the ByteString to contain UTF-8 encoded
 -- text, for the 'toHtml' method. The 'toHtmlRaw' method doesn't care,
 -- but the overall HTML rendering methods in this module assume UTF-8.
+--
+-- @since 2.9.5
 instance ToHtml L.ByteString where
   toHtml    = build . Blaze.fromHtmlEscapedLazyText . LT.decodeUtf8
   toHtmlRaw = build . Blaze.fromLazyByteString
@@ -404,13 +431,37 @@
 -- Some builders are happy to deliver results in a pure underlying
 -- monad, here 'Identity', but have trouble maintaining the polymorphic
 -- type. This utility generalizes from 'Identity'.
+--
+-- @since 2.9.6
 relaxHtmlT :: Monad m
-          => HtmlT Identity a  -- ^ The HTML generated purely.
-          -> HtmlT m a         -- ^ Same HTML accessible in a polymorphic context.
+           => HtmlT Identity a  -- ^ The HTML generated purely.
+           -> HtmlT m a         -- ^ Same HTML accessible in a polymorphic context.
 relaxHtmlT = hoist go
   where
     go :: Monad m => Identity a -> m a
     go = return . runIdentity
+
+-- | Commute inner @m@ to the front.
+--
+-- This is useful when you have impure HTML generation, e.g. using `StateT`.
+-- Recall, there is `MonadState s HtmlT` instance.
+--
+-- @
+-- exampleHtml :: MonadState Int m => HtmlT m ()
+-- exampleHtml = ul_ $ replicateM_ 5 $ do
+--   x <- get
+--   put (x + 1)
+--   li_ $ toHtml $ show x
+--
+-- exampleHtml' :: Monad m => HtmlT m ()
+-- exampleHtml' = evalState (commuteHtmlT exampleHtml) 1
+-- @
+--
+-- @since 2.9.9
+commuteHtmlT :: (Functor m, Monad n)
+             => HtmlT m a      -- ^ unpurely generated HTML
+             -> m (HtmlT n a)  -- ^ Commuted monads. /Note:/ @n@ can be 'Identity'
+commuteHtmlT (HtmlT xs) = fmap (HtmlT . return) xs
 
 -- | Evaluate the HTML to its return value. Analogous to @evalState@.
 --
diff --git a/src/Lucid/Html5.hs b/src/Lucid/Html5.hs
--- a/src/Lucid/Html5.hs
+++ b/src/Lucid/Html5.hs
@@ -515,6 +515,9 @@
 class_ :: Text -> Attribute
 class_ = makeAttribute "class"
 
+-- | Smart constructor for @class@ attribute.
+--
+-- @since 2.9.8
 classes_ :: [Text] -> Attribute
 classes_ = makeAttribute "class" . Data.Text.unwords
 
@@ -547,6 +550,8 @@
 coords_ = makeAttribute "coords"
 
 -- | The @crossorigin@ attribute.
+--
+-- @since 2.9.8
 crossorigin_ :: Text -> Attribute
 crossorigin_ = makeAttribute "crossorigin"
 
@@ -643,6 +648,8 @@
 id_ = makeAttribute "id"
 
 -- | The @integrity@ attribute.
+--
+-- @since 2.9.8
 integrity_ :: Text -> Attribute
 integrity_ = makeAttribute "integrity"
 
diff --git a/test/Main.hs b/test/Main.hs
--- a/test/Main.hs
+++ b/test/Main.hs
@@ -10,6 +10,8 @@
 import Lucid.Base
 import Lucid.Bootstrap
 
+import Control.Monad.State.Strict
+
 import Example1
 
 import Test.HUnit
@@ -29,6 +31,7 @@
   describe "extension" testExtension
   describe "special-elements" testSpecials
   describe "self-closing" testSelfClosing
+  describe "commuteHtmlT" testCommuteHtmlT
 
 -- | Test text/unicode.
 testText :: Spec
@@ -195,3 +198,23 @@
      it "input"
         (renderText (input_ [type_ "text"]) ==
          "<input type=\"text\">")
+
+testCommuteHtmlT :: Spec
+testCommuteHtmlT =
+  do it "makes using inner monads easy"
+        (example == renderText expected)
+  where
+    example = renderText $ evalState (commuteHtmlT exampleHtml) 1
+
+    exampleHtml :: MonadState Int m => HtmlT m ()
+    exampleHtml = ul_ $ replicateM_ 5 $ do
+      x <- get
+      put (x + 1)
+      li_ $ toHtml $ show x
+
+    expected = ul_ $ do
+      li_ "1"
+      li_ "2"
+      li_ "3"
+      li_ "4"
+      li_ "5"
