diff --git a/ChangeLog.md b/ChangeLog.md
new file mode 100644
--- /dev/null
+++ b/ChangeLog.md
@@ -0,0 +1,3 @@
+# Changelog for recursion-zipper
+
+## Unreleased changes
diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,30 @@
+Copyright Author name here (c) 2021
+
+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 Author name here 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,6 @@
+# recursive-zipper
+
+A zipper-type for self-recursive types like ASTs or JSON.
+
+Provide efficient access to a leaf node of such a structure, and a mechanism for
+navigating around, folding, etc.
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/recursive-zipper.cabal b/recursive-zipper.cabal
new file mode 100644
--- /dev/null
+++ b/recursive-zipper.cabal
@@ -0,0 +1,45 @@
+cabal-version: 1.12
+
+-- This file has been generated from package.yaml by hpack version 0.34.4.
+--
+-- see: https://github.com/sol/hpack
+
+name:           recursive-zipper
+version:        0.0.0.0
+synopsis:       Zippers over recursive data structures.
+description:    Please see the README on GitHub at <https://github.com/ChrisPenner/recursive-zipper#readme>
+category:       Data
+homepage:       https://github.com/ChrisPenner/recursive-zipper#readme
+bug-reports:    https://github.com/ChrisPenner/recursive-zipper/issues
+author:         Chris Penner
+maintainer:     example@example.com
+copyright:      2021 Chris Penner
+license:        BSD3
+license-file:   LICENSE
+build-type:     Simple
+extra-source-files:
+    README.md
+    ChangeLog.md
+
+source-repository head
+  type: git
+  location: https://github.com/ChrisPenner/recursive-zipper
+
+library
+  exposed-modules:
+      Zipper.Recursive
+      Zipper.Recursive.Internal
+  other-modules:
+      Paths_recursive_zipper
+  hs-source-dirs:
+      src
+  build-depends:
+      base >=4.7 && <5
+    , comonad
+    , containers
+    , free
+    , lens
+    , mtl
+    , recursion-schemes
+    , transformers
+  default-language: Haskell2010
diff --git a/src/Zipper/Recursive.hs b/src/Zipper/Recursive.hs
new file mode 100644
--- /dev/null
+++ b/src/Zipper/Recursive.hs
@@ -0,0 +1,36 @@
+module Zipper.Recursive
+  ( -- * Core type
+    Z.Zipper,
+    Z.Idx,
+
+    -- * Constructing Zippers
+    Z.zipper,
+    Z.fromRecursive,
+    Z.tagged,
+
+    -- * Movement
+    Z.down,
+    Z.up,
+    Z.sibling,
+    Z.tug,
+
+    -- * Folding and flattening
+    Z.rezip,
+    Z.flatten,
+    Z.fold,
+
+    -- * Getters
+    Z.focus,
+    Z.branches,
+    Z.currentIndex,
+
+    -- * Optics
+    Z.focus_,
+    Z.unwrapped_,
+    Z.branches_,
+    Z.children_,
+    Z.ichildren_,
+  )
+  where
+
+import Zipper.Recursive.Internal as Z
diff --git a/src/Zipper/Recursive/Internal.hs b/src/Zipper/Recursive/Internal.hs
new file mode 100644
--- /dev/null
+++ b/src/Zipper/Recursive/Internal.hs
@@ -0,0 +1,168 @@
+{-# LANGUAGE ConstraintKinds #-}
+{-# LANGUAGE DataKinds #-}
+{-# LANGUAGE DefaultSignatures #-}
+{-# LANGUAGE DeriveFunctor #-}
+{-# LANGUAGE FlexibleContexts #-}
+{-# LANGUAGE FlexibleInstances #-}
+{-# LANGUAGE InstanceSigs #-}
+{-# LANGUAGE KindSignatures #-}
+{-# LANGUAGE QuantifiedConstraints #-}
+{-# LANGUAGE RankNTypes #-}
+{-# LANGUAGE ScopedTypeVariables #-}
+{-# LANGUAGE StandaloneDeriving #-}
+{-# LANGUAGE TemplateHaskell #-}
+{-# LANGUAGE TupleSections #-}
+{-# LANGUAGE TypeFamilies #-}
+{-# LANGUAGE TypeOperators #-}
+{-# LANGUAGE UndecidableInstances #-}
+
+module Zipper.Recursive.Internal where
+
+import qualified Control.Comonad as Comonad
+import Control.Comonad.Cofree
+import qualified Control.Comonad.Cofree as Cofree
+import qualified Control.Comonad.Trans.Cofree as CofreeF
+import Control.Lens hiding (children, (:<))
+import Control.Monad
+import Data.Functor.Classes
+import qualified Data.Functor.Foldable as FF
+import Data.Maybe
+
+-- | Alias for constraints required for many zipper operations.
+type Idx i f a = (Ixed (f (Cofree f a)), IxValue (f (Cofree f a)) ~ (Cofree f a), Index (f (Cofree f a)) ~ i)
+
+-- | The core zipper type
+data Zipper i (f :: * -> *) a = Zipper
+  { parents :: [(i, Cofree f a)],
+    focus :: Cofree f a
+  }
+  deriving (Functor)
+
+deriving instance (Eq1 f, Eq i, Eq a) => Eq (Zipper i f a)
+
+deriving instance (Ord1 f, Ord i, Ord a) => Ord (Zipper i f a)
+
+-- | Get the location of the current selection within its parent if we have one.
+-- @O(1)
+currentIndex :: Zipper i f a -> Maybe i
+currentIndex (Zipper ((i, _) : _) _) = Just i
+currentIndex _ = Nothing
+
+-- | Get the path to the current value from the root of the structure.
+-- @O(depth)
+currentPath :: Zipper i f a -> [i]
+currentPath (Zipper parents _) = reverse $ fmap fst parents
+
+-- | Focus the tag at the current position.
+focus_ :: Lens' (Zipper i f a) a
+focus_ f (Zipper parents foc) = Zipper parents <$> (foc & _extract %%~ f)
+
+-- | Focus the currently selected sub-tree as a 'Cofree'
+unwrapped_ :: Lens' (Zipper i f a) (Cofree f a)
+unwrapped_ f (Zipper parents foc) = Zipper parents <$> f foc
+
+-- TODO: implement proper comonad instance
+-- extract :: Zipper i f a -> a
+-- extract (Zipper _ (a :< _)) = a
+
+-- instance Functor f => Comonad (Zipper i f) where
+--   extract = extract . _focus
+--   duplicate :: forall f a. (Zipper i f a) -> Zipper i f (Zipper i f a)
+--   duplicate z@(Zipper parents foc) = Zipper (zipWith (\z (i,_) -> (i, z)) rezippedParents parents) (foc $> z)
+--     where
+--       rezippedParents :: [Zipper i f a]
+--       rezippedParents = unfoldr go z
+--       go current =
+--         let x = up current
+--          in liftA2 (,) x x
+--       -- go (current, []) = Nothing
+--       -- go :: (Zipper i f a, [(i, Cofree f a)]) -> Maybe (_, Cofree f a)
+--       -- go = _
+
+-- | A useful combinator for chaining operations which might fail.
+-- If an operation fails, the original zipper is returned.
+--
+-- E.g.
+--
+-- >>> tug up z
+tug :: (a -> Maybe a) -> a -> a
+tug f a = fromMaybe a (f a)
+
+-- | Create a zipper over a cofree structure
+zipper :: Cofree f a -> Zipper i f a
+zipper f = Zipper [] f
+
+-- | Create a zipper from a recursive type, tagging it with '()'
+fromRecursive :: FF.Recursive t => t -> Zipper i (FF.Base t) ()
+fromRecursive t = zipper $ Cofree.unfold (((),) . FF.project) t
+
+-- | Create a zipper from a recursive type, given a function to generate annotations.
+tagged :: FF.Recursive t => (t -> a) -> t -> Zipper i (FF.Base t) a
+tagged f t = zipper $ Cofree.unfold (\x -> (f x, FF.project x)) t
+
+-- | Select the subtree at the given location.
+-- @O(1)@
+down :: (Idx i f a) => i -> Zipper i f a -> Maybe (Zipper i f a)
+down i (Zipper parents current) = Zipper ((i, current) : parents) <$> (current ^? _unwrap . ix i)
+
+-- | Select the parent of the current location.
+-- @O(1)@
+up :: Idx i f a => Zipper i f a -> Maybe (Zipper i f a)
+up (Zipper ((i, p) : parents) current) = Just $ Zipper parents (p & _unwrap . ix i .~ current)
+up _ = Nothing
+
+-- | Re-zip the entire tree.
+-- @O(d)@
+rezip :: Idx i f a => Zipper i f a -> Cofree f a
+rezip z = case up z of
+  Nothing -> focus z
+  Just p -> rezip p
+
+-- | Rezip, forget all tags, and flatten the structure.
+-- @O(d)@
+flatten :: (FF.Corecursive f, Idx i (FF.Base f) a) => Zipper i (FF.Base f) a -> f
+flatten = FF.cata alg . rezip
+  where
+    alg (_ CofreeF.:< fv) = FF.embed fv
+
+-- | Move to the sibling which is located at 'i' in its parent.
+-- @O(1)@
+--
+-- @@
+-- sibling i = up >=> down i
+-- @@
+sibling :: Idx i f a => i -> Zipper i f a -> Maybe (Zipper i f a)
+sibling i = up >=> down i
+
+-- parentTags :: Traversal' (Zipper i f a) a
+-- parentTags f (Zipper parents foc) = Zipper <$> (forwards (parents & traversed . _2 . _extract %%~ Backwards . f)) <*> pure foc
+
+-- | Traversal over all subtrees of the current location.
+children_ :: Traversable f => Traversal' (Zipper i f a) (Cofree f a)
+children_ f (Zipper parents current) = Zipper parents <$> (current & _unwrap . traversed %%~ f)
+
+-- | Indexed traversal over all subtrees of the current location.
+ichildren_ :: TraversableWithIndex i f => IndexedTraversal' i (Zipper i f a) (Cofree f a)
+ichildren_ f (Zipper parents current) = Zipper parents <$> (current & _unwrap . itraversed %%@~ \i a -> indexed f i a)
+
+-- | Get the base-functor at the current location.
+-- @O(1)
+branches :: Zipper i f a -> f (Cofree f a)
+branches (Zipper _ (_ :< cs)) = cs
+
+-- | A lens over the base-functor at the current location.
+branches_ :: Lens' (Zipper i f a) (f (Cofree f a))
+branches_ = lens getter setter
+  where
+    getter (Zipper _ (_ :< f)) = f
+    setter (Zipper p (a :< _)) f = (Zipper p (a :< f))
+
+-- retag :: Functor f => (a -> f b -> b) -> Cofree f a -> Cofree f b
+-- retag f (a :< fr) =
+--   let cs = fmap (retag f) fr
+--    in (f a $ fmap Comonad.extract cs) :< cs
+
+-- | Fold a zipper from bottom to top.
+-- @O(n)
+fold :: (Functor f, Idx i f a) => (a -> f r -> r) -> Zipper i f a -> r
+fold f = FF.cata (\(a CofreeF.:< fr) -> f a fr) . rezip
