diff --git a/diagnostician-html.cabal b/diagnostician-html.cabal
new file mode 100644
--- /dev/null
+++ b/diagnostician-html.cabal
@@ -0,0 +1,33 @@
+cabal-version: 3.4
+name: diagnostician-html
+version: 0.2.0.1
+license: (Apache-2.0 OR MIT)
+maintainer: root@owenlynch.org
+author: Coln contributors
+description: Lucid HTML rendering backend for Diagnostician
+category: Language
+
+source-repository head
+  type: git
+  location: https://github.com/coln-project/Coln
+
+library
+  exposed-modules: Diagnostician.HTML
+  hs-source-dirs: src
+  default-language: GHC2024
+  default-extensions:
+    DuplicateRecordFields
+    NoFieldSelectors
+    OverloadedRecordDot
+    OverloadedStrings
+
+  ghc-options:
+    -Wall
+
+  build-depends:
+    base ^>=4.21.0.0 || ^>=4.22,
+    diagnostician,
+    lucid,
+    prettyprinter,
+    prettyprinter-lucid,
+    text,
diff --git a/src/Diagnostician/HTML.hs b/src/Diagnostician/HTML.hs
new file mode 100644
--- /dev/null
+++ b/src/Diagnostician/HTML.hs
@@ -0,0 +1,39 @@
+-- SPDX-FileCopyrightText: 2026 Coln contributors
+--
+-- SPDX-License-Identifier: Apache-2.0 OR MIT
+
+module Diagnostician.HTML (
+  diagnosticToHtml,
+) where
+
+import Data.Text (Text)
+import Diagnostician
+import Lucid (Html, class_, div_, span_)
+import Prettyprinter (LayoutOptions (LayoutOptions, layoutPageWidth), PageWidth (Unbounded), layoutPretty)
+import Prettyprinter.Lucid (renderHtml)
+import Prettyprinter.Render.Util.SimpleDocTree (treeForm)
+
+-- | Render a diagnostic to some HTML with classes for annotations.
+diagnosticToHtml :: (Code a) => Diagnostic a -> Html ()
+diagnosticToHtml d =
+  div_
+    [class_ ("diag-" <> severityClassSuffix (codeMeta d.code).severity)]
+    . renderHtml
+    . treeForm
+    . fmap (span_ . pure @[] . class_ . ("ann-" <>) . annClassSuffix)
+    . layoutPretty LayoutOptions{layoutPageWidth = Unbounded}
+    $ dpretty d
+
+severityClassSuffix :: Severity -> Text
+severityClassSuffix = \case
+  SDebug -> "debug"
+  SInfo -> "info"
+  SWarning -> "warning"
+  SError -> "error"
+
+annClassSuffix :: DiagnosticAnn -> Text
+annClassSuffix = \case
+  DSeverity -> "severity"
+  DCode -> "code"
+  DBar -> "bar"
+  DSpan -> "span"
