diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,7 @@
+# Version 1.0
+
+* This library contains some of what was in `di-0.3`, conceptually at least.
+  Consider this first release of the new ecosystem a preview release: The API is
+  likely to stay stable, but extensive testing, formalization and tooling is
+  due.
+
diff --git a/LICENSE.txt b/LICENSE.txt
new file mode 100644
--- /dev/null
+++ b/LICENSE.txt
@@ -0,0 +1,30 @@
+Copyright (c) 2017-2018, Renzo Carbonara
+
+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 Renzo Carbonara 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,10 @@
+# di-handle
+
+Support for rendering to `Handle`s (e.g., files, stderr, stdout) with
+[di-core](https://hackage.haskell.org/package/di-core).
+
+[![Build Status](https://travis-ci.org/k0001/di.svg?branch=master)](https://travis-ci.org/k0001/di)
+
+See the [BSD3 LICENSE](https://github.com/k0001/di/blob/master/di-handle/LICENSE.txt)
+file to learn about the legal terms and conditions for this library.
+
diff --git a/Setup.hs b/Setup.hs
new file mode 100644
--- /dev/null
+++ b/Setup.hs
@@ -0,0 +1,4 @@
+#! /usr/bin/env nix-shell
+#! nix-shell ./shell.nix -i runghc
+import Distribution.Simple
+main = defaultMain
diff --git a/di-handle.cabal b/di-handle.cabal
new file mode 100644
--- /dev/null
+++ b/di-handle.cabal
@@ -0,0 +1,30 @@
+name: di-handle
+version: 1.0
+author: Renzo Carbonara
+maintainer: renλren.zone
+copyright: Renzo Carbonara 2017-2018
+license: BSD3
+license-file: LICENSE.txt
+extra-source-files: README.md CHANGELOG.md
+category: Logging
+build-type: Simple
+cabal-version: >=1.18
+synopsis: IO support for file handles in di-core
+description: IO support for file handles in di-core
+homepage: https://github.com/k0001/di
+bug-reports: https://github.com/k0001/di/issues
+
+library
+  hs-source-dirs: lib
+  default-language: Haskell2010
+  exposed-modules: Di.Handle
+  build-depends:
+    base >=4.9 && <5.0,
+    bytestring,
+    exceptions,
+    di-core
+  ghcjs-options: -Wall -O3
+  ghc-options: -Wall -O2
+  if !os(windows)
+     build-depends: unix
+
diff --git a/lib/Di/Handle.hs b/lib/Di/Handle.hs
new file mode 100644
--- /dev/null
+++ b/lib/Di/Handle.hs
@@ -0,0 +1,97 @@
+{-# LANGUAGE CPP #-}
+
+module Di.Handle
+ ( stderr
+ , handle
+ , blob
+ , LineRenderer(LineRendererUtf8)
+ , BlobRenderer(BlobRenderer)
+ ) where
+
+import qualified Control.Monad.Catch as Ex
+import Control.Monad.IO.Class (MonadIO(liftIO))
+import qualified Data.ByteString.Builder as BB
+import Data.Monoid ((<>))
+import qualified System.IO as IO
+
+#ifdef VERSION_unix
+import qualified System.Posix.Terminal
+import qualified System.Posix.IO
+#endif
+
+import Di.Core (Log)
+
+--------------------------------------------------------------------------------
+
+-- | How to render a 'Log' as a line of text.
+data LineRenderer level path msg
+  = LineRendererUtf8 !(Bool -> Log level path msg -> BB.Builder)
+  -- ^ The returned bytes must not contain a leading nor trailing newline.
+  --
+  -- The 'Bool' tells whether we are trying to write these bytes to a
+  -- terminal that supports ANSI colors.
+
+-- | How to render a 'Log' as a binary blob.
+data BlobRenderer level path msg
+  = BlobRenderer !(Log level path msg -> BB.Builder)
+
+--------------------------------------------------------------------------------
+
+-- | Like 'blob', but each 'Log' is rendered as text in its own line.
+--
+-- If the given 'IO.Handle' is associated to a TTY supporting ANSI colors, and
+-- the given 'LineRenderer' supports rendering with colors, and you ask for
+-- it, then you will get colorful output.
+handle
+  :: (MonadIO m, MonadIO n)
+  => Maybe Bool
+  -- ^ Whether to render with colors.
+  --
+  -- If 'Nothing', then we'll render with ANSI colors when the given
+  -- 'IO.Handle' is a TTY. You probably want to use 'Nothing' here most of the
+  -- time.
+  -> IO.Handle  -- ^ Handle where to write 'Log's.
+  -> LineRenderer level path msg -- ^ How to render each 'Log'.
+  -> m (Log level path msg -> n ())
+handle ywantColors h (LineRendererUtf8 render0) = liftIO $ do
+  wantColors <- maybe (isTty h) pure ywantColors
+  let !render1 = render0 wantColors
+      !newline = BB.char7 '\n'
+      render2 = \log' -> render1 log' <> newline
+  blob h (BlobRenderer render2)
+
+-- | Write 'Log's to a 'IO.Handle' as a binary blob.
+blob
+  :: (MonadIO m, MonadIO n)
+  => IO.Handle -- ^ Handle where to write 'Log's.
+  -> BlobRenderer level path msg -- ^ How to render each 'Log'.
+  -> m (Log level path msg -> n ())
+blob h (BlobRenderer render) = liftIO $ do
+  IO.hSetBinaryMode h True
+  pure $ \log_ ->
+     liftIO (Ex.finally (BB.hPutBuilder h (render log_))
+                        (IO.hFlush h))
+
+-- | 'Log's are written to 'IO.stderr', one per line.
+--
+-- /WARNING/ Currently this always renders as UTF-8.
+stderr
+  :: (MonadIO m, MonadIO n)
+  => LineRenderer level path msg -- ^ How to render each 'Log' line.
+  -> m (Log level path msg -> n ())
+stderr = handle Nothing IO.stderr
+
+--------------------------------------------------------------------------------
+
+isTty :: IO.Handle -> IO Bool
+#ifdef VERSION_unix
+isTty h
+  | h == IO.stderr = q System.Posix.IO.stdError
+  | h == IO.stdout = q System.Posix.IO.stdOutput
+  | h == IO.stdin  = q System.Posix.IO.stdInput
+  | otherwise = pure False  -- Is this good enough?
+  where q = System.Posix.Terminal.queryTerminal
+#else
+isTty _ = pure False
+#endif
+
