diff --git a/README.lhs b/README.lhs
--- a/README.lhs
+++ b/README.lhs
@@ -123,6 +123,17 @@
 
 ![](./cli.png)
 
+## Similar Libraries
+
+1. **[uri-template](https://hackage.haskell.org/package/uri-template)**: very
+   basic library, not updated since 2008. Unlikely to be well-tested or support
+   the full RFC.
+
+1. **[uri-templater](https://hackage.haskell.org/package/uri-templater)**: looks
+   more feature-complete, examples cover the full RFC. Built as a quasi-quoter
+   that interpolates Haskell variables from the surrounding scope. Not clear how
+   to expand with run-time values.
+
 ## License
 
 This project is licensed AGPLv3. See [COPYING](./COPYING).
diff --git a/README.md b/README.md
--- a/README.md
+++ b/README.md
@@ -123,6 +123,17 @@
 
 ![](./cli.png)
 
+## Similar Libraries
+
+1. **[uri-template](https://hackage.haskell.org/package/uri-template)**: very
+   basic library, not updated since 2008. Unlikely to be well-tested or support
+   the full RFC.
+
+1. **[uri-templater](https://hackage.haskell.org/package/uri-templater)**: looks
+   more feature-complete, examples cover the full RFC. Built as a quasi-quoter
+   that interpolates Haskell variables from the surrounding scope. Not clear how
+   to expand with run-time values.
+
 ## License
 
 This project is licensed AGPLv3. See [COPYING](./COPYING).
diff --git a/app/Main.hs b/app/Main.hs
--- a/app/Main.hs
+++ b/app/Main.hs
@@ -80,20 +80,15 @@
 main :: IO ()
 main = do
   opts <- parseOptions
-
-  let expanded = expandTemplate opts.variables opts.template
-
-  if opts.noPretty
-    then T.putStrLn expanded
-    else prettyPrint opts expanded
+  prettyPrint opts $ expandTemplateDoc opts.variables opts.template
 
-prettyPrint :: Options -> Text -> IO ()
+prettyPrint :: Options -> Doc Template.Ann -> IO ()
 prettyPrint opts expanded = do
   tty <- hIsTerminalDevice stdout
 
   let
     render =
-      if tty
+      if tty && not opts.noPretty
         then Ansi.renderStrict
         else Text.renderStrict
 
@@ -117,7 +112,7 @@
           $ reAnnotate AnnTemplate
           $ templatePretty opts.template
       , ""
-      , sectionPretty "Expanded" $ pretty expanded
+      , sectionPretty "Expanded" $ reAnnotate AnnTemplate expanded
       ]
 
 sectionPretty :: Text -> Doc Ann -> Doc Ann
@@ -137,6 +132,8 @@
   AnnTemplate x -> case x of
     AnnPunctuation -> Ansi.bold
     AnnOperator -> Ansi.color Ansi.Magenta
+    AnnIntercalate -> Ansi.color Ansi.Magenta
     AnnVarName -> Ansi.colorDull Ansi.Cyan
+    AnnVarValue -> Ansi.colorDull Ansi.Green
     AnnModifier -> Ansi.colorDull Ansi.Green
     AnnString -> Ansi.italicized <> Ansi.colorDull Ansi.Green
diff --git a/network-uri-template.cabal b/network-uri-template.cabal
--- a/network-uri-template.cabal
+++ b/network-uri-template.cabal
@@ -1,6 +1,6 @@
 cabal-version:      1.18
 name:               network-uri-template
-version:            0.1.1.4
+version:            0.1.2.0
 license:            AGPL-3
 license-file:       COPYING
 maintainer:         Pat Brisbin
diff --git a/src/Network/URI/Template/Expand.hs b/src/Network/URI/Template/Expand.hs
--- a/src/Network/URI/Template/Expand.hs
+++ b/src/Network/URI/Template/Expand.hs
@@ -8,20 +8,25 @@
 -- Portability : POSIX
 module Network.URI.Template.Expand
   ( expandTemplate
+  , expandTemplateDoc
   ) where
 
 import Prelude
 
 import Data.Map.Strict (Map)
-import Data.Text (Text)
+import Data.Text (Text, pack)
 import Network.URI.Template.Internal
+import Network.URI.Template.Internal.Pretty (Ann, Doc, pretty, renderPlain)
 import Network.URI.Template.VarName
 import Network.URI.Template.VarValue
 
 expandTemplate :: Map VarName VarValue -> Template -> Text
-expandTemplate env = foldMap (expandTemplatePart env) . (.unwrap)
+expandTemplate env = pack . renderPlain . expandTemplateDoc env
 
-expandTemplatePart :: Map VarName VarValue -> TemplatePart -> Text
+expandTemplateDoc :: Map VarName VarValue -> Template -> Doc Ann
+expandTemplateDoc env = foldMap (expandTemplatePart env) . (.unwrap)
+
+expandTemplatePart :: Map VarName VarValue -> TemplatePart -> Doc Ann
 expandTemplatePart env = \case
-  Lit t -> t
+  Lit t -> pretty t
   Exp e -> expandExpression env e
diff --git a/src/Network/URI/Template/Internal/Expression.hs b/src/Network/URI/Template/Internal/Expression.hs
--- a/src/Network/URI/Template/Internal/Expression.hs
+++ b/src/Network/URI/Template/Internal/Expression.hs
@@ -16,8 +16,6 @@
 import Prelude
 
 import Data.Map.Strict (Map)
-import Data.Text (Text)
-import Data.Text qualified as T
 import Network.URI.Template.Internal.Operator
 import Network.URI.Template.Internal.Parse
 import Network.URI.Template.Internal.Pretty
@@ -50,14 +48,15 @@
     <> hcat (punctuate "," $ map varSpecPretty e.variableList)
     <> annotate AnnPunctuation "}"
 
-expandExpression :: Map VarName VarValue -> Expression -> Text
+expandExpression :: Map VarName VarValue -> Expression -> Doc Ann
 expandExpression env e =
-  prefix <> T.intercalate oa.listIntercalate vars
+  prefix
+    <> mconcat (punctuate (annotate AnnIntercalate $ pretty oa.listIntercalate) vars)
  where
   oa = maybe nullOperatorActions operatorActions e.operator
 
   prefix
     | null vars = ""
-    | otherwise = oa.listPrefix
+    | otherwise = annotate AnnOperator $ pretty oa.listPrefix
 
-  vars = concatMap (expandVarSpec env oa) e.variableList
+  vars = mconcat $ map (expandVarSpec env oa) e.variableList
diff --git a/src/Network/URI/Template/Internal/Operator.hs b/src/Network/URI/Template/Internal/Operator.hs
--- a/src/Network/URI/Template/Internal/Operator.hs
+++ b/src/Network/URI/Template/Internal/Operator.hs
@@ -18,7 +18,6 @@
 import Prelude
 
 import Data.Text (Text, pack, unpack)
-import Data.Text qualified as T
 import Network.URI (escapeURIString, isReserved, isUnescapedInURIComponent)
 import Network.URI.Template.Internal.Parse
 import Network.URI.Template.Internal.Pretty
@@ -70,7 +69,7 @@
 data OperatorActions = OperatorActions
   { listPrefix :: Text
   , listIntercalate :: Text
-  , renderValue :: VarName -> Text -> Text
+  , renderValue :: VarName -> Doc Ann -> Doc Ann
   , escapeValue :: Text -> Text
   }
 
@@ -117,21 +116,39 @@
     nullOperatorActions
       { listPrefix = ";"
       , listIntercalate = ";"
-      , renderValue = \k v -> unVarName k <> if T.null v then "" else "=" <> v
+      , renderValue = \k v ->
+          mconcat
+            [ annotate AnnVarName $ pretty k
+            , if isEmptyDoc v then "" else annotate AnnPunctuation "="
+            , v
+            ]
       }
   Query ->
     nullOperatorActions
       { listPrefix = "?"
       , listIntercalate = "&"
-      , renderValue = \k v -> unVarName k <> "=" <> v
+      , renderValue = \k v ->
+          mconcat
+            [ annotate AnnVarName $ pretty k
+            , annotate AnnPunctuation "="
+            , v
+            ]
       }
   QueryContinuation ->
     nullOperatorActions
       { listPrefix = "&"
       , listIntercalate = "&"
-      , renderValue = \k v -> unVarName k <> "=" <> v
+      , renderValue = \k v ->
+          mconcat
+            [ annotate AnnVarName $ pretty k
+            , annotate AnnPunctuation "="
+            , v
+            ]
       }
   SpecReserved {} -> nullOperatorActions
 
 escapeURIText :: (Char -> Bool) -> Text -> Text
 escapeURIText p = pack . escapeURIString p . unpack
+
+isEmptyDoc :: Doc ann -> Bool
+isEmptyDoc = (== "") . show -- hack
diff --git a/src/Network/URI/Template/Internal/Pretty.hs b/src/Network/URI/Template/Internal/Pretty.hs
--- a/src/Network/URI/Template/Internal/Pretty.hs
+++ b/src/Network/URI/Template/Internal/Pretty.hs
@@ -22,7 +22,9 @@
 data Ann
   = AnnPunctuation
   | AnnOperator
+  | AnnIntercalate
   | AnnVarName
+  | AnnVarValue
   | AnnModifier
   | AnnString
 
diff --git a/src/Network/URI/Template/Internal/VarSpec.hs b/src/Network/URI/Template/Internal/VarSpec.hs
--- a/src/Network/URI/Template/Internal/VarSpec.hs
+++ b/src/Network/URI/Template/Internal/VarSpec.hs
@@ -48,10 +48,10 @@
   annotate AnnVarName (pretty $ unVarName v.name)
     <> maybe mempty (annotate AnnModifier . modifierPretty) v.modifier
 
-expandVarSpec :: Map VarName VarValue -> OperatorActions -> VarSpec -> [Text]
+expandVarSpec :: Map VarName VarValue -> OperatorActions -> VarSpec -> [Doc Ann]
 expandVarSpec vars oa vs = maybe [] renderValues $ Map.lookup vs.name vars
  where
-  renderValues :: VarValue -> [Text]
+  renderValues :: VarValue -> [Doc Ann]
   renderValues = \case
     VarNull -> []
     VarValue t
@@ -60,13 +60,22 @@
     VarList ts
       | null ts -> []
       | Just Explode <- vs.modifier -> concatMap renderValueEsc ts
-      | otherwise -> renderValueCsv $ map oa.escapeValue ts
+      | otherwise -> renderValueCsv $ map (pretty . oa.escapeValue) ts
     VarMap kvs
       | null kvs -> []
       | Just Explode <- vs.modifier -> map (uncurry $ renderKeyValue '=') kvs
       | otherwise -> renderValueCsv $ map (uncurry $ renderKeyValue ',') kvs
 
+  renderValue :: Doc Ann -> [Doc Ann]
   renderValue = pure . oa.renderValue vs.name
-  renderValueEsc = renderValue . oa.escapeValue
-  renderValueCsv = renderValue . T.intercalate ","
-  renderKeyValue c k v = oa.escapeValue k <> T.singleton c <> oa.escapeValue v
+
+  renderValueEsc = renderValue . annotate AnnVarValue . pretty . oa.escapeValue
+
+  renderValueCsv :: [Doc Ann] -> [Doc Ann]
+  renderValueCsv = renderValue . mconcat . punctuate (annotate AnnPunctuation ",")
+
+  renderKeyValue :: Char -> Text -> Text -> Doc Ann
+  renderKeyValue c k v =
+    annotate AnnVarName (pretty $ oa.escapeValue k)
+      <> annotate AnnPunctuation (pretty c)
+      <> annotate AnnVarValue (pretty $ oa.escapeValue v)
diff --git a/src/Network/URI/Template/VarName.hs b/src/Network/URI/Template/VarName.hs
--- a/src/Network/URI/Template/VarName.hs
+++ b/src/Network/URI/Template/VarName.hs
@@ -30,6 +30,9 @@
 instance IsString VarName where
   fromString = either error id . readVarName
 
+instance Pretty VarName where
+  pretty = pretty . (.unwrap)
+
 readVarName :: String -> Either String VarName
 readVarName = first errorBundlePretty . parse (varNameP <* eof) . pack
 
