diff --git a/ASCII/Isomorphism.hs b/ASCII/Isomorphism.hs
new file mode 100644
--- /dev/null
+++ b/ASCII/Isomorphism.hs
@@ -0,0 +1,28 @@
+module ASCII.Isomorphism ( CharIso (..), asChar, StringIso (..) ) where
+
+import ASCII.Char     ( Char )
+import ASCII.Superset ( CharSuperset (..), StringSuperset (..) )
+import Data.Function  ( id, (.) )
+import Data.List      ( map )
+
+class CharSuperset char => CharIso char
+  where
+    toChar :: char -> Char
+
+asChar :: CharIso char => (Char -> Char) -> char -> char
+asChar f = fromChar . f . toChar
+
+class StringSuperset string => StringIso string
+  where
+    toCharList :: string -> [Char]
+    mapChars :: (Char -> Char) -> string -> string
+
+-- | 'Char' is trivially isomorphic to itself.
+instance CharIso Char
+  where
+    toChar = id
+
+instance CharIso char => StringIso [char]
+  where
+    toCharList = map toChar
+    mapChars f = map (asChar f)
diff --git a/ASCII/Lift.hs b/ASCII/Lift.hs
new file mode 100644
--- /dev/null
+++ b/ASCII/Lift.hs
@@ -0,0 +1,47 @@
+{-# LANGUAGE FlexibleInstances, MultiParamTypeClasses #-}
+
+module ASCII.Lift ( Lift (..) ) where
+
+import ASCII.Char       ( Char )
+import ASCII.Refinement ( ASCII )
+import ASCII.Superset   ( CharSuperset, StringSuperset )
+
+import qualified ASCII.Refinement as R
+import qualified ASCII.Superset as S
+
+{- $setup
+
+>>> import ASCII.Char (Char (..))
+>>> import Data.Text (Text)
+>>> import Data.Word (Word8)
+
+-}
+
+class Lift ascii superset
+  where
+
+    {- | Converts from ASCII to any larger type.
+
+    >>> lift CapitalLetterA :: Word8
+    65
+
+    >>> lift [CapitalLetterH,SmallLetterI,ExclamationMark] :: Text
+    "Hi!"
+
+    Due to the highly polymorphic nature of the 'lift' function, often it must used with an explicit type signature or type application to avoid any type ambiguity.
+
+    -}
+
+    lift :: ascii -> superset
+
+-- | A value from an ASCII superset that has been refined by the 'ASCII' type constructor may be lifted back into the superset by unwrapping it from the 'ASCII' type.
+
+instance Lift (ASCII superset) superset where lift = R.lift
+
+-- | An ASCII 'Char' may be 'lift'ed into any larger character set (a 'CharSuperset').
+
+instance CharSuperset superset => Lift Char superset where lift = S.fromChar
+
+-- | An ASCII 'Char' list may be 'lift'ed into a string of any larger character set (a 'StringSuperset').
+
+instance StringSuperset superset => Lift [Char] superset where lift = S.fromCharList
diff --git a/ASCII/Refinement.hs b/ASCII/Refinement.hs
new file mode 100644
--- /dev/null
+++ b/ASCII/Refinement.hs
@@ -0,0 +1,161 @@
+module ASCII.Refinement
+  (
+    {- * ASCII type constructor -} ASCII, lift, asciiUnsafe,
+    {- * Character functions -} validateChar, fromChar, toChar, substituteChar, asChar,
+    {- * String functions -} validateString, fromCharList, toCharList, substituteString, mapChars
+  )
+  where
+
+import qualified ASCII.Char as ASCII
+import qualified ASCII.Superset as S
+import qualified ASCII.Isomorphism as I
+
+import ASCII.Superset    ( CharSuperset, StringSuperset )
+import Data.Bool         ( Bool (..) )
+import Data.Data         ( Data )
+import Data.Eq           ( Eq )
+import Data.Function     ( (.), ($), id )
+import Data.Hashable     ( Hashable )
+import Data.List         ( map )
+import Data.Maybe        ( Maybe (..) )
+import Data.Monoid       ( Monoid )
+import Data.Ord          ( Ord, (>) )
+import Data.Semigroup    ( Semigroup )
+import GHC.Generics      ( Generic )
+import Prelude           ( succ )
+import Text.Show         ( Show, showString, showsPrec, showParen, showList )
+
+{- $setup
+
+>>> :set -XOverloadedStrings
+>>> import ASCII.Char (Char (..))
+>>> import Data.List (map)
+>>> import Data.Int (Int)
+>>> import Data.String (String)
+>>> import Data.Text (Text)
+
+-}
+
+{- | This type constructor indicates that a value from some ASCII superset is valid ASCII. The type parameter is the ASCII superset, which should be a type with an instance of either 'CharSuperset' or 'StringSuperset'.
+
+For example, whereas a 'Data.Text.Text' value may contain a combination of ASCII and non-ASCII characters, a value of type @'ASCII' 'Data.Text.Text'@ may contain only ASCII characters.
+
+-}
+
+newtype ASCII superset = ASCII_Unsafe { lift :: superset }
+
+deriving stock instance Eq superset => Eq (ASCII superset)
+
+deriving stock instance Ord superset => Ord (ASCII superset)
+
+deriving newtype instance Hashable superset => Hashable (ASCII superset)
+
+deriving newtype instance Semigroup superset => Semigroup (ASCII superset)
+
+deriving newtype instance Monoid superset => Monoid (ASCII superset)
+
+deriving stock instance Data superset => Data (ASCII superset)
+
+deriving stock instance Generic (ASCII superset)
+
+instance Show superset => Show (ASCII superset)
+  where
+    showsPrec d x = showParen (d > app_prec) $
+        showString "asciiUnsafe " . showsPrec (succ app_prec) (lift x)
+      where app_prec = 10
+
+    showList x = showString "asciiUnsafe " . showList (map lift x)
+
+instance CharSuperset char => CharSuperset (ASCII char)
+  where
+    isAsciiChar _ = True
+    fromChar = asciiUnsafe . S.fromChar
+    toCharUnsafe = S.toCharUnsafe . lift
+
+instance CharSuperset char => I.CharIso (ASCII char)
+  where
+    toChar = S.toCharUnsafe
+
+instance StringSuperset string => StringSuperset (ASCII string)
+  where
+    isAsciiString _ = True
+    fromCharList = asciiUnsafe . S.fromCharList
+    toCharListUnsafe = S.toCharListUnsafe . lift
+    toCharListSub = S.toCharListUnsafe . lift
+    substituteString = id
+
+instance StringSuperset string => I.StringIso (ASCII string)
+  where
+    toCharList = S.toCharListUnsafe
+    mapChars = S.mapCharsUnsafe
+
+asciiUnsafe :: superset -> ASCII superset
+asciiUnsafe = ASCII_Unsafe
+
+{- |
+
+>>> map validateChar [-1, 65, 97, 128] :: [Maybe (ASCII Int)]
+[Nothing,Just (asciiUnsafe 65),Just (asciiUnsafe 97),Nothing]
+
+-}
+
+validateChar :: CharSuperset superset => superset -> Maybe (ASCII superset)
+validateChar x = if S.isAsciiChar x then Just (asciiUnsafe x) else Nothing
+
+substituteChar :: CharSuperset superset => superset -> ASCII superset
+substituteChar x = if S.isAsciiChar x then asciiUnsafe x else fromChar ASCII.Substitute
+
+fromChar :: CharSuperset superset => ASCII.Char -> ASCII superset
+fromChar = asciiUnsafe . S.fromChar
+
+toChar :: CharSuperset superset => ASCII superset -> ASCII.Char
+toChar = S.toCharUnsafe . lift
+
+{- |
+
+>>> fromCharList [CapitalLetterH,SmallLetterI,ExclamationMark] :: ASCII Text
+asciiUnsafe "Hi!"
+
+-}
+
+fromCharList :: StringSuperset superset => [ASCII.Char] -> ASCII superset
+fromCharList = asciiUnsafe . S.fromCharList
+
+{- |
+
+>>> toCharList (substituteString "Piñata" :: ASCII Text)
+[CapitalLetterP,SmallLetterI,Substitute,SmallLetterA,SmallLetterT,SmallLetterA]
+
+-}
+
+toCharList :: StringSuperset superset => ASCII superset -> [ASCII.Char]
+toCharList = S.toCharListUnsafe . lift
+
+{- | Forces a string from a larger character set into ASCII by using the 'ASCII.Substitute' character in place of any non-ASCII characters.
+
+>>> substituteString "Cristóbal" :: ASCII Text
+asciiUnsafe "Crist\SUBbal"
+
+-}
+
+substituteString :: StringSuperset superset => superset -> ASCII superset
+substituteString = asciiUnsafe . S.substituteString
+
+{- |
+
+>>> map validateString ["Hello", "Cristóbal"] :: [Maybe (ASCII Text)]
+[Just (asciiUnsafe "Hello"),Nothing]
+
+>>> map validateString ["Hello", "Cristóbal"] :: [Maybe (ASCII String)]
+[Just (asciiUnsafe "Hello"),Nothing]
+
+-}
+
+validateString :: StringSuperset superset => superset -> Maybe (ASCII superset)
+validateString x = if S.isAsciiString x then Just (asciiUnsafe x) else Nothing
+
+asChar :: CharSuperset superset => (ASCII.Char -> ASCII.Char) -> ASCII superset -> ASCII superset
+asChar f = asciiUnsafe . S.asCharUnsafe f . lift
+
+mapChars :: StringSuperset superset => (ASCII.Char -> ASCII.Char) -> ASCII superset -> ASCII superset
+mapChars f = asciiUnsafe . S.mapCharsUnsafe f . lift
diff --git a/ASCII/Superset.hs b/ASCII/Superset.hs
new file mode 100644
--- /dev/null
+++ b/ASCII/Superset.hs
@@ -0,0 +1,182 @@
+module ASCII.Superset (
+
+    {- * Characters -}
+    {- ** Class -} CharSuperset (..),
+    {- ** Functions -} asCharUnsafe, toCharMaybe, toCharOrFail, toCharSub, substituteChar,
+
+    {- * Strings -}
+    {- ** Class -} StringSuperset (..),
+    {- ** Functions -} toCharListMaybe, toCharListOrFail
+
+    ) where
+
+import Control.Monad      ( return )
+import Control.Monad.Fail ( MonadFail (fail) )
+import Data.Bool          ( Bool, (&&) )
+import Data.Function      ( (.), id )
+import Data.Ord           ( (<=), (>=) )
+import Data.Maybe         ( Maybe (..) )
+
+import qualified ASCII.Char               as  ASCII
+import qualified Data.Bool                as  Bool
+import qualified Data.ByteString          as  BS
+import qualified Data.ByteString.Lazy     as  LBS
+import qualified Data.ByteString.Builder  as  BSB
+import qualified Data.Char                as  Unicode
+import qualified Data.Int                 as  Int
+import qualified Data.List                as  List
+import qualified Data.Text                as  T
+import qualified Data.Text.Lazy           as  LT
+import qualified Data.Text.Lazy.Builder   as  TB
+import qualified Data.Word                as  Word
+import qualified Numeric.Natural          as  Nat
+import qualified Prelude
+
+
+---  Char  ---
+
+class CharSuperset char
+  where
+
+    isAsciiChar :: char -> Bool
+
+    fromChar :: ASCII.Char -> char
+
+    toCharUnsafe :: char -> ASCII.Char
+
+asCharUnsafe :: CharSuperset char => (ASCII.Char -> ASCII.Char) -> char -> char
+asCharUnsafe f = fromChar . f . toCharUnsafe
+
+toCharMaybe :: CharSuperset char => char -> Maybe ASCII.Char
+toCharMaybe = toCharOrFail
+
+toCharOrFail :: (CharSuperset char, MonadFail context) => char -> context ASCII.Char
+toCharOrFail x = if isAsciiChar x then return (toCharUnsafe x) else fail "Not an ASCII character"
+
+toCharSub :: CharSuperset char => char -> ASCII.Char
+toCharSub x = if isAsciiChar x then toCharUnsafe x else ASCII.Substitute
+
+substituteChar :: CharSuperset char => char -> char
+substituteChar x = if isAsciiChar x then x else fromChar ASCII.Substitute
+
+
+---  String  ---
+
+class StringSuperset string
+  where
+
+    isAsciiString :: string -> Bool
+
+    fromCharList :: [ASCII.Char] -> string
+
+    toCharListUnsafe :: string -> [ASCII.Char]
+
+    toCharListSub :: string -> [ASCII.Char]
+
+    substituteString :: string -> string
+
+    mapCharsUnsafe :: (ASCII.Char -> ASCII.Char) -> string -> string
+    mapCharsUnsafe f = fromCharList  . List.map f . toCharListUnsafe
+
+toCharListMaybe :: StringSuperset string => string -> Maybe [ASCII.Char]
+toCharListMaybe = toCharListOrFail
+
+toCharListOrFail :: (StringSuperset string, MonadFail context) => string -> context [ASCII.Char]
+toCharListOrFail x = if isAsciiString x then return (toCharListUnsafe x) else fail "String contains non-ASCII characters"
+
+
+---  Instances  ---
+
+-- | 'ASCII.Char' is trivially a superset of itself.
+
+instance CharSuperset ASCII.Char
+  where
+    isAsciiChar _ = Bool.True
+    fromChar = id
+    toCharUnsafe = id
+
+instance CharSuperset Unicode.Char
+  where
+    isAsciiChar = (<= '\DEL')
+    fromChar = Unicode.chr . ASCII.toInt
+    toCharUnsafe = ASCII.fromIntUnsafe . Unicode.ord
+
+instance CharSuperset Nat.Natural
+  where
+    isAsciiChar = (<= 127)
+    fromChar = Prelude.fromIntegral . ASCII.toInt
+    toCharUnsafe = ASCII.fromIntUnsafe . Prelude.fromIntegral
+
+instance CharSuperset Int.Int
+  where
+    isAsciiChar x = (x >= 0) && (x <= 127)
+    fromChar = ASCII.toInt
+    toCharUnsafe = ASCII.fromIntUnsafe
+
+instance CharSuperset Word.Word8
+  where
+    isAsciiChar = (<= 127)
+    fromChar = Prelude.fromIntegral . ASCII.toInt
+    toCharUnsafe = ASCII.fromIntUnsafe . Prelude.fromIntegral
+
+instance CharSuperset char => StringSuperset [char]
+  where
+    isAsciiString = List.all isAsciiChar
+    fromCharList = List.map fromChar
+    toCharListUnsafe = List.map toCharUnsafe
+    toCharListSub = List.map toCharSub
+    substituteString = List.map substituteChar
+
+instance StringSuperset T.Text
+  where
+    isAsciiString = T.all isAsciiChar
+    fromCharList = T.pack . fromCharList
+    toCharListUnsafe = toCharListUnsafe . T.unpack
+    toCharListSub = toCharListSub . T.unpack
+    substituteString = T.map substituteChar
+    mapCharsUnsafe f = T.map (asCharUnsafe f)
+
+instance StringSuperset LT.Text
+  where
+    isAsciiString = LT.all isAsciiChar
+    fromCharList = LT.pack . fromCharList
+    toCharListUnsafe = toCharListUnsafe . LT.unpack
+    toCharListSub = toCharListSub . LT.unpack
+    substituteString = LT.map substituteChar
+    mapCharsUnsafe f = LT.map (asCharUnsafe f)
+
+instance StringSuperset TB.Builder
+  where
+    isAsciiString = isAsciiString . TB.toLazyText
+    fromCharList = TB.fromString . fromCharList
+    toCharListUnsafe = toCharListUnsafe . TB.toLazyText
+    toCharListSub = toCharListSub . TB.toLazyText
+    substituteString = TB.fromLazyText . substituteString . TB.toLazyText
+    mapCharsUnsafe f = TB.fromLazyText . mapCharsUnsafe f . TB.toLazyText
+
+instance StringSuperset BS.ByteString
+  where
+    isAsciiString = BS.all isAsciiChar
+    fromCharList = BS.pack . fromCharList
+    toCharListUnsafe = toCharListUnsafe . BS.unpack
+    toCharListSub = toCharListSub . BS.unpack
+    substituteString = BS.map substituteChar
+    mapCharsUnsafe f = BS.map (asCharUnsafe f)
+
+instance StringSuperset LBS.ByteString
+  where
+    isAsciiString = LBS.all isAsciiChar
+    fromCharList = LBS.pack . fromCharList
+    toCharListUnsafe = toCharListUnsafe . LBS.unpack
+    toCharListSub = toCharListSub . LBS.unpack
+    substituteString = LBS.map substituteChar
+    mapCharsUnsafe f = LBS.map (asCharUnsafe f)
+
+instance StringSuperset BSB.Builder
+  where
+    isAsciiString = isAsciiString . BSB.toLazyByteString
+    fromCharList = BSB.lazyByteString . fromCharList
+    toCharListUnsafe = toCharListUnsafe . BSB.toLazyByteString
+    toCharListSub = toCharListSub . BSB.toLazyByteString
+    substituteString = BSB.lazyByteString . substituteString . BSB.toLazyByteString
+    mapCharsUnsafe f = BSB.lazyByteString . mapCharsUnsafe f . BSB.toLazyByteString
diff --git a/ascii-superset.cabal b/ascii-superset.cabal
new file mode 100644
--- /dev/null
+++ b/ascii-superset.cabal
@@ -0,0 +1,46 @@
+cabal-version: 2.0
+
+name: ascii-superset
+version: 1.0.0.0
+synopsis: Representing ASCII with refined supersets
+category: Data, Text
+
+description: This package defines classes which describe what subset of a type is valid as ASCII, as well as a type constructor representing a value of a superset that is known to be valid ASCII.
+
+license: Apache-2.0
+license-file: license.txt
+
+author: Chris Martin
+maintainer: Chris Martin, Julie Moronuki
+
+homepage:    https://github.com/typeclasses/ascii
+bug-Reports: https://github.com/typeclasses/ascii/issues
+
+build-type: Simple
+
+tested-with: GHC == 8.8.1, GHC == 8.6.5, GHC == 8.4.3
+
+source-repository head
+    type:     git
+    location: git://github.com/typeclasses/ascii.git
+
+library
+    default-language: Haskell2010
+    default-extensions: NoImplicitPrelude
+    default-extensions: StandaloneDeriving
+    default-extensions: DerivingStrategies
+    default-extensions: GeneralizedNewtypeDeriving
+    default-extensions: DeriveDataTypeable
+    default-extensions: DeriveGeneric
+    ghc-options: -Wall
+
+    build-depends: base       >= 4.11 && < 4.14
+    build-depends: bytestring >= 0.10 && < 0.11
+    build-depends: ascii-char >= 1.0  && < 1.1
+    build-depends: hashable   >= 1.2  && < 1.4
+    build-depends: text       >= 1.2  && < 1.3
+
+    exposed-modules: ASCII.Superset
+    exposed-modules: ASCII.Isomorphism
+    exposed-modules: ASCII.Refinement
+    exposed-modules: ASCII.Lift
diff --git a/license.txt b/license.txt
new file mode 100644
--- /dev/null
+++ b/license.txt
@@ -0,0 +1,13 @@
+Copyright 2020 Typeclass Consulting LLC
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
