diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,20 @@
+Copyright (c) 2015 Sharif Olorin
+
+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,7 @@
+# quickcheck-text
+
+The usual Arbitrary instance for Text (in
+[quickcheck-instances](https://hackage.haskell.org/package/quickcheck-instances))
+only has single-byte instances and so isn't an ideal representation of
+a valid UTF-8 character. This package has generators for one-, two-
+and three-byte UTF-8 characters (all that are currently in use).
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/quickcheck-text.cabal b/quickcheck-text.cabal
new file mode 100644
--- /dev/null
+++ b/quickcheck-text.cabal
@@ -0,0 +1,47 @@
+name:                quickcheck-text
+version:             0.1.0.0
+synopsis:            Alternative arbitrary instance for Text
+description:         The usual Arbitrary instance for Text
+                     (in
+                     <https://hackage.haskell.org/package/quickcheck-instances quickcheck-instances>) 
+                     only has single-byte
+                     instances and so isn't an ideal representation of a valid UTF-8
+                     character. This package has generators for one-, two- and three-byte
+                     UTF-8 characters (all that are currently in use).
+license:             MIT
+license-file:        LICENSE
+author:              Sharif Olorin
+maintainer:          sio@tesser.org
+copyright:           2015 Sharif Olorin
+category:            Text
+build-type:          Simple
+cabal-version:       >=1.10
+extra-source-files:  README.md
+
+source-repository    head
+  type:              git
+  location:          git@github.com:olorin/quickcheck-instances.git
+
+library
+  exposed-modules:     Test.QuickCheck.Utf8
+                       Data.Text.Arbitrary
+  build-depends:       base >=4.7 && <5
+                     , QuickCheck
+                     , binary
+                     , bytestring
+                     , text
+  hs-source-dirs:      src
+  default-language:    Haskell2010
+  ghc-options:         -Wall
+
+test-suite properties
+  hs-source-dirs:      test
+  main-is:             properties.hs
+  type:                exitcode-stdio-1.0
+  default-language:    Haskell2010
+  ghc-options:         -Wall
+  build-depends:       base >=4.7 && <5
+                     , QuickCheck
+                     , bytestring
+                     , quickcheck-text
+                     , text
diff --git a/src/Data/Text/Arbitrary.hs b/src/Data/Text/Arbitrary.hs
new file mode 100644
--- /dev/null
+++ b/src/Data/Text/Arbitrary.hs
@@ -0,0 +1,11 @@
+module Data.Text.Arbitrary(
+  Text()
+) where
+
+import           Data.Text
+
+import           Test.QuickCheck
+import           Test.QuickCheck.Utf8
+
+instance Arbitrary Text where
+  arbitrary = genValidUtf8
diff --git a/src/Test/QuickCheck/Utf8.hs b/src/Test/QuickCheck/Utf8.hs
new file mode 100644
--- /dev/null
+++ b/src/Test/QuickCheck/Utf8.hs
@@ -0,0 +1,94 @@
+module Test.QuickCheck.Utf8(
+    genValidUtf8
+  , utf8BS
+  , genValidUtf81
+  , utf8BS1
+    -- * Generators for single characters
+  , oneByte
+  , twoByte
+  , threeByte
+) where
+
+import           Control.Monad
+
+import           Data.Binary.Builder
+import           Data.Binary.Get
+
+import           Data.ByteString     (ByteString)
+import qualified Data.ByteString     as BS
+import qualified Data.ByteString.Lazy     as BL
+import           Data.Char
+import           Data.Text           (Text)
+import qualified Data.Text           as T
+import           Data.Text.Encoding
+import           Data.Text.Internal.Encoding.Utf8
+import           Data.Word
+
+import           Test.QuickCheck
+
+-- |
+-- Generate a possibly-empty valid UTF-8 'Text' value.
+genValidUtf8 :: Gen Text
+genValidUtf8 = fmap decodeUtf8 utf8BS
+
+-- |
+-- Generate a possibly-empty sequence of bytes which represent a valid
+-- UTF-8 code point.
+utf8BS :: Gen ByteString
+utf8BS = fmap BS.concat $ elements symbolTypes  >>= listOf
+
+-- |
+-- Like 'genValidUtf8', but does not allow empty 'Text' values.
+genValidUtf81 :: Gen Text
+genValidUtf81 = fmap decodeUtf8 utf8BS1
+
+-- |
+-- Like 'utf8BS', but does not allow empty 'ByteString's.
+utf8BS1 :: Gen ByteString
+utf8BS1 = fmap BS.concat $ elements symbolTypes  >>= listOf1
+
+symbolTypes :: [Gen ByteString]
+symbolTypes = [ oneByte
+              , twoByte
+              , threeByte
+              ]
+
+inRange :: Int -> Int -> Gen Word8
+inRange lo hi = fmap fromIntegral $ elements [lo..hi]
+
+-- | Single-byte UTF-8 (i.e., a standard ASCII byte with a cleared MSB).
+oneByte :: Gen ByteString
+oneByte = fmap (BS.pack . return) $
+  inRange 0 127 -- 0bbbbbbb
+
+twoByte :: Gen ByteString
+twoByte = do
+  b1 <- inRange 0xC2 0xDF -- 110bbbbb
+  b2 <- nonInitial
+  return . buildUtf $ putBytes2 b1 b2
+
+threeByte :: Gen ByteString
+threeByte = do
+  (b1, b2) <- oneof [b3_1, b3_2, b3_3, b3_4]
+  b3 <- nonInitial
+  return . buildUtf $ putBytes3 b1 b2 b3
+ where
+  b3_1 = (,) `fmap` return 0xE0 `ap` inRange 0xA0 0xBF
+
+  b3_2 = (,) `fmap` inRange 0xE1 0xEC `ap` nonInitial
+
+  b3_3 = (,) `fmap` return 0xED `ap` inRange 0x80 0x9F
+
+  b3_4 = (,) `fmap` inRange 0xEE 0xEF `ap` nonInitial
+
+buildUtf :: Builder -> ByteString 
+buildUtf = BS.concat . BL.toChunks . toLazyByteString
+
+putBytes2 :: Word8 -> Word8 -> Builder
+putBytes2 b1 b2 =  putCharUtf8 $ chr2 b1 b2
+
+putBytes3 :: Word8 -> Word8 -> Word8 -> Builder
+putBytes3 b1 b2 b3 =  putCharUtf8 $ chr3 b1 b2 b3
+
+nonInitial :: Gen Word8
+nonInitial = inRange 0x80 0xBF
diff --git a/test/properties.hs b/test/properties.hs
new file mode 100644
--- /dev/null
+++ b/test/properties.hs
@@ -0,0 +1,49 @@
+{-# LANGUAGE TemplateHaskell #-}
+
+module Main where
+
+import           Control.Monad
+
+import           Data.Bits
+import qualified Data.ByteString      as BS
+import           Data.Text.Encoding
+
+import           System.Exit
+
+import           Test.QuickCheck
+import           Test.QuickCheck.Utf8
+
+prop_decodes_without_exception :: Property
+prop_decodes_without_exception = forAll utf8BS $ \bs ->
+  decodeUtf8 bs === decodeUtf8 bs
+
+prop_oneByte_lsb :: Property
+prop_oneByte_lsb = forAll oneByte $ \bs ->
+  let b = head $ BS.unpack bs
+  in testBit b 7 === False
+
+prop_oneByte_range :: Property
+prop_oneByte_range = forAll oneByte $ \bs ->
+  let s = sum $ fmap fromIntegral $ BS.unpack bs
+  in (s >= 0 && s <= 127)
+
+prop_twoByte_range :: Property
+prop_twoByte_range = forAll twoByte $ \bs ->
+  let s = sum $ fmap fromIntegral $ BS.unpack bs
+  in (s >= 320 && s <= 65439)
+
+prop_threeByte_range :: Property
+prop_threeByte_range = forAll threeByte $ \bs ->
+  let s = sum $ fmap fromIntegral $ BS.unpack bs
+  in (s >= 480 && s <= 16777071)
+
+prop_validUtf81 :: Property
+prop_validUtf81 = forAll utf8BS1 $ \bs ->
+  BS.length bs >= 1
+
+return []
+props :: IO Bool
+props = $quickCheckAll
+
+main :: IO ()
+main = props >>= flip when exitFailure . not
