diff --git a/html-tokenizer.cabal b/html-tokenizer.cabal
--- a/html-tokenizer.cabal
+++ b/html-tokenizer.cabal
@@ -1,7 +1,7 @@
 name:
   html-tokenizer
 version:
-  0.6
+  0.6.1
 synopsis:
   An "attoparsec"-based HTML tokenizer
 description:
@@ -43,7 +43,12 @@
   exposed-modules:
     HTMLTokenizer.Data
     HTMLTokenizer.Parsing
+    HTMLTokenizer.Rendering.Text
+    HTMLTokenizer.Rendering.Text.Builder
   other-modules:
+    HTMLTokenizer.Data.Types
+    HTMLTokenizer.Data.Instances.Show
+    HTMLTokenizer.Data.Instances.Eq
     HTMLTokenizer.MonadPlus
     HTMLTokenizer.Prelude
   build-depends:
diff --git a/library/HTMLTokenizer/Data.hs b/library/HTMLTokenizer/Data.hs
--- a/library/HTMLTokenizer/Data.hs
+++ b/library/HTMLTokenizer/Data.hs
@@ -1,23 +1,11 @@
-{-# OPTIONS_GHC -funbox-strict-fields #-}
 module HTMLTokenizer.Data
+(
+  module HTMLTokenizer.Data.Types,
+  module HTMLTokenizer.Data.Instances.Show,
+  module HTMLTokenizer.Data.Instances.Eq,
+)
 where
 
-import HTMLTokenizer.Prelude
-
-
-data Token =
-  OpeningTagToken !Name !(Vector Attribute) !Bool |
-  ClosingTagToken !Name |
-  TextToken !Text |
-  CommentToken !Text |
-  DoctypeToken !Text
-  deriving (Show, Eq)
-
-data Name =
-  UnprefixedName !Text |
-  PrefixedName !Text !Text
-  deriving (Show, Eq)
-
-data Attribute =
-  Attribute !Name !Text
-  deriving (Show, Eq)
+import HTMLTokenizer.Data.Types
+import HTMLTokenizer.Data.Instances.Show
+import HTMLTokenizer.Data.Instances.Eq
diff --git a/library/HTMLTokenizer/Data/Instances/Eq.hs b/library/HTMLTokenizer/Data/Instances/Eq.hs
new file mode 100644
--- /dev/null
+++ b/library/HTMLTokenizer/Data/Instances/Eq.hs
@@ -0,0 +1,10 @@
+module HTMLTokenizer.Data.Instances.Eq
+where
+
+import HTMLTokenizer.Prelude
+import HTMLTokenizer.Data.Types
+
+
+deriving instance Eq Token
+deriving instance Eq Name
+deriving instance Eq Attribute
diff --git a/library/HTMLTokenizer/Data/Instances/Show.hs b/library/HTMLTokenizer/Data/Instances/Show.hs
new file mode 100644
--- /dev/null
+++ b/library/HTMLTokenizer/Data/Instances/Show.hs
@@ -0,0 +1,16 @@
+module HTMLTokenizer.Data.Instances.Show
+where
+
+import HTMLTokenizer.Prelude
+import HTMLTokenizer.Data.Types
+import qualified HTMLTokenizer.Rendering.Text as A
+
+
+instance Show Token where
+  show = textString . A.token
+
+instance Show Name where
+  show = textString . A.name
+
+instance Show Attribute where
+  show = textString . A.attribute
diff --git a/library/HTMLTokenizer/Data/Types.hs b/library/HTMLTokenizer/Data/Types.hs
new file mode 100644
--- /dev/null
+++ b/library/HTMLTokenizer/Data/Types.hs
@@ -0,0 +1,20 @@
+{-# OPTIONS_GHC -funbox-strict-fields #-}
+module HTMLTokenizer.Data.Types
+where
+
+import HTMLTokenizer.Prelude
+
+
+data Token =
+  OpeningTagToken !Name !(Vector Attribute) !Bool |
+  ClosingTagToken !Name |
+  TextToken !Text |
+  CommentToken !Text |
+  DoctypeToken !Text
+
+data Name =
+  UnprefixedName !Text |
+  PrefixedName !Text !Text
+
+data Attribute =
+  Attribute !Name !Text
diff --git a/library/HTMLTokenizer/Prelude.hs b/library/HTMLTokenizer/Prelude.hs
--- a/library/HTMLTokenizer/Prelude.hs
+++ b/library/HTMLTokenizer/Prelude.hs
@@ -1,6 +1,8 @@
 module HTMLTokenizer.Prelude
 ( 
   module Exports,
+  textString,
+  showText,
 )
 where
 
@@ -20,3 +22,15 @@
 -- vector
 -------------------------
 import Data.Vector as Exports (Vector)
+
+--------------------------------------------------------------------------------
+
+import qualified Data.Text as A
+
+textString :: Text -> String
+textString =
+  A.unpack
+
+showText :: Show a => a -> Text
+showText =
+  fromString . show
diff --git a/library/HTMLTokenizer/Rendering/Text.hs b/library/HTMLTokenizer/Rendering/Text.hs
new file mode 100644
--- /dev/null
+++ b/library/HTMLTokenizer/Rendering/Text.hs
@@ -0,0 +1,17 @@
+module HTMLTokenizer.Rendering.Text
+where
+
+import HTMLTokenizer.Prelude
+import HTMLTokenizer.Data.Types
+import qualified Text.Builder as A
+import qualified HTMLTokenizer.Rendering.Text.Builder as B
+
+
+token :: Token -> Text
+token = A.run . B.token
+
+attribute :: Attribute -> Text
+attribute = A.run . B.attribute
+
+name :: Name -> Text
+name = A.run . B.name
diff --git a/library/HTMLTokenizer/Rendering/Text/Builder.hs b/library/HTMLTokenizer/Rendering/Text/Builder.hs
new file mode 100644
--- /dev/null
+++ b/library/HTMLTokenizer/Rendering/Text/Builder.hs
@@ -0,0 +1,55 @@
+module HTMLTokenizer.Rendering.Text.Builder
+(
+  token,
+  attribute,
+  name,
+)
+where
+
+import HTMLTokenizer.Prelude hiding (takeWhile)
+import HTMLTokenizer.Data.Types
+import Text.Builder
+import qualified Data.Text as D
+import qualified HTMLEntities.Text as B
+
+
+encodedText :: Text -> Builder
+encodedText =
+  text . B.text
+
+name :: Name -> Builder
+name =
+  \ case
+    UnprefixedName localName ->
+      encodedText localName
+    PrefixedName space localName ->
+      encodedText space <> char ':' <> encodedText localName
+
+attribute :: Attribute -> Builder
+attribute (Attribute attributeName content) =
+  if D.null content
+    then name attributeName
+    else name attributeName <> text "=\"" <> encodedText content <> char '"'
+
+token :: Token -> Builder
+token =
+  \ case
+    OpeningTagToken tagName attrVec closed ->
+      char '<' <> name tagName <> attrs <> closing
+      where
+        attrs =
+          foldMap (\ x -> char ' ' <> attribute x) attrVec
+        closing =
+          if closed
+            then text "/>"
+            else char '>'
+    ClosingTagToken tagName ->
+      text "</" <> name tagName <> char '>'
+    TextToken theText ->
+      encodedText theText
+    CommentToken commentText ->
+      text "<!--" <> encodedText commentText <> text "-->"
+    DoctypeToken doctypeText ->
+      text "<!doctype" <>
+      (if D.null doctypeText then mempty else char ' ' <> encodedText doctypeText) <>
+      char '>'
