diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,6 @@
+## 0.1.1 (2024-05-16)
+* add `Doc` type for `ErrorMessage`-like errors, except term level also
+
 ## 0.1.0 (2024-05-07)
 Initial release.
 
diff --git a/src/TypeLevelShow/Doc.hs b/src/TypeLevelShow/Doc.hs
new file mode 100644
--- /dev/null
+++ b/src/TypeLevelShow/Doc.hs
@@ -0,0 +1,53 @@
+{-# LANGUAGE AllowAmbiguousTypes #-} -- for reifying
+
+module TypeLevelShow.Doc where
+
+import GHC.TypeLits qualified as TE -- TE = TypeError
+import GHC.TypeLits ( Symbol, KnownSymbol, symbolVal' )
+import GHC.Exts ( proxy# )
+
+-- | Simple pretty document ADT.
+--
+-- Designed to work on both type level (as a limited 'TE.ErrorMessage') and term
+-- level (as a boring ADT).
+--
+-- Note that 'TE.ShowType' is magical (see
+-- @compiler/GHC/Core/Type.hs#L1309@), so we need to remove it for term level.
+--
+-- singletons-base defines a version of this, but retains the 'TE.ShowType'
+-- constructor and is in the singletons ecosystem.
+data Doc s
+  = Text s
+  -- ^ plain ol' text
+  | Doc s :<>: Doc s
+  -- ^ append docs next to each other
+  | Doc s :$$: Doc s
+  -- ^ stack docs on top of each other (newline)
+    deriving stock Show
+
+-- | Promoted 'Doc'.
+type PDoc = Doc Symbol
+
+-- | Render a 'PDoc' as an 'ErrorMessage', for type-level error messages.
+--
+-- 'PDoc' is a subset of 'ErrorMessage', so this is very boring.
+type RenderDoc :: PDoc -> TE.ErrorMessage
+type family RenderDoc doc where
+    RenderDoc (Text s)   = TE.Text s
+    RenderDoc (l :<>: r) = RenderDoc l TE.:<>: RenderDoc r
+    RenderDoc (l :$$: r) = RenderDoc l TE.:$$: RenderDoc r
+
+-- | Reify a promoted 'Doc' to the corresponding term-level one.
+class ReifyDoc (doc :: PDoc) where
+    -- TODO do we want to do IsString directly in here? will it guarantee better
+    --      performance, rather than mapping afterwards? hard to say
+    reifyDoc :: Doc String
+
+instance KnownSymbol s => ReifyDoc (Text s) where
+    reifyDoc = Text $ symbolVal' (proxy# @s)
+
+instance (ReifyDoc l, ReifyDoc r) => ReifyDoc (l :<>: r) where
+    reifyDoc = reifyDoc @l :<>: reifyDoc @r
+
+instance (ReifyDoc l, ReifyDoc r) => ReifyDoc (l :$$: r) where
+    reifyDoc = reifyDoc @l :$$: reifyDoc @r
diff --git a/type-level-show.cabal b/type-level-show.cabal
--- a/type-level-show.cabal
+++ b/type-level-show.cabal
@@ -5,7 +5,7 @@
 -- see: https://github.com/sol/hpack
 
 name:           type-level-show
-version:        0.1.0
+version:        0.1.1
 synopsis:       Utilities for writing Show-like type families
 description:    Please see README.md.
 category:       Types, Data
@@ -31,6 +31,7 @@
 
 library
   exposed-modules:
+      TypeLevelShow.Doc
       TypeLevelShow.Natural
       TypeLevelShow.Utils
   other-modules:
