diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,3 @@
+## 0.1
+
+* Initial release.
diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,29 @@
+Copyright (c) 2026, Bodigrim
+
+
+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 the copyright holder nor the names of its
+      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
+HOLDER 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,21 @@
+# from-text [![Hackage](http://img.shields.io/hackage/v/from-text.svg)](https://hackage.haskell.org/package/from-text) [![Stackage LTS](http://stackage.org/package/from-text/badge/lts)](http://stackage.org/lts/package/from-text) [![Stackage Nightly](http://stackage.org/package/from-text/badge/nightly)](http://stackage.org/nightly/package/from-text)
+
+This package provides
+
+```haskell
+class IsText a where
+  fromText :: Text -> a
+```
+
+aiming to simplify conversion from `Text` to other textual data types, including `ByteArray`, `ByteString` and `OsPath`. When converting to binary types without an associated encoding, we use UTF-8.
+
+There is an overwhelming number of alternative packages for text conversions. To name a few in no particular order:
+* [`text-conversions`](https://hackage.haskell.org/package/text-conversions-0.3.1.1/docs/Data-Text-Conversions.html)
+* [`string-conv`](https://hackage.haskell.org/package/string-conv-0.2.0/docs/Data-String-Conv.html)
+* [`ttc`](https://hackage-content.haskell.org/package/ttc-1.5.0.1/docs/Data-TTC.html)
+* [`text-convert`](https://hackage-content.haskell.org/package/text-convert-0.1.0.1/docs/Text-Convert.html)
+* [`lawful-conversions`](https://hackage-content.haskell.org/package/lawful-conversions-0.4.0/docs/LawfulConversions.html)
+* [`witch`](https://hackage-content.haskell.org/package/witch-1.3.1.0/docs/Witch-Instances.html)
+* [`unwitch`](https://hackage-content.haskell.org/package/unwitch-3.0.0/docs/Unwitch-Convert-Text.html)
+
+At the moment none of them provide conversions from `Text` to `OsPath`. I could not decide which one to contribute such function to, so decided to create a new package.
diff --git a/from-text.cabal b/from-text.cabal
new file mode 100644
--- /dev/null
+++ b/from-text.cabal
@@ -0,0 +1,57 @@
+cabal-version:   3.0
+name:            from-text
+version:         0.1
+license:         BSD-3-Clause
+license-file:    LICENSE
+maintainer:      andrew.lelechenko@gmail.com
+author:          Bodigrim
+tested-with:
+    ghc ==9.14.1 ghc ==9.12.2 ghc ==9.10.3 ghc ==9.8.4 ghc ==9.6.7
+    ghc ==9.4.8 ghc ==9.2.8 ghc ==9.0.2 ghc ==8.10.7 ghc ==8.8.4
+    ghc ==8.6.5
+
+synopsis:        Type class to convert from Text
+description:
+    This package provides @class IsText a where fromText :: Text -> a@,
+    aiming to simplify conversion from @Text@ to other textual data types,
+    including @ByteArray@, @ByteString@ and @OsPath@.
+
+category:        Text
+build-type:      Simple
+extra-doc-files:
+    CHANGELOG.md
+    README.md
+
+source-repository head
+    type:     git
+    location: https://github.com/Bodigrim/from-text.git
+
+library
+    exposed-modules:  Data.Text.From
+    hs-source-dirs:   src
+    default-language: Haskell2010
+    ghc-options:      -Wall
+    build-depends:
+        base >=4.12 && <5,
+        bytestring >=0.12 && <0.13,
+        filepath >=1.5 && <1.6,
+        os-string >=2.0.2 && <2.1,
+        text >=2.1.1 && <2.2
+
+    if impl(ghc <9.4)
+        build-depends: data-array-byte >=0.1 && <0.2
+
+test-suite from-text-test
+    type:             exitcode-stdio-1.0
+    main-is:          Main.hs
+    hs-source-dirs:   test
+    default-language: Haskell2010
+    ghc-options:      -Wall
+    build-depends:
+        base,
+        bytestring,
+        from-text,
+        os-string,
+        tasty,
+        tasty-quickcheck,
+        text
diff --git a/src/Data/Text/From.hs b/src/Data/Text/From.hs
new file mode 100644
--- /dev/null
+++ b/src/Data/Text/From.hs
@@ -0,0 +1,130 @@
+{-# LANGUAGE BangPatterns #-}
+{-# LANGUAGE FlexibleInstances #-}
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE MagicHash #-}
+{-# LANGUAGE ScopedTypeVariables #-}
+{-# LANGUAGE TypeApplications #-}
+{-# LANGUAGE TypeOperators #-}
+{-# LANGUAGE UnboxedTuples #-}
+
+-- | Convert strict 'T.Text' to other textual types,
+-- including t'ByteArray', 'B.ByteString' and 'System.OsPath.OsPath'.
+module Data.Text.From (
+  IsText (..),
+) where
+
+import Data.Array.Byte (ByteArray (..))
+import Data.Bits (shiftR, (.&.))
+import qualified Data.ByteString as B
+import qualified Data.ByteString.Builder as BB
+import qualified Data.ByteString.Lazy as BL
+import qualified Data.ByteString.Short as BS
+import Data.Char (ord)
+import Data.Coerce (coerce)
+import Data.Functor.Const (Const (..))
+import Data.Functor.Identity (Identity (..))
+import qualified Data.Text as T
+import qualified Data.Text.Array as TA
+import qualified Data.Text.Encoding as TE
+import qualified Data.Text.Internal as T
+import qualified Data.Text.Lazy as TL
+import qualified Data.Text.Lazy.Builder as TLB
+import qualified Data.Text.Unsafe as TU
+import GHC.Exts (sizeofByteArray#, writeWord16Array#)
+import GHC.Int (Int (..))
+import GHC.ST (ST (..), runST)
+import GHC.Word (Word16 (..))
+import qualified System.OsString as OS
+import qualified System.OsString.Data.ByteString.Short.Internal as OSI
+import qualified System.OsString.Internal.Types as OSIT
+import qualified System.OsString.Posix as OSP
+
+-- | Convert strict 'T.Text' to other textual types.
+--
+-- This is modeled after 'Data.String.IsString'
+-- with the aim to avoid dealing with 'String' ever.
+class IsText a where
+  fromText :: T.Text -> a
+
+instance a ~ Char => IsText [a] where
+  fromText = T.unpack
+
+instance IsText a => IsText (Identity a) where
+  fromText = coerce (fromText @a)
+
+instance IsText a => IsText (Const a b) where
+  fromText = coerce (fromText @a)
+
+-- | Encodes as UTF-8.
+instance IsText ByteArray where
+  fromText (T.Text arr@(ByteArray ba) 0 len)
+    | I# (sizeofByteArray# ba) == len = arr
+  fromText (T.Text arr off len) = TA.run $ do
+    marr <- TA.new len
+    TA.copyI len marr 0 arr off
+    pure marr
+
+-- | Encodes as UTF-8.
+instance IsText B.StrictByteString where
+  fromText = TE.encodeUtf8
+
+-- | Encodes as UTF-8.
+instance IsText BL.LazyByteString where
+  fromText = BL.fromStrict . TE.encodeUtf8
+
+-- | Encodes as UTF-8.
+instance IsText BB.Builder where
+  fromText = TE.encodeUtf8Builder
+
+-- | Encodes as UTF-8.
+instance IsText BS.ShortByteString where
+  fromText = coerce (fromText @ByteArray)
+
+instance IsText T.StrictText where
+  fromText = id
+
+instance IsText TL.LazyText where
+  fromText = TL.fromStrict
+
+instance IsText TLB.Builder where
+  fromText = TLB.fromText
+
+-- | Encodes as UTF-8.
+instance IsText OSP.PosixString where
+  fromText = coerce (fromText @ByteArray)
+
+-- | Encodes as UTF-16 LE.
+instance IsText OSIT.WindowsString where
+  fromText (T.Text src off len) = runST $ do
+    marr <- TA.new (len * 2)
+    let go !srcOff !dstOff
+          | srcOff >= len + off = do
+              TA.shrinkM marr (dstOff * 2)
+              arr <- TA.unsafeFreeze marr
+              pure $ coerce arr
+          | otherwise = do
+              let !(TU.Iter c d) = TU.iterArray src srcOff
+                  n = ord c
+              d' <-
+                if n <= 0xFFFF
+                  then do
+                    writeWord16LE marr dstOff (fromIntegral n)
+                    pure 1
+                  else do
+                    let n1 = n - 0x10000
+                    writeWord16LE marr dstOff (fromIntegral $ n1 `shiftR` 10 + 0xD800)
+                    writeWord16LE marr (dstOff + 1) (fromIntegral $ n1 .&. 0x3FF + 0xDC00)
+                    pure 2
+              go (srcOff + d) (dstOff + d')
+    go off 0
+    where
+      writeWord16LE :: TA.MArray s -> Int -> Word16 -> ST s ()
+      writeWord16LE (TA.MutableByteArray marr) (I# offset) (W16# w16) = ST $ \s ->
+        case writeWord16Array# marr offset (OSI.word16ToLE# w16) s of
+          s' -> (# s', () #)
+
+-- | Also known as 'System.OsPath.OsPath'.
+instance IsText OS.OsString where
+  fromText = case OS.coercionToPlatformTypes of
+    Left {} -> coerce (fromText @OSIT.WindowsString)
+    Right {} -> coerce (fromText @OSP.PosixString)
diff --git a/test/Main.hs b/test/Main.hs
new file mode 100644
--- /dev/null
+++ b/test/Main.hs
@@ -0,0 +1,59 @@
+{-# OPTIONS_GHC -Wno-orphans #-}
+
+module Main (main) where
+
+import qualified Data.ByteString.Builder as BB
+import qualified Data.ByteString.Lazy as BL
+import qualified Data.ByteString.Short as BS
+import Data.Functor.Const (Const (..))
+import Data.Functor.Identity (Identity (..))
+import qualified Data.Text as T
+import qualified Data.Text.Encoding as TE
+import Data.Text.From
+import qualified Data.Text.Lazy as TL
+import qualified Data.Text.Lazy.Builder as TLB
+import qualified System.OsString as OS
+import qualified System.OsString.Posix as OSP
+import qualified System.OsString.Windows as OSW
+import Test.Tasty (defaultMain, testGroup)
+import Test.Tasty.QuickCheck (Arbitrary (..), choose, getUnicodeString, testProperty, (===))
+
+main :: IO ()
+main =
+  defaultMain $
+    testGroup
+      "IsText"
+      [ testProperty "String" $
+          \xs -> T.pack (fromText xs) === xs
+      , testProperty "Identity" $
+          \xs -> fromText xs === Identity (fromText xs :: String)
+      , testProperty "Const" $
+          \xs -> (fromText xs :: Const String ()) === Const (fromText xs)
+      , testProperty "ByteArray" $
+          \xs -> TE.decodeUtf8 (BS.fromShort (BS.ShortByteString (fromText xs))) === xs
+      , testProperty "StrictByteString" $
+          \xs -> TE.decodeUtf8 (fromText xs) === xs
+      , testProperty "LazyByteString" $
+          \xs -> TE.decodeUtf8 (BL.toStrict (fromText xs)) === xs
+      , testProperty "Builder (ByteString)" $
+          \xs -> TE.decodeUtf8 (BL.toStrict (BB.toLazyByteString (fromText xs))) === xs
+      , testProperty "StrictText" $
+          \xs -> fromText xs === xs
+      , testProperty "LazyText" $
+          \xs -> TL.toStrict (fromText xs) === xs
+      , testProperty "Builder (Text)" $
+          \xs -> TL.toStrict (TLB.toLazyText (fromText xs)) === xs
+      , testProperty "PosixString" $
+          \xs -> OSP.decodeUtf (fromText xs) === Just (T.unpack xs)
+      , testProperty "WindowsString" $
+          \xs -> OSW.decodeUtf (fromText xs) === Just (T.unpack xs)
+      , testProperty "OsString" $
+          \xs -> OS.decodeUtf (fromText xs) === Just (T.unpack xs)
+      ]
+
+instance Arbitrary T.Text where
+  arbitrary = do
+    t <- T.pack . getUnicodeString <$> arbitrary
+    t' <- (`T.drop` t) <$> choose (0, T.length t `quot` 2)
+    (`T.take` t) <$> choose (0, T.length t' `quot` 2)
+  shrink = map T.pack . shrink . T.unpack
