diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,10 @@
 # Revision history for byteslice
 
+## 0.2.9.0 -- 2022-12-08
+
+* Add `toShortText`, `toShortTextU`, and `toText` to `Data.Byte.Text.Ascii`.
+* Add `fromShortText` and `fromText` to `Data.Bytes.Text.Utf8`.
+
 ## 0.2.8.0 -- 2022-11-21
 
 * Add `Data.Bytes.replace` and `Data.Bytes.findIndices`
diff --git a/byteslice.cabal b/byteslice.cabal
--- a/byteslice.cabal
+++ b/byteslice.cabal
@@ -1,6 +1,6 @@
 cabal-version: 2.4
 name: byteslice
-version: 0.2.8.0
+version: 0.2.9.0
 synopsis: Slicing managed and unmanaged memory
 description:
   This library provides types that allow the user to talk about a slice of
@@ -43,10 +43,11 @@
   build-depends:
     , base >=4.14 && <5
     , bytestring >=0.10.8 && <0.12
-    , primitive >=0.7 && <0.8
+    , primitive >=0.7.4 && <0.8
     , primitive-unlifted >=0.1.2 && <0.2
     , primitive-addr >=0.1 && <0.2
     , run-st >=0.1.1 && <0.2
+    , text >=1.2.5
     , text-short >=0.1.3 && <0.2
     , tuples >=0.1 && <0.2
     , vector >=0.12 && <0.14
diff --git a/src/Data/Bytes.hs b/src/Data/Bytes.hs
--- a/src/Data/Bytes.hs
+++ b/src/Data/Bytes.hs
@@ -167,7 +167,7 @@
 import Cstrlen (cstringLength#)
 import Data.ByteString.Short.Internal (ShortByteString(SBS))
 import Data.Bytes.Pure (length,fromByteArray,foldr,unsafeDrop)
-import Data.Bytes.Pure (unsafeIndex)
+import Data.Bytes.Pure (unsafeIndex,toShortByteString)
 import Data.Bytes.Types (Bytes(Bytes,array,offset))
 import Data.Primitive (Array,ByteArray(ByteArray))
 import Data.Text.Short (ShortText)
@@ -615,14 +615,6 @@
 all :: (Word8 -> Bool) -> Bytes -> Bool
 {-# inline all #-}
 all f = foldr (\b r -> f b && r) True
-
--- | Convert the sliced 'Bytes' to an unsliced 'ShortByteString'. This
--- reuses the array backing the sliced 'Bytes' if the slicing metadata
--- implies that all of the bytes are used. Otherwise, it makes a copy.
-toShortByteString :: Bytes -> ShortByteString
-{-# inline toShortByteString #-}
-toShortByteString !b = case Pure.toByteArray b of
-  PM.ByteArray x -> SBS x
 
 -- | Variant of 'toShortByteString' that unconditionally makes a copy of
 -- the array backing the sliced 'Bytes' even if the original array
diff --git a/src/Data/Bytes/Pure.hs b/src/Data/Bytes/Pure.hs
--- a/src/Data/Bytes/Pure.hs
+++ b/src/Data/Bytes/Pure.hs
@@ -38,6 +38,7 @@
   , map
   , mapU
   , null
+  , toShortByteString
   ) where
 
 import Prelude hiding (length,foldl,foldr,map,null)
@@ -47,6 +48,7 @@
 import Data.Bits (xor)
 import Data.Bytes.Types (Bytes(Bytes))
 import Data.ByteString (ByteString)
+import Data.ByteString.Short.Internal (ShortByteString(SBS))
 import Data.Primitive (ByteArray(ByteArray),MutableByteArray,PrimArray(PrimArray))
 import Data.Word (Word64,Word32,Word8)
 import Foreign.Ptr (Ptr,plusPtr)
@@ -313,3 +315,12 @@
 {-# inline unsafeHead #-}
 unsafeHead :: Bytes -> Word8
 unsafeHead bs = unsafeIndex bs 0
+
+-- | Convert the sliced 'Bytes' to an unsliced 'ShortByteString'. This
+-- reuses the array backing the sliced 'Bytes' if the slicing metadata
+-- implies that all of the bytes are used. Otherwise, it makes a copy.
+toShortByteString :: Bytes -> ShortByteString
+{-# inline toShortByteString #-}
+toShortByteString !b = case toByteArray b of
+  PM.ByteArray x -> SBS x
+
diff --git a/src/Data/Bytes/Text/Ascii.hs b/src/Data/Bytes/Text/Ascii.hs
--- a/src/Data/Bytes/Text/Ascii.hs
+++ b/src/Data/Bytes/Text/Ascii.hs
@@ -1,3 +1,5 @@
+{-# LANGUAGE BangPatterns #-}
+{-# LANGUAGE CPP #-}
 {-# LANGUAGE TypeApplications #-}
 
 -- | This module treats 'Bytes' data as holding ASCII text. Providing bytes
@@ -9,17 +11,29 @@
 module Data.Bytes.Text.Ascii
   ( fromString
   , decodeDecWord
+  , toShortText
+  , toShortTextU
+#if MIN_VERSION_text(2,0,0)
+  , toText
+#endif
   ) where
 
-import Data.Bytes.Types (Bytes)
+import Data.ByteString.Short.Internal (ShortByteString(SBS))
+import Data.Bytes.Text.Latin1 (decodeDecWord)
+import Data.Bytes.Types (Bytes(Bytes))
 import Data.Char (ord)
+import Data.Primitive (ByteArray)
+import Data.Text (Text)
+import Data.Text.Short (ShortText)
 import Data.Word (Word8)
-import Data.Bytes.Text.Latin1 (decodeDecWord)
 
 import qualified Data.Bytes.Pure as Bytes
+import qualified Data.Primitive as PM
+import qualified Data.Text.Array as A
+import qualified Data.Text.Internal as I
+import qualified Data.Text.Short.Unsafe as TS
 import qualified GHC.Exts as Exts
 
-
 -- | Convert a 'String' consisting of only characters in the ASCII block
 -- to a byte sequence. Any character with a codepoint above @U+007F@ is
 -- replaced by @U+0000@.
@@ -29,3 +43,27 @@
   . map (\c -> let i = ord c in if i < 128 then fromIntegral @Int @Word8 i else 0)
 
 -- TODO presumably also fromText and fromShortText
+
+toShortText :: Bytes -> Maybe ShortText
+{-# inline toShortText #-}
+toShortText !b = case Bytes.foldr (\w acc -> w < 128 && acc) True b of
+  True -> Just (TS.fromShortByteStringUnsafe (Bytes.toShortByteString b))
+  False -> Nothing
+
+toShortTextU :: ByteArray -> Maybe ShortText
+{-# inline toShortTextU #-}
+toShortTextU !b = case Bytes.foldr (\w acc -> w < 128 && acc) True (Bytes.fromByteArray b) of
+  True -> Just (TS.fromShortByteStringUnsafe (case b of {PM.ByteArray x -> SBS x}))
+  False -> Nothing
+
+#if MIN_VERSION_text(2,0,0)
+-- | Interpret byte sequence as ASCII codepoints.
+-- Only available when building with @text-2.0@ and newer.
+-- Returns 'Nothing' if any of the bytes are outside of the
+-- range @0x00-0x7F@
+toText :: Bytes -> Maybe Text
+{-# inline toText #-}
+toText !b@(Bytes (PM.ByteArray arr) off len) = case Bytes.foldr (\w acc -> w < 128 && acc) True b of
+  True -> Just (I.Text (A.ByteArray arr) off len)
+  False -> Nothing
+#endif
diff --git a/src/Data/Bytes/Text/Utf8.hs b/src/Data/Bytes/Text/Utf8.hs
--- a/src/Data/Bytes/Text/Utf8.hs
+++ b/src/Data/Bytes/Text/Utf8.hs
@@ -1,3 +1,42 @@
--- | Placeholder module in case there is demand for treating 'Bytes' as
--- UTF8-encoded text
-module Data.Bytes.Text.Utf8 () where
+{-# language CPP #-}
+{-# language BangPatterns #-}
+
+-- | Convert 'Bytes' to and from 'Text' and 'ShortText'.
+module Data.Bytes.Text.Utf8
+  ( fromShortText
+  , toShortText
+#if MIN_VERSION_text(2,0,0)
+  , fromText
+#endif
+  ) where
+
+import Data.Bytes.Types (Bytes(Bytes))
+import Data.Text.Short (ShortText)
+import Data.Text (Text)
+import Data.Primitive (ByteArray(ByteArray))
+
+import qualified Data.Text.Short as TS
+import qualified Data.Bytes as Bytes
+import qualified Data.Text.Internal as I
+import qualified Data.Text.Array as A
+
+-- | Encode 'ShortText' using UTF-8. Since 'ShortText' is backed by a UTF-8
+-- byte sequence, this does not perform a copy.
+fromShortText :: ShortText -> Bytes
+{-# inline fromShortText #-}
+fromShortText = Bytes.fromShortByteString . TS.toShortByteString
+
+-- | Attempt to interpret the byte sequence as UTF-8 encoded text. Returns
+-- 'Nothing' if the bytes are not UTF-8 encoded text.
+toShortText :: Bytes -> Maybe ShortText
+{-# inline toShortText #-}
+toShortText !b = TS.fromShortByteString (Bytes.toShortByteString b)
+
+#if MIN_VERSION_text(2,0,0)
+-- | Encode 'Text' using 'UTF-8'. Only available when building with
+-- @text-2.0@ and newer. Since 'Text' is backed by a UTF-8
+-- byte sequence, this does not perform a copy.
+fromText :: Text -> Bytes
+{-# inline fromText #-}
+fromText (I.Text (A.ByteArray b) off len) = Bytes (ByteArray b) off len
+#endif
