diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,9 @@
+# CHANGELOG
+
+## [Unreleased]
+
+## [v0.0.1.0] – 20/10/2021
+* Initial release
+
+[Unreleased]: https://github.com/kleidukos/text-display/compare/v0.0.1.0...HEAD
+[v0.0.1.0]: https://github.com/kleidukos/text-display/releases/tag/v0.0.1.0
diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,20 @@
+Copyright (c) 2021 Hécate Moonlight
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be included
+in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/README.md b/README.md
new file mode 100644
--- /dev/null
+++ b/README.md
@@ -0,0 +1,69 @@
+<p align="center">
+
+<img src="https://raw.githubusercontent.com/haskell-text/text-display/main/images/logo.png" />
+</p>
+
+<p align="center">
+<a href="https://github.com/Kleidukos/text-display/actions"></a>
+  <img src="https://img.shields.io/github/workflow/status/Kleidukos/text-display/CI?style=flat-square" alt="CI badge" />
+</a>
+<a href="https://haskell.org">
+  <img src="https://img.shields.io/badge/Made%20in-Haskell-%235e5086?logo=haskell&style=flat-square" alt="made with Haskell"/>
+</a>
+</p>
+
+<p align="center">
+<a href='https://ko-fi.com/Q5Q327ZHW' target='_blank'><img height='36' style='border:0px;height:36px;' src='https://cdn.ko-fi.com/cdn/kofi1.png?v=3' border='0' alt='Buy Me a Coffee at ko-fi.com' /></a>
+</p>
+
+> *A Typeclass for user-facing output*
+
+
+## Description
+
+The `text-display` library offers a way for developers to print a textual representation of datatypes that does not
+have to abide by the rules of the [Show typeclass][Show].
+
+If you wish to learn more about how things are done and why, please read the [DESIGN.md](./DESIGN.md) file.
+
+## Examples
+
+There are two methods to implement `Display` for your type:
+
+The first one is a manual implementation:
+
+```haskell
+data ManualType = MT Int
+
+-- >>> display (MT 32)
+-- "MT 32"
+instance Display ManualType where
+  displayPrec prec (MT i) = displayParen (prec > 10) $ "MT " <> displayPrec 11 i
+```
+
+But this can be quite time-consuming, especially if your datatype already has
+an existing `Show` that you wish to reuse. In which case, you can piggy-back
+on this instance like this:
+
+```haskell
+{-# LANGUAGE DerivingVia #-}
+data AutomaticallyDerived = AD
+  -- We derive 'Show'
+  deriving Show 
+  -- We take advantage of the 'Show' instance to derive 'Display' from it
+  deriving Display
+    via (ShowInstance AutomaticallyDerived) 
+```
+
+But let's say you want to redact an instance of `Display`? You can do it locally, through
+the `OpaqueInstance` helper. It is most useful to hide tokens or passwords:
+
+```haskell
+data UserToken = UserToken UUID                           
+ deriving Display                                         
+   via (OpaqueInstance "[REDACTED]" UserToken)            
+                                                          
+display $ UserToken "7a01d2ce-31ff-11ec-8c10-5405db82c3cd"
+-- => "[REDACTED]"                                              
+```
+[Show]: https://hackage.haskell.org/package/base/docs/Text-Show.html#v:Show
diff --git a/src/Data/Text/Display.hs b/src/Data/Text/Display.hs
new file mode 100644
--- /dev/null
+++ b/src/Data/Text/Display.hs
@@ -0,0 +1,414 @@
+{-# LANGUAGE DataKinds #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE DerivingVia #-}
+{-# LANGUAGE GeneralizedNewtypeDeriving #-}
+{-# LANGUAGE ScopedTypeVariables #-}
+{-# LANGUAGE StandaloneDeriving #-}
+{-# LANGUAGE StrictData #-}
+{-# LANGUAGE TypeApplications #-}
+{-# LANGUAGE TypeFamilies #-}
+{-# LANGUAGE TypeOperators #-}
+{-# LANGUAGE UndecidableInstances #-}
+
+{-|
+  Module      : Data.Text.Entity
+  Copyright   : © Hécate Moonlight, 2021
+  License     : MIT
+  Maintainer  : hecate@glitchbra.in
+  Stability   : stable
+
+  Use 'display' to produce user-facing text
+
+-}
+module Data.Text.Display
+  ( -- * Documentation
+    display
+  , Display(..)
+  , -- * Deriving your instance automatically
+    ShowInstance(..)
+  , OpaqueInstance(..)
+  , -- * Writing your instance by hand
+    displayParen
+  -- * Design choices
+  -- $designChoices
+  ) where
+
+import Control.Exception hiding (TypeError)
+import Data.ByteString
+import Data.Int
+import Data.Kind
+import Data.List.NonEmpty
+import Data.Text (Text)
+import Data.Text.Lazy.Builder (Builder)
+import Data.Word
+import GHC.Show (showLitString)
+import GHC.TypeLits
+import qualified Data.ByteString.Lazy as BL
+import qualified Data.Text.Lazy.Builder as TB
+import qualified Data.Text.Lazy.Builder.Int as TB
+import qualified Data.Text.Lazy.Builder.RealFloat as TB
+import qualified Data.Text.Lazy as TL
+import Data.Proxy
+
+-- | A typeclass for user-facing output.
+--
+-- @since 0.0.1.0
+class Display a where
+  {-# MINIMAL displayBuilder | displayPrec #-}
+  -- | Implement this method to describe how to convert your value to 'Builder'.
+  displayBuilder :: a -> Builder
+  displayBuilder = displayPrec 0
+
+  -- | The method 'displayList' is provided to allow for a specialised
+  -- way to render lists of a certain value.
+  -- This is used to render the list of 'Char' as a string of characters
+  -- enclosed in double quotes, rather than between square brackets and
+  -- separated by commas.
+  --
+  -- === Example
+  --
+  -- > instance Display Char where
+  -- >   displayBuilder '\'' = "'\\''"
+  -- >   displayBuilder c = "'" <> TB.singleton c <> "\'"
+  -- >   -- 'displayList' is overloaded, so that when the @Display [a]@ instance calls 'displayList',
+  -- >   -- we end up with a nice string enclosed between double quotes.
+  -- >   displayList cs = TB.fromString $ "\"" <> showLitString cs "\""
+  --
+  -- > instance Display a => Display [a] where
+  -- >   -- In this instance, 'displayBuilder' is defined in terms of 'displayList', which for most types
+  -- >   -- is defined as the default written in the class declaration.
+  -- >   -- But when a ~ Char, there is an explicit implementation that is selected instead, which
+  -- >   -- provides the rendering of the character string between double quotes.
+  -- >   displayBuilder = displayList
+  --
+  -- ==== How implementations are selected
+  --
+  -- > displayBuilder ([1,2,3] :: [Int])
+  -- > → displayBuilder @[Int] = displayBuilderList @Int
+  -- > → Default `displayList`
+  -- >
+  -- > displayBuilder ("abc" :: [Char])
+  -- > → displayBuilder @[Char] = displayBuilderList @Char
+  -- > → Custom `displayList`
+  displayList :: [a] -> Builder
+  displayList [] = "[]"
+  displayList (x:xs) = displayList' xs ("[" <> displayBuilder x)
+    where
+      displayList' :: [a] -> Builder -> Builder
+      displayList' [] acc     = acc <> "]"
+      displayList' (y:ys) acc = displayList' ys (acc <> "," <> displayBuilder y)
+
+  -- | The method 'displayPrec' allows you to write instances that
+  -- require nesting. The precedence parameter can be thought of as a
+  -- suggestion coming from the surrounding context for how tightly to bind. If the precedence
+  -- parameter is higher than the precedence of the operator (or constructor, function, etc.)
+  -- being displayed, then that suggests that the output will need to be surrounded in parentheses
+  -- in order to bind tightly enough (see 'displayParen').
+  --
+  -- For example, if an operator constructor is being displayed, then the precedence requirement
+  -- for its arguments will be the precedence of the operator. Meaning, if the argument
+  -- binds looser than the surrounding operator, then it will require parentheses.
+  --
+  -- Note that function/constructor application has an effective precedence of 10.
+  --
+  -- === Examples
+  --
+  -- > instance Display a => Display (Maybe a) where
+  -- >   -- In this instance, we define 'displayPrec' rather than 'displayBuilder' as we need to decide
+  -- >   -- whether or not to surround ourselves in parentheses based on the surrounding context.
+  -- >   -- If the precedence parameter is higher than 10 (the precedence of constructor application)
+  -- >   -- then we indeed need to surround ourselves in parentheses to avoid malformed outputs
+  -- >   -- such as @Just Just 5@.
+  -- >   -- We then set the precedence parameter of the inner 'displayPrec' to 11, as even
+  -- >   -- constructor application is not strong enough to avoid parentheses.
+  -- >   displayPrec _ Nothing = "Nothing"
+  -- >   displayPrec prec (Just a) = displayParen (prec > 10) $ "Just " <> displayPrec 11 a
+  --
+  -- > data Pair a b = a :*: b
+  -- > infix 5 :*: -- arbitrary choice of precedence
+  -- > instance (Display a, Display b) => Display (Pair a b) where
+  -- >   displayPrec prec (a :*: b) = displayParen (prec > 5) $ displayPrec 6 a <> " :*: " <> displayPrec 6 b
+  displayPrec
+    :: Int -- ^ The precedence level passed in by the surrounding context
+    -> a
+    -> Builder
+  displayPrec _ = displayBuilder
+
+
+-- | Convert a value to a readable 'Text'.
+--
+-- === Examples
+-- >>> display 3
+-- "3"
+--
+-- >>> display True
+-- "True"
+--
+-- @since 0.0.1.0
+display :: Display a => a -> Text
+display a = TL.toStrict $ TB.toLazyText $ displayBuilder a
+
+-- | 🚫 You should not try to display functions!
+--
+-- 💡 Write a 'newtype' wrapper that represents your domain more accurately.
+--    If you are not consciously trying to use `display` on a function,
+--    make sure that you are not missing an argument somewhere.
+--
+-- @since 0.0.1.0
+instance CannotDisplayBareFunctions => Display (a -> b) where
+  displayBuilder = undefined
+
+-- | @since 0.0.1.0
+type family CannotDisplayBareFunctions :: Constraint where
+  CannotDisplayBareFunctions = TypeError
+    ( 'Text "🚫 You should not try to display functions!" ':$$:
+      'Text "💡 Write a 'newtype' wrapper that represents your domain more accurately." ':$$:
+      'Text "   If you are not consciously trying to use `display` on a function," ':$$:
+      'Text "   make sure that you are not missing an argument somewhere."
+    )
+
+-- | 🚫 You should not try to display strict ByteStrings!
+--
+-- 💡 Always provide an explicit encoding.
+-- Use 'Data.Text.Encoding.decudeUtf8'' or 'Data.Text.Encoding.decudeUtf8With' to convert from UTF-8
+--
+-- @since 0.0.1.0
+instance CannotDisplayByteStrings => Display ByteString where
+  displayBuilder = undefined
+
+-- | 🚫 You should not try to display lazy ByteStrings!
+--
+-- 💡 Always provide an explicit encoding.
+-- Use 'Data.Text.Encoding.decudeUtf8'' or 'Data.Text.Encoding.decudeUtf8With' to convert from UTF-8
+--
+-- @since 0.0.1.0
+instance CannotDisplayByteStrings => Display BL.ByteString where
+  displayBuilder = undefined
+
+type family CannotDisplayByteStrings :: Constraint where
+  CannotDisplayByteStrings = TypeError
+    ( 'Text "🚫 You should not try to display ByteStrings!" ':$$:
+      'Text "💡 Always provide an explicit encoding" ':$$:
+      'Text     "Use 'Data.Text.Encoding.decudeUtf8'' or 'Data.Text.Encoding.decudeUtf8With' to convert from UTF-8"
+    )
+
+-- | A utility function that surrounds the given 'Builder' with parentheses when the Bool parameter is True.
+-- Useful for writing instances that may require nesting. See the 'displayPrec' documentation for more
+-- information.
+--
+-- @since 0.0.1.0
+displayParen :: Bool -> Builder -> Builder
+displayParen b txt = if b then "(" <> txt <> ")" else txt
+
+-- | This wrapper allows you to create an opaque instance for your type,
+-- useful for redacting sensitive content like tokens or passwords.
+--
+-- === Example
+--
+-- > data UserToken = UserToken UUID
+-- >  deriving Display
+-- >    via (OpaqueInstance "[REDACTED]" UserToken)
+--
+-- > display $ UserToken "7a01d2ce-31ff-11ec-8c10-5405db82c3cd"
+-- > "[REDACTED]"
+--
+-- @since 0.0.1.0
+newtype OpaqueInstance (str :: Symbol) (a :: Type) = Opaque a
+
+-- | This wrapper allows you to create an opaque instance for your type,
+-- useful for redacting sensitive content like tokens or passwords.
+--
+-- @since 0.0.1.0
+instance KnownSymbol str => Display (OpaqueInstance str a) where
+  displayBuilder _ = TB.fromString $ symbolVal (Proxy @str)
+
+-- | This wrapper allows you to rely on a pre-existing 'Show' instance in order to
+-- derive 'Display' from it.
+--
+-- === Example
+--
+-- > data AutomaticallyDerived = AD
+-- >  -- We derive 'Show'
+-- >  deriving stock Show
+-- >  -- We take advantage of the 'Show' instance to derive 'Display' from it
+-- >  deriving Display
+-- >    via (ShowInstance AutomaticallyDerived)
+--
+-- @since 0.0.1.0
+newtype ShowInstance (a :: Type)
+  = ShowInstance a
+  deriving newtype
+    ( Show -- ^ @since 0.0.1.0
+    )
+
+-- | This wrapper allows you to rely on a pre-existing 'Show' instance in order to derive 'Display' from it.
+--
+-- @since 0.0.1.0
+instance Show e => Display (ShowInstance e) where
+  displayBuilder s = TB.fromString $ show s
+
+-- @since 0.0.1.0
+newtype DisplayDecimal e
+  = DisplayDecimal e
+  deriving newtype
+    (Integral, Real, Enum, Ord, Num, Eq)
+
+-- @since 0.0.1.0
+instance Integral e => Display (DisplayDecimal e) where
+  displayBuilder = TB.decimal
+
+-- @since 0.0.1.0
+newtype DisplayRealFloat e
+  = DisplayRealFloat e
+  deriving newtype
+    (RealFloat, RealFrac, Real, Ord, Eq, Num, Fractional, Floating)
+
+-- @since 0.0.1.0
+instance RealFloat e => Display (DisplayRealFloat e) where
+  displayBuilder = TB.realFloat
+
+-- | @since 0.0.1.0
+deriving via (ShowInstance ()) instance Display ()
+
+-- | @since 0.0.1.0
+deriving via (ShowInstance Bool) instance Display Bool
+
+-- | @since 0.0.1.0
+instance Display Char where
+  displayBuilder '\'' = "'\\''"
+  displayBuilder c = "'" <> TB.singleton c <> "\'"
+  -- 'displayList' is overloaded, so that when the @Display [a]@ instance calls 'displayList',
+  -- we end up with a nice string enclosed between double quotes.
+  displayList cs = TB.fromString $ "\"" <> showLitString cs "\""
+
+-- | Lazy 'TL.Text'
+--
+-- @since 0.0.1.0
+instance Display TL.Text where
+  displayBuilder = TB.fromLazyText
+
+-- | Strict 'Data.Text.Text'
+--
+-- @since 0.0.1.0
+instance Display Text where
+  displayBuilder = TB.fromText
+
+-- | @since 0.0.1.0
+instance Display a => Display [a] where
+  {-# SPECIALISE instance Display [String] #-}
+  {-# SPECIALISE instance Display [Char] #-}
+  {-# SPECIALISE instance Display [Int] #-}
+  -- In this instance, 'displayBuilder' is defined in terms of 'displayList', which for most types
+  -- is defined as the default written in the class declaration.
+  -- But when @a ~ Char@, there is an explicit implementation that is selected instead, which
+  -- provides the rendering of the character string between double quotes.
+  displayBuilder = displayList
+
+-- | @since 0.0.1.0
+instance Display a => Display (NonEmpty a) where
+  displayBuilder (a :| as) = displayBuilder a <> TB.fromString " :| " <> displayBuilder as
+
+-- | @since 0.0.1.0
+instance Display a => Display (Maybe a) where
+  -- In this instance, we define 'displayPrec' rather than 'displayBuilder' as we need to decide
+  -- whether or not to surround ourselves in parentheses based on the surrounding context.
+  -- If the precedence parameter is higher than 10 (the precedence of constructor application)
+  -- then we indeed need to surround ourselves in parentheses to avoid malformed outputs
+  -- such as @Just Just 5@.
+  -- We then set the precedence parameter of the inner 'displayPrec' to 11, as even
+  -- constructor application is not strong enough to avoid parentheses.
+  displayPrec _ Nothing = "Nothing"
+  displayPrec prec (Just a) = displayParen (prec > 10) $ "Just " <> displayPrec 11 a
+
+-- | @since 0.0.1.0
+deriving via (DisplayRealFloat Double) instance Display Double
+
+-- | @since 0.0.1.0
+deriving via (DisplayRealFloat Float) instance Display Float
+
+-- | @since 0.0.1.0
+deriving via (DisplayDecimal Int) instance Display Int
+
+-- | @since 0.0.1.0
+deriving via (DisplayDecimal Int8) instance Display Int8
+
+-- | @since 0.0.1.0
+deriving via (DisplayDecimal Int16) instance Display Int16
+
+-- | @since 0.0.1.0
+deriving via (DisplayDecimal Int32) instance Display Int32
+
+-- | @since 0.0.1.0
+deriving via (DisplayDecimal Int64) instance Display Int64
+
+-- | @since 0.0.1.0
+deriving via (DisplayDecimal Integer) instance Display Integer
+
+-- | @since 0.0.1.0
+deriving via (DisplayDecimal Word) instance Display Word
+
+-- | @since 0.0.1.0
+deriving via (DisplayDecimal Word8) instance Display Word8
+
+-- | @since 0.0.1.0
+deriving via (DisplayDecimal Word16) instance Display Word16
+
+-- | @since 0.0.1.0
+deriving via (DisplayDecimal Word32) instance Display Word32
+
+-- | @since 0.0.1.0
+deriving via (DisplayDecimal Word64) instance Display Word64
+
+-- | @since 0.0.1.0
+deriving via (ShowInstance IOException) instance Display IOException
+
+-- | @since 0.0.1.0
+deriving via (ShowInstance SomeException) instance Display SomeException
+
+-- | @since 0.0.1.0
+instance (Display a, Display b) => Display (a, b) where
+  displayBuilder (a, b) = "(" <> displayBuilder a <>  "," <> displayBuilder b <> ")"
+
+-- | @since 0.0.1.0
+instance (Display a, Display b, Display c) => Display (a, b, c) where
+  displayBuilder (a, b, c) = "(" <> displayBuilder a <>  "," <> displayBuilder b <> "," <> displayBuilder c <> ")"
+
+-- | @since 0.0.1.0
+instance (Display a, Display b, Display c, Display d) => Display (a, b, c, d) where
+  displayBuilder (a, b, c, d) = "(" <> displayBuilder a <>  "," <> displayBuilder b <> "," <> displayBuilder c <> "," <> displayBuilder d <> ")"
+
+-- $designChoices
+--
+-- === A “Lawless Typeclass”
+--
+-- The `Display` typeclass does not contain any law. This is a controversial choice for some people,
+-- but the truth is that there are not any laws to ask of the consumer that are not already enforced
+-- by the type system and the internals of the `Data.Text.Internal.Text` type.
+--
+-- === "🚫 You should not try to display functions!"
+--
+-- Sometimes, when using the library, you may encounter this message:
+--
+-- > • 🚫 You should not try to display functions!
+-- >   💡 Write a 'newtype' wrapper that represents your domain more accurately.
+-- >      If you are not consciously trying to use `display` on a function,
+-- >      make sure that you are not missing an argument somewhere.
+--
+-- The `display` library does not allow the definition and usage of `Display` on
+-- bare function types (@(a -> b)@).
+-- Experience and time have shown that due to partial application being baked in the language,
+-- many users encounter a partial application-related error message when a simple missing
+-- argument to a function is the root cause.
+--
+-- There may be legitimate uses of a `Display` instance on a function type.
+-- But these usages are extremely dependent on their domain of application.
+-- That is why it is best to wrap them in a newtype that can better
+-- express and enforce the domain.
+--
+-- === "🚫 You should not try to display ByteStrings!"
+--
+-- An arbitrary ByteStrings cannot be safely converted to text without prior knowledge of its encoding.
+--
+-- As such, in order to avoid dangerously blind conversions, it is recommended to use a specialised
+-- function such as `Data.Text.Encoding.decudeUtf8'` or `Data.Text.Encoding.decudeUtf8With` if you wish to turn a UTF8-encoded ByteString
+-- to Text.
diff --git a/test/Main.hs b/test/Main.hs
new file mode 100644
--- /dev/null
+++ b/test/Main.hs
@@ -0,0 +1,86 @@
+{-# LANGUAGE DerivingVia #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE TypeApplications #-}
+{-# LANGUAGE DataKinds #-}
+{-# OPTIONS_GHC -fdefer-type-errors -Wno-deferred-type-errors #-}
+
+module Main where
+
+import Data.ByteString
+import Data.List.NonEmpty
+import Data.Text (Text)
+import Test.Hspec
+import Test.ShouldNotTypecheck (shouldNotTypecheck)
+import qualified Data.List.NonEmpty as NE
+import qualified Data.Text as T
+
+import Data.Text.Display
+
+main :: IO ()
+main = hspec spec
+
+data AutomaticallyDerived = AD
+  deriving stock Show
+  deriving Display via (ShowInstance AutomaticallyDerived)
+
+data ManualType = MT Int
+
+instance Display ManualType where
+  displayPrec prec (MT i) = displayParen (prec > 10) $ "MT " <> displayPrec 11 i
+
+data OpaqueType = OpaqueType Int
+  deriving Display
+    via (OpaqueInstance "<opaque>" OpaqueType)
+
+spec :: Spec
+spec = do
+  describe "Display Tests" $ do
+    it "Display instance for Text stays the same" $
+      display ("3" :: Text) `shouldBe` ("3" :: Text)
+    it "Deriving via its own Show instance works" $
+      T.unpack (display AD) `shouldBe` show AD
+    it "Opaque types stay opaque" $
+      display (OpaqueType 3 :: OpaqueType) `shouldBe` "<opaque>"
+    it "Manual instance is stays the same" $
+      display (MT 32) `shouldBe` "MT 32"
+    it "List instance is equivalent to Show" $ do
+      let list = [1 .. 5] :: [Int]
+      T.unpack (display list) `shouldBe` show list
+    it "Single-element List instance is equivalent to Show" $ do
+      let list = [1] :: [Int]
+      T.unpack (display list) `shouldBe` show list
+    it "NonEmpty instance is equivalent to Show" $ do
+      let ne = NE.fromList [1 .. 5] :: NonEmpty Int
+      T.unpack (display ne) `shouldBe` show ne
+    it "Just True instance is equivalent to Show" $ do
+      T.unpack (display (Just True)) `shouldBe` show (Just True)
+    it "Nested Maybe instance is equivalent to Show" $ do
+      let nestedMaybe = Just (Just 5) :: Maybe (Maybe Int)
+      T.unpack (display nestedMaybe) `shouldBe` show nestedMaybe
+    it "Nothing instance is equivalent to Show" $ do
+      T.unpack (display (Nothing @Bool)) `shouldBe` show (Nothing @Bool)
+    it "String instance is equivalent to Show" $ do
+      let string = "Bonjour \"tout le monde\" !" :: String
+      T.unpack (display string) `shouldBe` show string
+    it "Char 'c' instance is equivalent to Show" $ do
+      T.unpack (display 'c') `shouldBe` show 'c'
+    it "Char '\'' instance is equivalent to Show" $ do
+      T.unpack (display '\'') `shouldBe` show '\''
+    it "2-Tuple instance is equivalent to Show" $ do
+      let tuple = (1 :: Int, True)
+      T.unpack (display tuple) `shouldBe` show tuple
+    it "3-Tuple instance is equivalent to Show" $ do
+      let tuple = (1 :: Int, True, "hahahha" :: String)
+      T.unpack (display tuple) `shouldBe` show tuple
+
+    it "Should not compile for a function instance" $
+      shouldNotTypecheck (display id) `shouldThrow` anyErrorCall
+    it "Should not compile for ByteStrings" $
+      let bs = "badstring" :: ByteString
+       in shouldNotTypecheck (display bs) `shouldThrow` anyErrorCall
+
+  describe "displayParen tests" $ do
+    it "surrounds with parens when True" $
+      displayParen True "foo" `shouldBe` "(foo)"
+    it "doesn't surround with parens when False" $
+      displayParen False "foo" `shouldBe` "foo"
diff --git a/text-display.cabal b/text-display.cabal
new file mode 100644
--- /dev/null
+++ b/text-display.cabal
@@ -0,0 +1,61 @@
+cabal-version:      3.0
+name:               text-display
+version:            0.0.1.0
+category:           Text
+synopsis:           A typeclass for user-facing output
+description:
+  The 'Display' typeclass provides a solution for user-facing output that does not have to abide by the rules of the Show typeclass.
+
+homepage:           https://github.com/haskell-text/text-display#readme
+bug-reports:        https://github.com/haskell-text/text-display/issues
+author:             Hécate Moonlight
+maintainer:         Hécate Moonlight
+license:            MIT
+build-type:         Simple
+tested-with:        GHC ==8.8.4 || ==8.10.7 || ==9.0.1 || ==9.2.1
+extra-source-files:
+  CHANGELOG.md
+  LICENSE
+  README.md
+
+source-repository head
+  type:     git
+  location: https://github.com/haskell-text/text-display
+
+common common-extensions
+  default-language: Haskell2010
+
+common common-ghc-options
+  ghc-options:
+    -Wall -Wcompat -Widentities -Wincomplete-record-updates
+    -Wincomplete-uni-patterns -Wpartial-fields -Wredundant-constraints
+    -fhide-source-paths -Wno-unused-do-bind -fwrite-ide-info
+    -hiedir=.hie -haddock
+
+common common-rts-options
+  ghc-options: -rtsopts -threaded -with-rtsopts=-N
+
+library
+  import:          common-extensions
+  import:          common-ghc-options
+  hs-source-dirs:  src
+  exposed-modules: Data.Text.Display
+  build-depends:
+    , base        >= 4.12 && <= 5
+    , bytestring  ^>=0.11
+    , text        ^>=1.2
+
+test-suite text-display-test
+  import:         common-extensions
+  import:         common-ghc-options
+  import:         common-rts-options
+  type:           exitcode-stdio-1.0
+  main-is:        Main.hs
+  hs-source-dirs: test
+  build-depends:
+    , base
+    , bytestring
+    , text-display
+    , hspec
+    , text
+    , should-not-typecheck
