diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,7 @@
+# `comma-and` changelog
+
+Convert a list of items to text by adding commas and an "and" at the end.
+
+## Version 0.1.0.0
+
+The first working version of `comma-and`.
diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,30 @@
+Copyright Willem Van Onsem (c) 2020
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+    * Redistributions of source code must retain the above copyright
+      notice, this list of conditions and the following disclaimer.
+
+    * 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.
+
+    * Neither the name of Willem Van Onsem nor the names of other
+      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
+OWNER 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,27 @@
+# comma-and
+[![Build Status of the package by GitHub actions](https://github.com/hapytex/comma-and/actions/workflows/build-ci.yml/badge.svg)](https://github.com/hapytex/comma-and/actions/workflows/build-ci.yml)
+[![Build Status of the package by Hackage](https://matrix.hackage.haskell.org/api/v2/packages/comma-and/badge)](https://matrix.hackage.haskell.org/#/package/comma-and)
+[![Hackage version badge](https://img.shields.io/hackage/v/comma-and.svg)](https://hackage.haskell.org/package/comma-and)
+
+Joins text items together by separating these with commas, and "and" at the end.
+
+## Usage
+
+We can combine elements with:
+
+```
+ghci> comma ["red", "green", "blue"]
+"red, green, and blue"
+```
+
+The package has tooling for the two different comma styles, and can work with any *string-like* type.
+
+## `comma-and` is *safe* Haskell
+
+The module is compiled with `Safe` and does not depend on unsafe modules.
+
+## Contribute
+
+You can contribute by making a pull request on the [*GitHub repository*](https://github.com/hapytex/comma-and).
+
+You can contact the package maintainer by sending a mail to [`hapytexeu+gh@gmail.com`](mailto:hapytexeu+gh@gmail.com).
diff --git a/Setup.hs b/Setup.hs
new file mode 100644
--- /dev/null
+++ b/Setup.hs
@@ -0,0 +1,2 @@
+import Distribution.Simple
+main = defaultMain
diff --git a/comma-and.cabal b/comma-and.cabal
new file mode 100644
--- /dev/null
+++ b/comma-and.cabal
@@ -0,0 +1,53 @@
+name:                comma-and
+version:             0.1.0.0
+synopsis: Join text together with commas, and "and".
+description: Convert a 'Text' object into a visually pleasant URL component.
+homepage:            https://github.com/hapytex/comma-and#readme
+license:             BSD3
+license-file:        LICENSE
+author:              Willem Van Onsem
+maintainer:          hapytexeu+gh@gmail.com
+copyright:           2020 Willem Van Onsem
+category:            utils
+build-type:          Simple
+extra-source-files:
+    README.md
+  , CHANGELOG.md
+cabal-version:       >=1.10
+
+library
+  hs-source-dirs:      src
+  exposed-modules:
+      Text.Comma
+  build-depends:
+      base >= 4.7 && < 5
+    , data-default-class >=0.1 && <0.2
+  default-language:    Haskell2010
+
+test-suite gulsify
+  type:                exitcode-stdio-1.0
+  main-is:             Main.hs
+  hs-source-dirs:      test
+  other-modules:
+      Text.CommaSpec
+  build-depends:
+      base
+    , comma-and
+    , data-default-class >=0.1 && <0.2
+    , hspec ==2.*
+    , QuickCheck >=2.8
+  build-tool-depends: hspec-discover:hspec-discover == 2.*
+  default-language:    Haskell2010
+  default-extensions:
+      BlockArguments
+    , OverloadedStrings
+  ghc-options:
+    -Wall -Wcompat -Wcompat
+    -Wincomplete-record-updates
+    -Wincomplete-uni-patterns
+    -Wredundant-constraints
+
+
+source-repository head
+  type:     git
+  location: https://github.com/hapytex/comma-and
diff --git a/src/Text/Comma.hs b/src/Text/Comma.hs
new file mode 100644
--- /dev/null
+++ b/src/Text/Comma.hs
@@ -0,0 +1,108 @@
+{-# LANGUAGE Safe #-}
+
+{-|
+Module      : Text.Comma
+Description : Join text together with a comma, and "and".
+Maintainer  : hapytexeu+gh@gmail.com
+Stability   : experimental
+Portability : POSIX
+
+This module provides functions to join elements of /string-like/ types by adding a comma between the elements, and an "and" (optionally with a comma) between the one-but-last and the last element.
+-}
+
+module Text.Comma (
+    comma_, and_, commaAnd_
+  , CommaStyle(OxfordComma, NoComma)
+  , lastJoin
+  , commaAs, commaEmptyAs, comma, noComma, commaEmpty, noCommaEmpty
+  , combineWith, combineWithEmpty
+  ) where
+
+import Data.Default.Class(Default(def))
+import Data.Foldable(toList)
+import Data.String(IsString(fromString))
+
+-- | The /string-like/ value for a comma, so @", "@.
+comma_
+  :: IsString s
+  => s  -- ^ A /string-like/ type.
+comma_ = fromString ", "
+
+-- | The /string-like/ value for an "and", so @" and "@.
+and_
+  :: IsString s
+  => s  -- ^ A /string-like/ type.
+and_ = fromString " and "
+
+-- | The /string-like/ value for a comma and an "and", so @", and "@.
+commaAnd_
+  :: IsString s
+  => s  -- ^ A /string-like/ type.
+commaAnd_ = fromString ", and "
+
+-- | The two different ways to join the last two items together: with or without a comma.
+data CommaStyle
+  = OxfordComma  -- ^ The /Oxford comma/ which uses a comma before the latest element, also known as /Harvard comma/ or /series comma/.
+  | NoComma  -- ^ The comma style where there is no comma before the "and" of the last item, informally known as the /Heathen comma/.
+  deriving (Bounded, Enum, Eq, Ord, Read, Show)
+
+instance Default CommaStyle where
+  def = OxfordComma
+
+-- | Specify the string that determines how to join the last but one and the last item based on the 'CommaStyle'.
+lastJoin
+  :: IsString s
+  => CommaStyle -- ^ The given comma style.
+  -> s  -- ^ A string that specifies how to join the last but one item and the last item based on the comma style.
+lastJoin OxfordComma = commaAnd_
+lastJoin _ = and_
+
+-- | Join the 'Foldable' of elements with a given item for a comma and for the last join with a custom value if the 'Foldable' is empty.
+combineWithEmpty
+  :: (Semigroup s, Foldable f)
+  => s  -- ^ The item used if the foldable item is empty.
+  -> s  -- ^ The /comma/ item placed between each item and the next, except for the last join.
+  -> s  -- ^ The item used to join the one but last item and the last item.
+  -> f s  -- ^ The 'Foldable' of items that should be joined.
+  -> s  -- ^ The item generated by joining the elements with the comma and last join item.
+combineWithEmpty e c0 c1 = _combine e c0 c1 . toList
+
+-- | Join the 'Foldable' of elements with a given item for a comma and for the last join.
+combineWith
+  :: (Monoid s, Foldable f)
+  => s  -- ^ The /comma/ item placed between each item and the next, except for the last join.
+  -> s  -- ^ The item used to join the one but last item and the last item.
+  -> f s  -- ^ The 'Foldable' of items that should be joined.
+  -> s  -- ^ The item generated by joining the elements with the comma and last join item.
+combineWith c0 c1 = _combine mempty c0 c1 . toList
+
+_combine :: Semigroup s => s -> s -> s -> [s] -> s
+_combine e _ _ [] = e
+_combine _ _ _ [s] = s
+_combine _ c0 c1 (x:x2:xs) = go xs x x2
+  where go [] s1 s2 = s1 <> c1 <> s2
+        go (s3:ss) s1 s2 = s1 <> c0 <> go ss s2 s3
+
+-- | Joins the sequence of items with the /Oxford comma/ style, uses the empty string if there are no items.
+comma :: (IsString s, Monoid s, Foldable f) => f s -> s
+comma = combineWith comma_ commaAnd_
+
+-- | Joins the sequence of items with the /no comma/ style, uses the empty string if there are no items.
+noComma :: (IsString s, Monoid s, Foldable f) => f s -> s
+noComma = combineWith comma_ and_
+
+-- | Join the sequence of items with the /Oxford comma/ style, uses a given "string" if there are no items.
+commaEmpty :: (IsString s, Semigroup s, Foldable f) => s -> f s -> s
+commaEmpty e = combineWithEmpty e comma_ commaAnd_
+
+-- | Join the sequence of items with the /no comma/ style, uses a given "string" if there are no items.
+noCommaEmpty :: (IsString s, Semigroup s, Foldable f) => s -> f s -> s
+noCommaEmpty e = combineWithEmpty e comma_ and_
+
+-- | Join the sequence of items with the given comma style, uses the empty string if there are no items.
+commaAs :: (IsString s, Monoid s, Foldable f) => CommaStyle -> f s -> s
+commaAs = combineWith comma_ . lastJoin
+
+-- | Join the sequence of items with the given comma style, uses a given "string" if there are no items.
+commaEmptyAs :: (IsString s, Semigroup s, Foldable f) => s -> CommaStyle -> f s -> s
+commaEmptyAs e = combineWithEmpty e comma_ . lastJoin
diff --git a/test/Main.hs b/test/Main.hs
new file mode 100644
--- /dev/null
+++ b/test/Main.hs
@@ -0,0 +1,3 @@
+{-# OPTIONS_GHC -F -pgmF hspec-discover #-}
+-- cabal v2-test --test-show-details=direct
+-- cabal v2-test --test-show-details=direct --test-options="--color --format=progress"
diff --git a/test/Text/CommaSpec.hs b/test/Text/CommaSpec.hs
new file mode 100644
--- /dev/null
+++ b/test/Text/CommaSpec.hs
@@ -0,0 +1,12 @@
+module Text.CommaSpec where
+
+import Test.Hspec(Spec, describe, it, shouldBe)
+import Text.Comma(comma)
+
+spec :: Spec
+spec = describe "comma" $ do
+    it "comma" $ do
+      comma [] `shouldBe` ""
+      comma ["red"] `shouldBe` "red"
+      comma ["red", "green"] `shouldBe` "red, and green"
+      comma ["red", "green", "blue"] `shouldBe` "red, green, and blue"
