diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,3 @@
+# 0.1.0.0
+
+- Initial implementation.
diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,29 @@
+BSD 3-Clause License
+
+Copyright (c) 2020, Christoph Hermann
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+1. Redistributions of source code must retain the above copyright notice, this
+   list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright notice,
+   this list of conditions and the following disclaimer in the documentation
+   and/or other materials provided with the distribution.
+
+3. Neither the name of the copyright holder nor the names of its
+   contributors may be used to endorse or promote products derived from
+   this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/README.md b/README.md
new file mode 100644
--- /dev/null
+++ b/README.md
@@ -0,0 +1,23 @@
+# pretty-diff
+
+Pretty printing a diff of two values.
+
+## Usage
+
+```hs
+import qualified Pretty.Diff as Diff
+import Data.Default (def)
+
+Diff.pretty def "1234" "_23"
+```
+Will create a string that looks like this:
+
+```
+ ▼ ▼
+"1234"
+╷
+│
+╵
+"_23"
+ ▲
+```
diff --git a/pretty-diff.cabal b/pretty-diff.cabal
new file mode 100644
--- /dev/null
+++ b/pretty-diff.cabal
@@ -0,0 +1,62 @@
+cabal-version: 1.18
+
+-- This file has been generated from package.yaml by hpack version 0.33.0.
+--
+-- see: https://github.com/sol/hpack
+--
+-- hash: 4f4d19546099374e472ed7d6dd40094526c70cb5b1e0b7c2033232726b349f95
+
+name:           pretty-diff
+version:        0.1.0.0
+synopsis:       Pretty printing a simple diff of two values.
+description:    Please see the README at <https://github.com/stoeffel/pretty-diff>.
+category:       Diffing
+homepage:       https://github.com/stoeffel/pretty-diff#readme
+bug-reports:    https://github.com/stoeffel/pretty-diff/issues
+author:         Christoph Hermann
+maintainer:     schtoeffel@gmail.com
+copyright:      2020 Christoph Hermann
+license:        BSD3
+license-file:   LICENSE
+build-type:     Simple
+extra-doc-files:
+    README.md
+    CHANGELOG.md
+
+source-repository head
+  type: git
+  location: https://github.com/stoeffel/pretty-diff
+
+library
+  exposed-modules:
+      Pretty.Diff
+  other-modules:
+      Paths_pretty_diff
+  hs-source-dirs:
+      src
+  build-depends:
+      Diff >=0.3 && <0.4
+    , base >=4.10.1.0 && <5
+    , data-default >=0.7 && <0.8
+    , text >=1.2 && <1.3
+  default-language: Haskell2010
+
+test-suite spec
+  type: exitcode-stdio-1.0
+  main-is: Main.hs
+  other-modules:
+      Pretty.Diff
+      Paths_pretty_diff
+  hs-source-dirs:
+      test
+      src
+  build-depends:
+      Diff >=0.3 && <0.4
+    , base
+    , data-default >=0.7 && <0.8
+    , pretty-diff
+    , tasty >=1.1 && <1.3
+    , tasty-hunit
+    , tasty-test-reporter
+    , text >=1.2 && <1.3
+  default-language: Haskell2010
diff --git a/src/Pretty/Diff.hs b/src/Pretty/Diff.hs
new file mode 100644
--- /dev/null
+++ b/src/Pretty/Diff.hs
@@ -0,0 +1,189 @@
+{-# LANGUAGE NamedFieldPuns #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+-- |
+-- Printing nice and simple diffs of two values.
+--
+-- @
+-- import qualified Pretty.Diff as Diff
+-- import Data.Default (def)
+--
+-- Diff.pretty def "1234" "_23"
+-- @
+--
+-- Will create a string that looks like this:
+--
+-- @
+--  ▼ ▼
+-- "1234"
+-- ╷
+-- │
+-- ╵
+-- "_23"
+--  ▲
+-- @
+module Pretty.Diff
+  ( -- * Configuration
+    Config (Config, separatorText, wrapping),
+    Wrapping (Wrap, NoWrap),
+
+    -- * pretty printing
+    pretty,
+    actual,
+    expected,
+  )
+where
+
+import qualified Data.Algorithm.Diff as Diff
+import Data.Default (Default, def)
+import Data.Function ((&))
+import Data.List (transpose)
+import Data.Maybe (fromMaybe, mapMaybe)
+import Data.String (IsString)
+import qualified Data.Text as Text
+import Data.Text (Text)
+import Prelude
+
+-- | Configuration for `Pretty.Diff.pretty`.
+data Config
+  = Config
+      { -- | Text that gets displayed inbetween the diffed values
+        --
+        -- @
+        -- Diff.pretty def { Diff.separatorText = "differing" } "1234" "_23"
+        -- @
+        --
+        -- Will create a string that looks like this:
+        --
+        -- @
+        --  ▼ ▼
+        -- "1234"
+        -- ╷
+        -- │ differing
+        -- ╵
+        -- "_23"
+        --  ▲
+        -- @
+        separatorText :: Maybe Text,
+        -- | Wrapping text to multiple lines if they are longer than the provided length.
+        -- This is useful in combination with [terminal-size](https://hackage.haskell.org/package/terminal-size).
+        --
+        -- @
+        -- Diff.pretty def { Diff.wrapping = Diff.Wrap 6 } "0900000000" "9000000000"
+        -- @
+        --
+        -- Will create a string that looks like this:
+        --
+        -- @
+        --  ▼
+        -- "09000
+        -- 00000"
+        -- ╷
+        -- │
+        -- ╵
+        -- "90000
+        -- 00000"
+        --     ▲
+        -- @
+        wrapping :: Wrapping
+      }
+
+instance Default Config where
+  def = Config {separatorText = Nothing, wrapping = NoWrap}
+
+-- | Define whether or not to wrap the diffing lines.
+data Wrapping
+  = Wrap Int
+  | NoWrap
+
+-- | Printing a full diff of both values separated by some pipes.
+pretty :: Show a => Config -> a -> a -> Text
+pretty Config {separatorText, wrapping} x y =
+  [ actual wrapping x y,
+    separator separatorText,
+    expected wrapping x y
+  ]
+    & mconcat
+
+-- | Printing The first value and the diff indicator above.
+--
+--  @
+--  Diff.actual Diff.NoWrap "1234" "_23"
+--  @
+--
+--  @
+--  ▼ ▼
+-- "1234"
+--  @
+actual :: Show a => Wrapping -> a -> a -> Text
+actual wrapping x y =
+  wrap wrapping [diffLine First down x y, Text.pack (show x)]
+    & filterEmptyLines
+    & Text.unlines
+
+-- | Printing The second value and the diff indicator below.
+--
+--  @
+--  Diff.expected Diff.NoWrap "1234" "_23"
+--  @
+--
+--  @
+-- "_23"
+--  ▲
+--  @
+expected :: Show a => Wrapping -> a -> a -> Text
+expected wrapping x y =
+  wrap wrapping [Text.pack (show y), diffLine Second up x y]
+    & filterEmptyLines
+    & Text.unlines
+
+wrap :: Wrapping -> [Text] -> [Text]
+wrap wrapping text =
+  case wrapping of
+    Wrap n ->
+      text
+        & fmap (Text.chunksOf n)
+        & interleaveLists
+    NoWrap -> text
+
+down :: Char
+down = '▼'
+
+up :: Char
+up = '▲'
+
+data Position = First | Second
+
+diffLine :: Show a => Position -> Char -> a -> a -> Text.Text
+diffLine pos differ a b =
+  Diff.getDiff
+    (show a)
+    (show b)
+    & mapMaybe (toDiffLine pos differ)
+    & Text.pack
+    & Text.stripEnd
+
+toDiffLine :: Position -> Char -> Diff.Diff a -> Maybe Char
+toDiffLine pos c d =
+  case d of
+    Diff.First _ -> case pos of
+      First -> Just c
+      Second -> Nothing
+    Diff.Second _ -> case pos of
+      First -> Nothing
+      Second -> Just c
+    Diff.Both _ _ -> Just ' '
+
+separator :: Maybe Text -> Text
+separator maybeComparison =
+  [ "╷",
+    "│" <> (fromMaybe "" $ ((<>) " ") <$> maybeComparison),
+    "╵"
+  ]
+    & Text.unlines
+
+interleaveLists :: [[a]] -> [a]
+interleaveLists = mconcat . transpose
+
+filterEmptyLines :: [Text] -> [Text]
+filterEmptyLines = filter (not . Text.null . Text.strip)
diff --git a/test/Main.hs b/test/Main.hs
new file mode 100644
--- /dev/null
+++ b/test/Main.hs
@@ -0,0 +1,173 @@
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE ScopedTypeVariables #-}
+
+module Main
+  ( main,
+  )
+where
+
+import Control.Exception (catch, throw)
+import Data.Default (def)
+import Data.Function ((&))
+import qualified Data.Text as Text
+import qualified Data.Text.IO as Text.IO
+import qualified Pretty.Diff as Diff
+import Test.Tasty
+import Test.Tasty.HUnit
+import qualified Test.Tasty.Runners.Reporter as Reporter
+
+main = defaultMainWithIngredients [Reporter.ingredient] tests
+
+tests :: TestTree
+tests =
+  testGroup
+    "Pretty.Diff"
+    [ testCase "Comparing two equal strings" $
+        expectDiffToEqual
+          ( Diff.pretty
+              def
+              "Hello"
+              "Hello"
+          )
+          [ "Hello" & showText,
+            "╷",
+            "│",
+            "╵",
+            "Hello" & showText
+          ],
+      testCase "Removed characters" $
+        expectDiffToEqual
+          ( Diff.pretty
+              def
+              "Hello"
+              "Hel"
+          )
+          [ "   ▼▼" & forEscapedString,
+            "Hello" & showText,
+            "╷",
+            "│",
+            "╵",
+            "Hel" & showText
+          ],
+      testCase "Added characters" $
+        expectDiffToEqual
+          ( Diff.pretty
+              def
+              "Hel"
+              "Hello"
+          )
+          [ "Hel" & showText,
+            "╷",
+            "│",
+            "╵",
+            "Hello" & showText,
+            "   ▲▲" & forEscapedString
+          ],
+      testCase "Changed characters" $
+        expectDiffToEqual
+          ( Diff.pretty
+              def
+              "Axxx"
+              "Bxxx"
+          )
+          [ "▼" & forEscapedString,
+            "Axxx" & showText,
+            "╷",
+            "│",
+            "╵",
+            "Bxxx" & showText,
+            "▲" & forEscapedString
+          ],
+      testCase "Mixed changes" $
+        expectDiffToEqual
+          ( Diff.pretty
+              def
+              "12345"
+              "1004"
+          )
+          [ " ▼▼ ▼" & forEscapedString,
+            "12345" & showText,
+            "╷",
+            "│",
+            "╵",
+            "1004" & showText,
+            " ▲▲" & forEscapedString
+          ],
+      testCase "Multiline changes (on first and last line)" $
+        expectDiffToEqual
+          ( Diff.pretty
+              def
+                { Diff.wrapping = Diff.Wrap $ 5 + 1 -- + 1 because of "
+                }
+              "0900000000"
+              "9000000000"
+          )
+          [ "▼" & forEscapedString,
+            "09000" & showTextPrefix,
+            "00000" & showTextPostfix,
+            "╷",
+            "│",
+            "╵",
+            "90000" & showTextPrefix,
+            "00000" & showTextPostfix,
+            "    ▲"
+          ],
+      testCase "Multiline changes (inbetween)" $
+        expectDiffToEqual
+          ( Diff.pretty
+              def
+                { Diff.wrapping = Diff.Wrap $ 5 + 1 -- + 1 because of "
+                }
+              "0000090000"
+              "0090000000"
+          )
+          [ "00000" & showTextPrefix,
+            "▼",
+            "90000" & showTextPostfix,
+            "╷",
+            "│",
+            "╵",
+            "00900" & showTextPrefix,
+            "  ▲" & forEscapedString,
+            "00000" & showTextPostfix
+          ],
+      testCase "With separator text" $
+        expectDiffToEqual
+          ( Diff.pretty
+              Diff.Config
+                { Diff.separatorText = Just "equals",
+                  Diff.wrapping = Diff.Wrap $ 5 + 1 -- + 1 because of "
+                }
+              "0000090000"
+              "0090000000"
+          )
+          [ "00000" & showTextPrefix,
+            "▼",
+            "90000" & showTextPostfix,
+            "╷",
+            "│ equals",
+            "╵",
+            "00900" & showTextPrefix,
+            "  ▲" & forEscapedString,
+            "00000" & showTextPostfix
+          ]
+    ]
+
+expectDiffToEqual actual expected_ = do
+  let expected = Text.unlines expected_
+  (actual @?= expected)
+    `catch` ( \(e :: HUnitFailure) -> do
+                Text.IO.putStrLn "Actual was:"
+                Text.IO.putStrLn actual
+                Text.IO.putStrLn "Expected was:"
+                Text.IO.putStrLn expected
+                throw e
+            )
+
+forEscapedString x = " " <> x
+
+showText = Text.pack . show
+
+showTextPrefix x = "\"" <> x
+
+showTextPostfix x = x <> "\""
