diff --git a/diagnostician-terminal.cabal b/diagnostician-terminal.cabal
new file mode 100644
--- /dev/null
+++ b/diagnostician-terminal.cabal
@@ -0,0 +1,34 @@
+cabal-version: 3.4
+name: diagnostician-terminal
+version: 0.2.0.1
+license: (Apache-2.0 OR MIT)
+maintainer: root@owenlynch.org
+author: Coln contributors
+description: ANSI terminal rendering backend for Diagnostician
+category: Language
+
+source-repository head
+  type: git
+  location: https://github.com/coln-project/Coln
+
+library
+  exposed-modules: Diagnostician.Terminal
+  hs-source-dirs: src
+  default-language: GHC2024
+  default-extensions:
+    DuplicateRecordFields
+    NoFieldSelectors
+    OrPatterns
+    OverloadedRecordDot
+    OverloadedStrings
+
+  ghc-options:
+    -Wall
+
+  build-depends:
+    ansi-terminal,
+    base ^>=4.21.0.0 || ^>=4.22,
+    diagnostician,
+    prettyprinter,
+    prettyprinter-ansi-terminal,
+    text,
diff --git a/src/Diagnostician/Terminal.hs b/src/Diagnostician/Terminal.hs
new file mode 100644
--- /dev/null
+++ b/src/Diagnostician/Terminal.hs
@@ -0,0 +1,38 @@
+-- SPDX-FileCopyrightText: 2026 Coln contributors
+--
+-- SPDX-License-Identifier: Apache-2.0 OR MIT
+
+module Diagnostician.Terminal (
+  terminalReporter,
+) where
+
+import Diagnostician
+import Prettyprinter (hardline)
+import Prettyprinter.Render.Terminal qualified as Ansi
+import Prettyprinter.Render.Text qualified as Text
+import System.Console.ANSI (hSupportsANSI)
+import System.IO (Handle)
+
+-- | Writes diagnostics to the handle, containing ANSI colour escape codes only if the handle refers to a terminal.
+terminalReporter :: (Code a) => Handle -> Reporter a
+terminalReporter handle =
+  Reporter
+    { reportIO = \d -> do
+        isTerm <- hSupportsANSI handle
+        let render =
+              if isTerm
+                then Ansi.putDoc . fmap (toAnsiStyle (codeMeta d.code).severity)
+                else Text.putDoc
+        render $ dpretty d <> hardline
+    }
+
+toAnsiStyle :: Severity -> DiagnosticAnn -> Ansi.AnsiStyle
+toAnsiStyle severity = \case
+  DSeverity; DSpan -> Ansi.color colour
+  DCode; DBar -> Ansi.colorDull colour
+ where
+  colour = case severity of
+    SDebug -> Ansi.Magenta
+    SInfo -> Ansi.Blue
+    SWarning -> Ansi.Yellow
+    SError -> Ansi.Red
