diff --git a/ChangeLog.md b/ChangeLog.md
new file mode 100644
--- /dev/null
+++ b/ChangeLog.md
@@ -0,0 +1,5 @@
+# Changelog for composite-xml
+
+## v0.1.0
+
+* Add RecXML Type
diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,30 @@
+Copyright Author name here (c) 2022
+
+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,55 @@
+# composite-xml
+
+A simple xml parser/printer type using composite records. RecXML is defined like
+
+```
+data RecXML :: Symbol -> [Type] -> [Type] -> Type where
+  RNode :: Rec Maybe xs -> [Field ys] -> RecXML s xs ys
+```
+
+An `RNode` is typed indexed by it's node name, an index of attributes it may
+contain, and list of child node types of which it may contain any number or
+multiplicity. The child nodes will typically also be RecXMLs, but this is not
+enforced.
+
+```
+withLensesAndProxies
+  [d|
+    type A = "a" :-> Text
+
+    type B = "b" :-> Int
+
+    type C = "c" :-> Bool
+
+    type D = "d" :-> Text
+
+    type E = "e" :-> Int
+    |]
+
+type Child1 = RecXML "Child1" '[C] '[]
+type Child2 = RecXML "Child2" '[D, E] '[]
+
+type Root = RecXML "Root" '[A, B] '[Child1, Child2]
+
+child1 :: Child1
+child1 = RNode (Just True :^: RNil) []
+
+child2 :: Child2
+child2 = RNode (Just "bar" :^: Just 7 :^: RNil) []
+
+child2b :: Child2
+child2b = RNode (Just "quux" :^: Just 8 :^: RNil) []
+
+root :: Root
+root = RNode (Just "foo" :^: Just 5 :^: RNil) $ [field child1, field child2, field child2b]
+```
+
+corresponds to the xml
+
+```
+<Root a="foo" b="5">
+  <Child1 c="true"></Child1>
+  <Child2 d="bar" e="7"></Child2>
+  <Child2 d="quux" e="8"></Child2>
+</Root>
+```
diff --git a/composite-xml.cabal b/composite-xml.cabal
new file mode 100644
--- /dev/null
+++ b/composite-xml.cabal
@@ -0,0 +1,62 @@
+cabal-version: 1.12
+
+-- This file has been generated from package.yaml by hpack version 0.34.7.
+--
+-- see: https://github.com/sol/hpack
+
+name:           composite-xml
+version:        0.1.0.0
+synopsis:       RecXML Type
+description:    RecXML type
+category:       Composite, Data, XML
+author:         Daniel Firth
+maintainer:     dan.firth@homotopic.tech
+copyright:      2021 Daniel Firth
+license:        MIT
+license-file:   LICENSE
+build-type:     Simple
+extra-source-files:
+    README.md
+    ChangeLog.md
+    test/data/A.xml
+
+source-repository head
+  type: git
+  location: https://gitlab.homotopic.tech/haskell/composite-xml
+
+library
+  exposed-modules:
+      Composite.XML
+  other-modules:
+      Paths_composite_xml
+  hs-source-dirs:
+      src
+  ghc-options: -Weverything -Wno-all-missed-specialisations -Wno-missing-import-lists -Wno-implicit-prelude -Wno-monomorphism-restriction -Wno-missing-local-signatures -Wno-missing-safe-haskell-mode -Wno-prepositive-qualified-module -Wno-orphans -Wno-safe -Wno-unsafe
+  build-depends:
+      base >=4.7 && <5
+    , composite-base >=0.7.0.0 && <0.9
+    , containers
+    , text >=1.0 && <2.1
+    , vinyl >=0.13.0 && <0.15
+    , xml-conduit >=1.9 && <2.0
+  default-language: Haskell2010
+
+test-suite composite-xml-test
+  type: exitcode-stdio-1.0
+  main-is: Spec.hs
+  other-modules:
+      Paths_composite_xml
+  hs-source-dirs:
+      test
+  ghc-options: -Weverything -Wno-all-missed-specialisations -Wno-missing-import-lists -Wno-implicit-prelude -Wno-monomorphism-restriction -Wno-missing-local-signatures -Wno-missing-safe-haskell-mode -Wno-prepositive-qualified-module -Wno-orphans -Wno-safe -Wno-unsafe -threaded -rtsopts -with-rtsopts=-N
+  build-depends:
+      base >=4.7 && <5
+    , composite-base >=0.7.0.0 && <0.9
+    , composite-xml
+    , containers
+    , tasty
+    , tasty-hunit
+    , text >=1.0 && <2.1
+    , vinyl >=0.13.0 && <0.15
+    , xml-conduit >=1.9 && <2.0
+  default-language: Haskell2010
diff --git a/src/Composite/XML.hs b/src/Composite/XML.hs
new file mode 100644
--- /dev/null
+++ b/src/Composite/XML.hs
@@ -0,0 +1,223 @@
+{-# LANGUAGE AllowAmbiguousTypes      #-}
+{-# LANGUAGE DataKinds                #-}
+{-# LANGUAGE DerivingStrategies       #-}
+{-# LANGUAGE FlexibleContexts         #-}
+{-# LANGUAGE FlexibleInstances        #-}
+{-# LANGUAGE FunctionalDependencies   #-}
+{-# LANGUAGE GADTs                    #-}
+{-# LANGUAGE LambdaCase               #-}
+{-# LANGUAGE OverloadedStrings        #-}
+{-# LANGUAGE PolyKinds                #-}
+{-# LANGUAGE ScopedTypeVariables      #-}
+{-# LANGUAGE StandaloneDeriving       #-}
+{-# LANGUAGE StandaloneKindSignatures #-}
+{-# LANGUAGE TypeApplications         #-}
+{-# LANGUAGE TypeFamilies             #-}
+{-# LANGUAGE TypeOperators            #-}
+{-# LANGUAGE UndecidableInstances     #-}
+module Composite.XML (RecXML(RNode)
+         , ToAttr(..)
+         , ToAttrs(..)
+         , FromAttr(..)
+         , FromAttrs(..)
+         , Formattable(..)
+         , Readable(..)
+         , ToElement(..)
+         , ToElements(..)
+         , FromElement(..)
+         , FromElements(..)) where
+
+
+
+import           Composite.CoRecord
+import           Composite.Record
+import           Control.Arrow
+import           Control.Monad
+import           Data.Functor.Identity
+import           Data.Kind
+import           Data.Map              (Map)
+import qualified Data.Map              as Map
+import           Data.Maybe
+import           Data.Proxy
+import           Data.Ratio
+import           Data.String
+import           Data.Text             (Text)
+import qualified Data.Text             as T
+import           Data.Vinyl
+import           Data.Vinyl.Functor    hiding (Identity)
+import           GHC.TypeLits
+import           Text.XML              as X
+
+type RecXML :: Symbol -> [Type] -> [Type] -> Type
+data RecXML :: Symbol -> [Type] -> [Type] -> Type where
+  RNode :: Rec Maybe xs -> [Field ys] -> RecXML s xs ys
+
+deriving stock instance (Show (Field ys), Show (Rec Maybe xs), RecApplicative ys) => Show (RecXML s xs ys)
+deriving stock instance (Eq (Field ys), Eq (Rec Maybe xs), RecApplicative ys) => Eq (RecXML s xs ys)
+
+type ToAttrs :: Type -> Constraint
+class ToAttrs x where
+  toAttrs :: x -> Map Name Text
+
+instance ToAttrs (Rec f '[]) where
+  toAttrs RNil = mempty
+
+type Formattable :: Type -> Constraint
+class Formattable x where
+  formatC :: x -> Text
+
+instance Formattable Bool where
+  formatC True  = "true"
+  formatC False = "false"
+
+instance Formattable String where
+  formatC = T.pack
+
+instance Formattable Int where
+  formatC = T.pack . show
+
+instance Formattable () where
+  formatC = T.pack . show
+
+instance Formattable (Ratio Integer) where
+  formatC = T.pack . show
+
+instance Formattable Double where
+  formatC = T.pack . show
+
+instance Formattable Text where
+  formatC = id
+
+type ToAttr :: Type -> Constraint
+class ToAttr a where
+  toAttr :: a -> (Name, Text)
+
+instance (Formattable a, KnownSymbol s) => ToAttr (s :-> a) where
+  toAttr = first (fromString . T.unpack) . fmap formatC . valWithName
+
+instance (ToAttr x, ToAttrs (Record xs)) => ToAttrs (Record (x ': xs)) where
+  toAttrs (Identity x :& xs) = (Map.fromList . pure . toAttr $ x) <> toAttrs xs
+
+instance (ToAttr x, ToAttrs (Rec Maybe xs)) => ToAttrs (Rec Maybe (x ': xs)) where
+  toAttrs (Just x :& xs)  = (Map.fromList . pure . toAttr $ x) <> toAttrs xs
+  toAttrs (Nothing :& xs) = toAttrs xs
+
+
+type ToElement :: Type -> Constraint
+class ToElement a where
+  toElement :: a -> Element
+
+type ToElements :: Type -> Constraint
+class ToElements x where
+  toElements :: x -> [Element]
+
+instance ToElements (Rec f '[]) where
+  toElements RNil = mempty
+
+instance (ToElement x, ToElements (Record xs)) => ToElements (Record (x ': xs)) where
+  toElements (Identity x :& xs) = toElement x : toElements xs
+
+instance (KnownSymbol s, ToAttrs (Rec Maybe xs), RecApplicative ys, AllHave '[ToElement] ys) => ToElement (RecXML s xs ys) where
+  toElement (RNode x y) = Element (fromString $ symbolVal @s (Proxy :: Proxy s)) (toAttrs x) (NodeElement <$> toElements y)
+
+type Readable :: Type -> Constraint
+class Readable x where
+  readC :: Text -> Maybe x
+
+instance Readable Bool where
+  readC = \case
+    "true"  -> Just True
+    "false" -> Just False
+    _       -> Nothing
+
+instance Readable String where
+  readC = Just . T.unpack
+
+instance Readable Text where
+  readC = Just
+
+instance Readable Double where
+  readC = Just . read . T.unpack
+
+instance Readable Int where
+  readC = Just . read . T.unpack
+
+type FromAttr :: Symbol -> Type -> Constraint
+class FromAttr s a | a -> s where
+  fromAttr :: (Name, Text) -> Maybe a
+
+instance (Readable a, KnownSymbol s) => FromAttr s (s :-> a) where
+  fromAttr (n, x) = if n == fromString (symbolVal @s (Proxy :: Proxy s)) then Val @s <$> readC x else Nothing
+
+type FromAttrs :: Type -> Constraint
+class FromAttrs a where
+  fromAttrs :: [(Name, Text)] -> Maybe a
+
+instance FromAttrs (Rec f '[]) where
+  fromAttrs _ = pure RNil
+
+instance (FromAttr s (s :-> x), FromAttrs (Record xs)) => FromAttrs (Record (s :-> x ': xs)) where
+  fromAttrs xs = do
+    let as' = fmap (\i -> (i, fromAttr @s @(s :-> x) i)) xs
+    let ts = snd <$> Prelude.filter (isJust . snd) as'
+    let rs = fst <$> Prelude.filter (isNothing . snd) as'
+    t <- join $ listToMaybe ts
+    xs' <- fromAttrs rs
+    pure $ Identity t :& xs'
+
+instance (FromAttr s (s :-> x), FromAttrs (Rec Maybe xs)) => FromAttrs (Rec Maybe (s :-> x ': xs)) where
+  fromAttrs xs = do
+    let as' = fmap (\i -> (i, fromAttr @s @(s :-> x) i)) xs
+    let ts = snd <$> Prelude.filter (isJust . snd) as'
+    let rs = fst <$> Prelude.filter (isNothing . snd) as'
+    let t = join $ listToMaybe ts
+    xs' <- fromAttrs rs
+    pure $ t :& xs'
+
+type FromElement :: Type -> Constraint
+class FromElement a where
+  fromElement :: Element -> Maybe a
+
+type FromElements :: Type -> Constraint
+class FromElements x where
+  fromElements :: [Element] -> Maybe x
+
+instance forall ys. (AllHave '[ToElement] ys, RecApplicative ys) => ToElement (CoRec Identity ys) where
+  toElement (CoVal (Identity x)) = toElement' x
+    where
+      toElementer :: Rec (Op Element) ys
+      toElementer = reifyDicts (Proxy @'[ToElement]) (\_ -> Op toElement)
+      toElement' = runOp (rget toElementer)
+
+instance forall ys. (AllHave '[FromElement] ys, RecApplicative ys, FoldRec ys ys, RMap ys) => FromElement (CoRec Identity ys) where
+  fromElement x = firstField $ ($ x) $ rtraverse getCompose fromElementer where
+    fromElementer :: Rec ((->) Element :. Maybe ) ys
+    fromElementer = reifyDicts (Proxy @'[FromElement]) (\_ -> Compose fromElement)
+
+instance ToElement a => ToElements [a] where
+  toElements = Prelude.map toElement
+
+instance FromElement a => FromElements [a] where
+  fromElements = Prelude.traverse fromElement
+
+instance FromElements (Record '[]) where
+  fromElements [] = Just RNil
+  fromElements _  = Nothing
+
+instance (FromElement x, FromElements (Record xs)) => FromElements (Record (x ': xs)) where
+  fromElements (x : xs) = do
+     x' <- fromElement x
+     xs' <- fromElements xs
+     pure $ Identity x' :& xs'
+  fromElements _ = Nothing
+
+instance (KnownSymbol s, FromAttrs (Rec Maybe xs), FromElement (CoRec Identity ys)) => FromElement (RecXML s xs ys) where
+  fromElement (Element n xs ys) = do
+    guard (symbolVal (Proxy @s) == T.unpack (nameLocalName n))
+    xs' <- fromAttrs $ Map.toList xs
+    ts <- sequence $ Prelude.filter isJust $ Prelude.map
+                    (\case
+                       NodeElement x' -> Just x'
+                       _              -> Nothing) ys
+    ys' <- fromElements ts
+    pure $ RNode xs' ys'
diff --git a/test/Spec.hs b/test/Spec.hs
new file mode 100644
--- /dev/null
+++ b/test/Spec.hs
@@ -0,0 +1,69 @@
+{-# LANGUAGE DataKinds                  #-}
+{-# LANGUAGE DerivingVia                #-}
+{-# LANGUAGE FlexibleContexts           #-}
+{-# LANGUAGE FlexibleInstances          #-}
+{-# LANGUAGE GeneralisedNewtypeDeriving #-}
+{-# LANGUAGE OverloadedStrings          #-}
+{-# LANGUAGE RecordWildCards            #-}
+{-# LANGUAGE ScopedTypeVariables        #-}
+{-# LANGUAGE StandaloneDeriving         #-}
+{-# LANGUAGE TemplateHaskell            #-}
+{-# LANGUAGE TypeApplications           #-}
+{-# LANGUAGE TypeOperators              #-}
+{-# OPTIONS_GHC -fno-warn-unused-top-binds #-}
+{-# OPTIONS_GHC -fno-warn-missing-kind-signatures #-}
+
+module Main (main) where
+
+import           Composite
+import           Composite.CoRecord hiding (Op)
+import           Composite.TH
+import           Composite.XML
+import           Data.Text
+import           Test.Tasty
+import           Test.Tasty.HUnit
+import           Text.XML           as X
+
+withLensesAndProxies
+  [d|
+    type A = "a" :-> Text
+
+    type B = "b" :-> Int
+
+    type C = "c" :-> Bool
+
+    type D = "d" :-> Text
+
+    type E = "e" :-> Int
+    |]
+
+type Child1 = RecXML "Child1" '[C] '[]
+
+type Child2 = RecXML "Child2" '[D, E] '[]
+
+type Root = RecXML "Root" '[A, B] '[Child1, Child2]
+
+child1 :: Child1
+child1 = RNode (Just True :^: RNil) []
+
+child2 :: Child2
+child2 = RNode (Just "bar" :^: Just 7 :^: RNil) []
+
+child2b :: Child2
+child2b = RNode (Just "quux" :^: Just 8 :^: RNil) []
+
+root :: Root
+root = RNode (Just "foo" :^: Just 5 :^: RNil) [field child1, field child2, field child2b]
+
+tests :: TestTree
+tests =
+  testGroup
+    "Parsing Tests"
+    [ testCase "parses RecXML" $ do
+        k <- X.readFile def "./test/data/A.xml"
+        let x = fromElement @Root $ documentRoot k
+        assertEqual "" (Just root) x
+    ]
+
+main :: IO ()
+main = defaultMain tests
diff --git a/test/data/A.xml b/test/data/A.xml
new file mode 100644
--- /dev/null
+++ b/test/data/A.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<Root a="foo" b="5">
+  <Child1 c="true"></Child1>
+  <Child2 d="bar" e="7"></Child2>
+  <Child2 d="quux" e="8"></Child2>
+</Root>
