diff --git a/isobmff-builder.cabal b/isobmff-builder.cabal
--- a/isobmff-builder.cabal
+++ b/isobmff-builder.cabal
@@ -1,5 +1,5 @@
 name:                isobmff-builder
-version:             0.2.0.3
+version:             0.3.0.0
 synopsis:            A (bytestring-) builder for the ISO-14496-12 base media file format
 description:         Please see README.md
 homepage:            https://github.com/sheyll/isobmff-builder#readme
@@ -17,7 +17,9 @@
   hs-source-dirs:      src
   exposed-modules:     Data.ByteString.IsoBaseFileFormat.Builder
                      , Data.ByteString.IsoBaseFileFormat.Boxes.Box
+                     , Data.ByteString.IsoBaseFileFormat.Boxes.BoxFields
                      , Data.ByteString.IsoBaseFileFormat.Boxes.FileType
+                     , Data.ByteString.IsoBaseFileFormat.Boxes.FullBox
                      , Data.ByteString.IsoBaseFileFormat.Boxes.MediaData
                      , Data.ByteString.IsoBaseFileFormat.Boxes.Skip
                      , Data.ByteString.IsoBaseFileFormat.Boxes.ProgressiveDownloadInformation
@@ -25,7 +27,11 @@
   build-depends:       base >= 4.9 && < 5
                      , bytestring >= 0.10.8.1
                      , type-list >= 0.5.0.0
+                     -- , mtl
+                     , vector-sized >= 0.3.2.0
+                     , singletons >= 2.2
   default-language:    Haskell2010
+  ghc-options:       -Wall -funbox-strict-fields -fno-warn-unused-do-bind
   default-extensions:  ConstraintKinds
                      , CPP
                      , DataKinds
@@ -52,6 +58,50 @@
                      , TypeInType
                      , TypeOperators
                      , TypeSynonymInstances
+
+test-suite spec
+  type:                exitcode-stdio-1.0
+  ghc-options:         -Wall
+  hs-source-dirs:      spec
+  default-language:    Haskell2010
+  main-is:             Spec.hs
+  other-modules:       BoxFieldsSpec
+                     , BoxSpec
+  build-depends:       base >= 4.9 && < 5
+                     , bytestring >= 0.10.8.1
+                     , hspec
+                     , isobmff-builder
+                     , binary
+  default-language:    Haskell2010
+  default-extensions:  ConstraintKinds
+                     , CPP
+                     , DataKinds
+                     , DefaultSignatures
+                     , DeriveDataTypeable
+                     , DeriveFunctor
+                     , DeriveGeneric
+                     , FlexibleInstances
+                     , FlexibleContexts
+                     , FunctionalDependencies
+                     , GADTs
+                     , GeneralizedNewtypeDeriving
+                     , KindSignatures
+                     , MultiParamTypeClasses
+                     , OverloadedStrings
+                     , QuasiQuotes
+                     , RecordWildCards
+                     , RankNTypes
+                     , ScopedTypeVariables
+                     , StandaloneDeriving
+                     , TemplateHaskell
+                     , TupleSections
+                     , TypeFamilies
+                     , TypeInType
+                     , TypeOperators
+                     , TypeSynonymInstances
+  ghc-options:       -Wall -O0 -j +RTS -A256m -n2m -RTS
+                     -fwarn-unused-binds -fno-warn-unused-do-bind -fno-warn-missing-signatures
+
 source-repository head
   type:     git
   location: https://github.com/githubuser/isobmff-builder
diff --git a/spec/BoxFieldsSpec.hs b/spec/BoxFieldsSpec.hs
new file mode 100644
--- /dev/null
+++ b/spec/BoxFieldsSpec.hs
@@ -0,0 +1,76 @@
+module BoxFieldsSpec (spec) where
+
+import Test.Hspec
+import Data.ByteString.IsoBaseFileFormat.Builder
+-- import qualified Data.ByteString.Builder as B
+import qualified Data.ByteString.Lazy as BL
+-- import qualified Data.Binary.Get as Binary
+import Control.Exception (evaluate)
+
+-- import qualified Data.ByteString.Builder as B import qualified Data.ByteString.Lazy as BL import
+-- qualified Data.Binary.Get as Binary
+
+spec :: Spec
+spec =
+  describe "Scalar, Constant, Template and ScalarArry composition" $
+  describe "IsBoxContent instances" $
+  do describe "example1" $
+       do it "boxSize reports the correct size" $
+             boxSize example1 `shouldBe` (1 + 1 + (3 * 8) + (7 * 8))
+          it "crashes during rendering because of the invalid number of array elements for \"baz\"" $
+             evaluate (renderBox example1') `shouldThrow` anyException
+     describe "example2" $
+       do it "boxSize reports the correct size" $
+             boxSize example2 `shouldBe` (2 * 4 + 2 + 2 + 2 + 2 + 9 * 4 + 4 + 4)
+          it "it renders the expected content" $
+             renderBox example2 `shouldBe` (BL.pack [0,0,0,0,
+                                                     0,0,0,0,
+                                                        0,65,
+                                                        0,66,
+                                                         1,0,
+                                                         0,0,
+                                                     0,0,0,67,
+                                                     0,0,0,68,
+                                                     0,0,0,69,
+                                                     0,0,0,70,
+                                                     0,0,0,71,
+                                                     0,0,0,72,
+                                                     0,0,0,73,
+                                                     0,0,0,74,
+                                                     0,0,0,75,
+                                                     0,0,0,76,
+                                                     0,0,0,77])
+
+renderBox :: IsBoxContent c => c -> BL.ByteString
+renderBox = toLazyByteString . boxBuilder
+
+type ExampleContent = Scalar Word8 "bla" :+ Constant (Scalar Word8 "blub") 123 :+ Template (ScalarArray "foos" 3 Int64) '[1, 2, 3] :+ ScalarArray "baz" 7 Word64
+
+type ExampleContentShort = U8 "bla" :+ Constant (U8 "blub") 123 :+ Template (I64Arr "foos" 3) '[1, 2, 3] :+ U64Arr "baz" 7
+
+example1 :: ExampleContent
+example1 = Scalar 100 <+> Constant <+> Default <+> u64Arr [1,2,3]
+
+example1' :: ExampleContentShort
+example1' = example1
+
+type Example2 isAudio =
+     Constant (I32Arr "reserved" 2) '[0, 0]
+  :+ Template (I16 "layer") 0
+  :+ Template (I16 "alternate_group") 0
+  :+ Template (I16 "volume") (If isAudio 256 0)
+  :+ Constant (I16 "reserved") 0
+  :+ Template (I32Arr "matrix" 9) '[65536, 0, 0, 0, 65536, 0, 0, 0, 1073741824]
+  :+ I32 "width"
+  :+ I32 "height"
+
+example2 :: Example2 'True
+example2 =
+  Constant <+>
+  Custom 65 <+>
+  Custom 66 <+>
+  Default <+>
+  Constant <+>
+  Custom (i32Arr [67 .. 75]) <+>
+  i32 76 <+>
+  i32 77
diff --git a/spec/BoxSpec.hs b/spec/BoxSpec.hs
new file mode 100644
--- /dev/null
+++ b/spec/BoxSpec.hs
@@ -0,0 +1,57 @@
+module BoxSpec (spec) where
+
+import Test.Hspec
+import Data.ByteString.IsoBaseFileFormat.Builder
+import qualified Data.ByteString.Builder as B
+import qualified Data.ByteString.Lazy as BL
+import qualified Data.Binary.Get as Binary
+
+spec :: Spec
+spec =
+  do describe "IsBoxContent instances" $
+       do describe "()" $
+            do describe "boxSize" $ it "returns 0" $ boxSize () `shouldBe` 0
+               describe "boxBuilder" $
+                 it "emits no data" $
+                 BL.length (B.toLazyByteString (boxBuilder ())) `shouldBe` 0
+          describe "Box" $
+            do describe "boxSize" $
+                 it "returns the header size if the content is empty" $
+                 boxSize testBox1 `shouldBe` (4 + 4)
+          describe "Boxes" $
+            do describe "boxSize" $
+                 do describe "a box with one nested box" $
+                      do it "returns the sum of both boxSizes" $
+                           boxSize (testParentBox1 $ Nested :. testBox1) `shouldBe`
+                           (2 * boxSize testBox1)
+                         it "returns the same value as written by boxBuilder" $
+                           let b = testParentBox1 $ Nested :. testBox1
+                               writtenSize =
+                                 let out = toLazyByteString (boxBuilder b)
+                                     getSize = Binary.runGet Binary.getWord32be
+                                 in BoxSize $ fromIntegral $ getSize out
+                               reportedSize = boxSize b
+                           in writtenSize `shouldBe` reportedSize
+
+
+data TestBox1
+
+instance BoxRules TestBox1 where
+  type RestrictedTo TestBox1 = 'Nothing
+
+instance IsBoxType' TestBox1 where
+  toBoxType' _ = StdType "tst1"
+
+testBox1 :: Box' TestBox1
+testBox1 = closedBox ()
+
+
+data TestParentBox1
+
+instance BoxRules TestParentBox1
+
+instance IsBoxType' TestParentBox1 where
+  toBoxType' _ = StdType "par1"
+
+testParentBox1 :: (ValidBoxes TestParentBox1 ts) => Boxes ts -> Box' TestParentBox1
+testParentBox1 = containerBox
diff --git a/spec/Spec.hs b/spec/Spec.hs
new file mode 100644
--- /dev/null
+++ b/spec/Spec.hs
@@ -0,0 +1,1 @@
+{-# OPTIONS_GHC -F -pgmF hspec-discover #-}
diff --git a/src/Data/ByteString/IsoBaseFileFormat/Boxes/Box.hs b/src/Data/ByteString/IsoBaseFileFormat/Boxes/Box.hs
--- a/src/Data/ByteString/IsoBaseFileFormat/Boxes/Box.hs
+++ b/src/Data/ByteString/IsoBaseFileFormat/Boxes/Box.hs
@@ -20,16 +20,20 @@
 import GHC.TypeLits as X
 import Data.String
 import Data.Type.Equality
-
 import Data.Type.List
 import Data.Type.Bool
-import Data.Type.Equality
-import GHC.Exts
+import qualified Data.ByteString as B
 
 -- * Basic Types and classes
 
+-- | Base class for all (abstract/phantom/normal-) types that represent boxes
+class (IsBoxContent (BoxContent t), BoxRules t) => IsBoxType' t where
+  type BoxContent t
+  type BoxContent t = ()
+  toBoxType' :: proxy t -> BoxType
+
 -- | A class that describes (on the type level) how a box can be nested into
--- other boxes (see 'Boxes').
+-- other boxes (see 'Boxes).
 class BoxRules (t :: k) where
   -- | List of boxes that this box can be nested into.
   type RestrictedTo t :: Maybe [k]
@@ -43,18 +47,11 @@
   type RequiredNestedBoxes t = '[]
   -- | Describes how many times a box should be present in a container (-box).
   type GetCardinality t (c :: k) :: Cardinality
-  type GetCardinality t any = ExactlyOnce
+  type GetCardinality t any = 'ExactlyOnce
 
 -- | Describes how many times a box should be present in a container.
 data Cardinality = AtMostOnce | ExactlyOnce | OnceOrMore
 
--- | Convert type level box types to values
-class BoxRules t => IsBoxType (t :: k) where
-  toBoxType :: proxy t -> BoxType
-
-instance (BoxRules t, KnownSymbol t) => IsBoxType t where
-  toBoxType _ = StdType (fromString (symbolVal (Proxy :: Proxy t)))
-
 -- | Types that go into a box. A box content is a piece of data that can be
 -- reused in different instances of 'IsBox'. It has no 'BoxType' and hence
 -- defines no box.
@@ -62,49 +59,56 @@
   boxSize :: a -> BoxSize
   boxBuilder :: a -> Builder
 
--- | An empty box content can by represented by @()@ (i.e. /unit/).
-instance IsBoxContent () where
-  boxSize _ = 0
-  boxBuilder _ = mempty
+-- * Data types
 
--- | Box content composed of box contents @a@ and @b@.
-data Extend a b =
-  Extend a
-         b
+-- | A type that wraps the contents of a box and the box type.
+data Box' b where
+  Box' :: (IsBoxType' b, ValidBoxes b ts) => BoxContent b -> Boxes ts -> Box' b
 
-instance (IsBoxContent p,IsBoxContent c) => IsBoxContent (Extend p c) where
-  boxSize (Extend p c) = boxSize p + boxSize c
-  boxBuilder (Extend p c) = boxBuilder p <> boxBuilder c
+instance IsBoxContent (Box' cnt) where
+  boxBuilder b@(Box' cnt nested) = sB <> tB <> sExtB <> tExtB <> cntB <> nestedB
+    where s       = boxSize    b
+          t       = toBoxType' b
+          sB      = boxBuilder s
+          sExtB   = boxBuilder (BoxSizeExtension s)
+          tB      = boxBuilder t
+          tExtB   = boxBuilder (BoxTypeExtension t)
+          cntB    = boxBuilder cnt
+          nestedB = boxBuilder nested
 
--- * Boxes
+  boxSize b@(Box' cnt nested) = sPayload + boxSize (BoxSizeExtension sPayload)
+    where sPayload =   boxSize (BoxSize undefined)
+                     + boxSize t
+                     + boxSize cnt
+                     + boxSize (BoxTypeExtension t)
+                     + boxSize nested
+          t        = toBoxType' b
 
--- | Create a 'Box' from any 'IsBoxContent' with a type.
-box :: forall t c.
-       (IsBoxType t,IsBoxContent c)
-    => c -> Box t
-box cnt = Box (toBoxType (Proxy :: Proxy t)) cnt
+-- | A heterogenous collection of boxes.
+data Boxes (boxTypes :: [k]) where
+        Nested :: Boxes '[]
+        (:.) :: Boxes ts -> Box' t -> Boxes (t ': ts)
 
--- | An /empty/ box. This is for boxes without any extra /fields/. Although it is
--- called /emptyBox/ it might still contain nested boxes.
-emptyBox :: forall t . (IsBoxType t) => Box t
-emptyBox = box ()
+infixl 2 :.
 
--- | A type that wraps the contents of a box and the box type.
-data Box (b :: t) where
-        Box :: (IsBoxType t,IsBoxContent c) => BoxType -> c -> Box t
+instance IsBoxContent (Boxes bs) where
+  boxSize Nested = 0
+  boxSize (bs :. b) = boxSize bs + boxSize b
+  boxBuilder Nested = mempty
+  boxBuilder (bs :. b) = boxBuilder bs <> boxBuilder b
 
-instance IsBoxContent (Box t) where
-  boxBuilder b@(Box t cnt) = sFix <> tFix <> sExt <> tExt <> boxBuilder cnt
-    where s = boxSize b
-          sFix = boxBuilder s
-          sExt = boxBuilder (BoxSizeExtension s)
-          tFix = boxBuilder t
-          tExt = boxBuilder (BoxTypeExtension t)
-  boxSize b@(Box t cnt) = sPayload + boxSize (BoxSizeExtension sPayload)
-    where sPayload =
-            boxSize sPayload + boxSize t + boxSize cnt +
-            boxSize (BoxTypeExtension t)
+-- | A box that contains no nested boxes.
+closedBox :: (IsBoxType' t, ValidBoxes t '[]) => BoxContent t -> Box' t
+closedBox c = Box' c Nested
 
+-- | A box that contains no fields, but nested boxes.
+containerBox :: (IsBoxType' t, ValidBoxes t ts, BoxContent t ~ ()) => Boxes ts -> Box' t
+containerBox = Box' ()
+
+-- | A complete media file, consisting of top-level boxes.
+mediaFile :: Boxes ts -> Builder
+mediaFile = boxBuilder
+
 -- * Box Size and Type
 
 -- | The size of the box. If the size is limited to a (fixed) value, it can be
@@ -121,7 +125,7 @@
   boxBuilder UnlimitedSize = word32BE 0
   boxBuilder (BoxSize n) =
     word32BE $
-    if n < 2 ^ 32
+    if n < (4294967296 :: Word64)
        then fromIntegral n
        else 1
 
@@ -149,13 +153,13 @@
 instance IsBoxContent BoxSizeExtension where
   boxBuilder (BoxSizeExtension UnlimitedSize) = mempty
   boxBuilder (BoxSizeExtension (BoxSize n)) =
-    if n < 2 ^ 32
+    if n < 4294967296
        then mempty
        else word64BE n
   boxSize (BoxSizeExtension UnlimitedSize) = 0
   boxSize (BoxSizeExtension (BoxSize n)) =
     BoxSize $
-    if n < 2 ^ 32
+    if n < 4294967296
        then 0
        else 8
 
@@ -194,7 +198,7 @@
   boxBuilder t =
     case t of
       StdType x -> boxBuilder x
-      CustomBoxType u -> boxBuilder (FourCc ('u','u','i','d'))
+      CustomBoxType _ -> boxBuilder (FourCc ('u','u','i','d'))
 
 -- | When using custom types extra data must be written after the extra size
 -- information. Since the box type and the optional custom box type are not
@@ -213,82 +217,14 @@
 
 -- * Type-safe box composition
 
--- | A 'ParentBox' is a 'Box' but without it's children. It has no
--- 'IsBoxContent' instance, and it must be converted to a real 'Box' using
--- 'boxes'. This is to prevent creation of boxes with invalid children.
-data ParentBox (b :: t) where
-        ParentBox :: (IsBoxType t,IsBoxContent c) => BoxType -> c -> ParentBox t
 
--- | A parent box. Similar to 'box' but for 'ParentBox'es.
-parentBox :: forall t c . (IsBoxType t, IsBoxContent c) => c -> ParentBox t
-parentBox = toParentBox . box
-  where toParentBox :: IsBoxType s => Box s -> ParentBox s
-        toParentBox (Box t c) = ParentBox t c
-
--- | A 'ParentBox' without /fields/.
-emptyParentBox :: forall t . (IsBoxType t) => ParentBox t
-emptyParentBox = parentBox ()
-
--- | A box that containing nested boxes. The nesting is validated at compile
--- time using 'BoxRules'. 
-boxes :: (IsBoxType t,IsBoxContent (Boxes t ts))
-      => ParentBox t -> Boxes t ts -> Box t
-boxes p = box . Extend (toBox p)
-  where
-    toBox :: IsBoxType t => ParentBox t -> Box t
-    toBox (ParentBox t c) = Box t c
-
--- | An operator for starting a 'Boxes' from the parent box.
---
--- Example:
--- >  xxx :: Box "moov"
--- >  xxx = movieBox
--- >         ^- Nested (movieHeaderBox (MovieHeader ...))
--- >                   :- (trackBox
--- >                       ^- Nested (trackHeaderBox (TrackHeader ...))
--- >                                 :- trackReferenceBox (TrackReference ...)
--- >                                 :- trackGroupingIndication (TrackGroupingInd ...))
---
-(^-) :: (IsBoxType t,IsBoxContent (Boxes t ts))
-     => ParentBox t -> Boxes t ts -> Box t
-parent ^- nested = parent ^- nested
-
-infixr 1 ^-
-
--- | A heterogenous collection of child boxes for a parent box wiht type @cont@.
-data Boxes (cont :: x) (boxTypes :: [x]) where
-        Nested :: IsBoxType t => Box t -> Boxes c '[t]
-        (:-) :: IsBoxType t => Boxes c ts -> Box t -> Boxes c (t ': ts)
-
-infixl 2 :-
-
--- | To be nested into a box, 'Boxes' must be an instance of 'IsBoxContent'.
--- This instance concatenates all nested boxes.
-instance (IsBoxType t,ValidBoxes t bs) => IsBoxContent (Boxes t bs) where
-  boxSize bs = boxSize (UnverifiedBoxes bs)
-  boxBuilder bs = boxBuilder (UnverifiedBoxes bs)
-
--- | An internal wrapper type around 'Boxes' for the 'IsBoxContent' instance.
--- Since the 'IsBoxContent' instance recursivly deconstructs a 'Boxes' the
--- constraints for the validity 'ValidBoxes' cannot be asserted. To circumvent
--- this the 'IsBoxContent' instance for 'Boxes' delegates to the instance of
--- 'UnverifiedBoxes', which has no 'ValidBoxes' constraint in the instance head.
-newtype UnverifiedBoxes t ts = UnverifiedBoxes (Boxes t ts)
-
-instance IsBoxContent (UnverifiedBoxes t bs) where
-  boxSize (UnverifiedBoxes (Nested b)) = boxSize b
-  boxSize (UnverifiedBoxes (bs :- b)) = boxSize (UnverifiedBoxes bs) + boxSize b
-  boxBuilder (UnverifiedBoxes (Nested b)) = boxBuilder b
-  boxBuilder (UnverifiedBoxes (bs :- b)) = boxBuilder (UnverifiedBoxes bs) <> boxBuilder b
-
 -- * Type level consistency checks
 
 -- | A type-level check that uses 'BoxRules' to check that the contained boxes
 -- are standard conform.
 type ValidBoxes t ts =
   ( AllAllowedIn t ts ~ 'True
-  , HasAllRequiredBoxes t (RequiredNestedBoxes t) ts ~ 'True
-  , CheckTopLevelOk t ~ 'True)
+  , HasAllRequiredBoxes t (RequiredNestedBoxes t) ts ~ 'True )
 
 -- | A type function to check that all nested boxes are allowed in the
 -- container.
@@ -307,18 +243,18 @@
 
 -- | The custom (type-) error message for 'AllAllowedIn'.
 type NotAllowedMsg c t =
-  Text "Boxes of type: "
-  :<>: ShowType c
-  :<>: Text " may not contain boxes of type "
-  :<>: ShowType t
-  :$$: Text "Valid containers for "
-  :<>: ShowType t
-  :<>: Text " boxes are: "
-  :$$: ShowType (RestrictedTo t)
-  :$$: ShowType t
-  :<>: If (IsTopLevelBox c)
-          (Text " boxes may appear top-level in a file.")
-          (Text " boxes must be nested.")
+  'Text "Boxes of type: "
+  ':<>: 'ShowType c
+  ':<>: 'Text " may not contain boxes of type "
+  ':<>: 'ShowType t
+  ':$$: 'Text "Valid containers for "
+  ':<>: 'ShowType t
+  ':<>: 'Text " boxes are: "
+  ':$$: 'ShowType (RestrictedTo t)
+  ':$$: 'ShowType t
+  ':<>: If (IsTopLevelBox c)
+          ('Text " boxes may appear top-level in a file.")
+          ('Text " boxes must be nested.")
 
 
 -- | Check that all required boxes have been nested.
@@ -332,103 +268,43 @@
 
 type IsSubSet base sub = Intersection base sub == sub
 
--- | The custom (type-) error message for 'HasAllRequiredBoxes'.
+-- | The custom (type-) error message for 'HasAllRequiredBoxes.
 type MissingRequired c r nested =
-  Text "Boxes of type: "
-  :<>: ShowType c
-  :<>: Text " require these nested boxes: "
-  :<>: ShowType (RequiredNestedBoxes c)
-  :$$: Text "but only these box types were nested: "
-  :<>: ShowType nested
-  :$$: Text "e.g. this type is missing: "
-  :<>: ShowType r
+  'Text "Boxes of type: "
+  ':<>: 'ShowType c
+  ':<>: 'Text " require these nested boxes: "
+  ':<>: 'ShowType (RequiredNestedBoxes c)
+  ':$$: 'Text "but only these box types were nested: "
+  ':<>: 'ShowType nested
+  ':$$: 'Text "e.g. this type is missing: "
+  ':<>: 'ShowType r
 
+-- | Check that all boxes may appear top-level.
+type family CheckAllTopLevelOk (ts :: [k]) :: Bool where
+   CheckAllTopLevelOk '[] = 'True
+   CheckAllTopLevelOk (t ': rest) = CheckTopLevelOk t && CheckAllTopLevelOk rest
+
 -- | Check that the box may appear top-level.
 type family CheckTopLevelOk (t :: k) :: Bool where
    CheckTopLevelOk t = IsTopLevelBox t || TypeError (NotTopLevenError t)
 
+
 -- | The custom (type-) error message indicating that a box may not appear
 -- top-level.
 type NotTopLevenError c =
-       Text "Boxes of type "
-  :<>: ShowType c
-  :<>: Text " MUST be nested inside boxes of these types: "
-  :$$: ShowType (RestrictedTo c)
-
--- * Full Boxes
-
--- | A `Box` with /version/ and /branding/ information
-type FullBox t = Extend FullBoxHeader t
-
--- | Create a 'FullBox' from a 'FourCc' 'StdType' and the nested box content.
-fullBox
- :: (IsBoxType t, IsBoxContent c)
- => BoxVersion -> BoxFlags 24 -> c -> Box t
-fullBox ver fs cnt = box (Extend (FullBoxHeader ver fs) cnt)
-
--- | The additional header with /version/ and /branding/ information
-data FullBoxHeader =
-  FullBoxHeader BoxVersion
-                (BoxFlags 24)
-
-instance IsBoxContent FullBoxHeader where
-  boxSize (FullBoxHeader _ f) = 1 + boxSize f
-  boxBuilder (FullBoxHeader (BoxVersion v) f) = word8 v <> boxBuilder f
-
--- | The box version (in a 'FullBox') is a single byte
-newtype BoxVersion =
-  BoxVersion Word8
-
--- | In addition to a 'BoxVersion' there can be 24 bits for custom flags etc in
--- a 'FullBox'.
-newtype BoxFlags bits =
-  BoxFlags Integer
-  deriving (Eq,Show,Num)
-
--- | Internal function that creates a bit mask with all bits in a 'BoxFlags' set
--- to 1.
-boxFlagBitMask :: KnownNat bits
-               => BoxFlags bits -> Integer
-boxFlagBitMask px = 2 ^ natVal px - 1
+        'Text "Boxes of type "
+  ':<>: 'ShowType c
+  ':<>: 'Text " MUST be nested inside boxes of these types: "
+  ':$$: 'ShowType (RestrictedTo c)
 
--- | Internal function that masks-out all bits higher than 'bits'.
-cropBits :: KnownNat bits
-         => BoxFlags bits -> BoxFlags bits
-cropBits f@(BoxFlags b) = BoxFlags (b .&. boxFlagBitMask f)
+-- * 'IsBoxContent' instances
 
--- | Get the number of bytes required to store a number of bits.
-instance KnownNat bits => IsBoxContent (BoxFlags bits) where
-  boxSize f =
-    let minBytes = fromInteger $ natVal f `div` 8
-        modBytes = fromInteger $ natVal f `mod` 8
-    in BoxSize $ minBytes + signum modBytes
-  boxBuilder f@(BoxFlags b) =
-    let bytes =
-          let (BoxSize bytes') = boxSize f
-          in fromIntegral bytes'
-        wordSeq n
-          | n <= bytes =
-            word8 (fromIntegral (shiftR b ((bytes - n) * 8) .&. 255)) <>
-            wordSeq (n + 1)
-          | otherwise = mempty
-    in wordSeq 1
+-- | An empty box content can by represented by @()@ (i.e. /unit/).
+instance IsBoxContent () where
+  boxSize _ = 0
+  boxBuilder _ = mempty
 
-instance KnownNat bits => Bits (BoxFlags bits) where
-  (.&.) lf@(BoxFlags l) (BoxFlags r) = cropBits $ BoxFlags $ l .&. r
-  (.|.) lf@(BoxFlags l) (BoxFlags r) = cropBits $ BoxFlags $ l .&. r
-  xor (BoxFlags l) (BoxFlags r) = cropBits $ BoxFlags $ xor l r
-  complement (BoxFlags x) = cropBits $ BoxFlags $ complement x
-  shift (BoxFlags x) = cropBits . BoxFlags . shift x
-  rotateL = error "TODO rotateL"
-  rotateR = error "TODO rotateR"
-  bitSize = fromInteger . natVal
-  bitSizeMaybe = Just . fromInteger . natVal
-  isSigned _ = False
-  testBit f n =
-    let (BoxFlags b) = cropBits f
-    in testBit b n
-  bit = cropBits . BoxFlags . bit
-  popCount f =
-    let (BoxFlags b) = cropBits f
-    in popCount b
-  zeroBits = BoxFlags 0
+-- | Trivial instance for 'ByteString'
+instance IsBoxContent B.ByteString where
+  boxSize = fromIntegral . B.length
+  boxBuilder = byteString
diff --git a/src/Data/ByteString/IsoBaseFileFormat/Boxes/BoxFields.hs b/src/Data/ByteString/IsoBaseFileFormat/Boxes/BoxFields.hs
new file mode 100644
--- /dev/null
+++ b/src/Data/ByteString/IsoBaseFileFormat/Boxes/BoxFields.hs
@@ -0,0 +1,239 @@
+-- | Mini EDSL for labelled box fields. The boxfields can be 'Scalar' or
+-- 'ScalarArray's.
+module Data.ByteString.IsoBaseFileFormat.Boxes.BoxFields
+       where
+
+import Data.ByteString.IsoBaseFileFormat.Boxes.Box
+import Data.Int
+import Data.Maybe
+import Text.Printf
+import Data.Singletons
+import Data.Singletons.Prelude.List
+import qualified Data.Vector.Sized as Vec
+
+-- * Scalar box fields
+
+type U64 label = Scalar Word64 label
+
+type I64 label = Scalar Int64 label
+
+u64 :: Word64 -> U64 label
+u64 = Scalar
+
+i64 :: Int64 -> I64 label
+i64 = Scalar
+
+type U32 label = Scalar Word32 label
+
+type I32 label = Scalar Int32 label
+
+u32 :: Word32 -> U32 label
+u32 = Scalar
+
+i32 :: Int32 -> I32 label
+i32 = Scalar
+
+type U16 label = Scalar Word16 label
+
+type I16 label = Scalar Int16 label
+
+u16 :: Word16 -> U16 label
+u16 = Scalar
+
+i16 :: Int16 -> I16 label
+i16 = Scalar
+
+type U8 label = Scalar Word8 label
+
+type I8 label = Scalar Int8 label
+
+u8 :: Word8 -> U8 label
+u8 = Scalar
+
+i8 :: Int8 -> I8 label
+i8 = Scalar
+
+-- | A numeric box field with a type level label. Note that it has a 'Num'
+-- instance. Use the type aliases above, e.g.
+-- 'U8','I8','U16','I16','U32','I32','U64','I64' from above. Use either the
+-- smart constructors, e.g. 'u8','i8','u16','i16','u32','i32','u64','i64' or the
+-- 'Num' instance, whereas the constructors might give a bit more safety.
+newtype Scalar scalartype (label :: k) =
+  Scalar scalartype deriving (Show, Read, Ord, Eq, Num)
+
+instance IsBoxContent (Scalar Word8 label) where
+  boxSize _ = 1
+  boxBuilder (Scalar v) = word8 v
+
+instance IsBoxContent (Scalar Word16 label) where
+  boxSize _ = 2
+  boxBuilder (Scalar v) = word16BE v
+
+instance IsBoxContent (Scalar Word32 label) where
+  boxSize _ = 4
+  boxBuilder (Scalar v) = word32BE v
+
+instance IsBoxContent (Scalar Word64 label) where
+  boxSize _ = 8
+  boxBuilder (Scalar v) = word64BE v
+
+instance IsBoxContent (Scalar Int8 label) where
+  boxSize _ = 1
+  boxBuilder (Scalar v) = int8 v
+
+instance IsBoxContent (Scalar Int16 label) where
+  boxSize _ = 2
+  boxBuilder (Scalar v) = int16BE v
+
+instance IsBoxContent (Scalar Int32 label) where
+  boxSize _ = 4
+  boxBuilder (Scalar v) = int32BE v
+
+instance IsBoxContent (Scalar Int64 label) where
+  boxSize _ = 8
+  boxBuilder (Scalar v) = int64BE v
+
+instance (KnownNat scalar,Num o) => FromTypeLit (Scalar o label) scalar where
+  fromTypeLit _ = Scalar $ fromIntegral $ natVal (Proxy :: Proxy scalar)
+
+-- * Array fields
+
+type U64Arr label size = ScalarArray label size Word64
+
+u64Arr :: (KnownNat size,KnownSymbol label)
+       => [Word64] -> U64Arr label size
+u64Arr = fromList
+
+type I64Arr label size = ScalarArray label size Int64
+
+i64Arr :: (KnownNat size,KnownSymbol label)
+       => [Int64] -> I64Arr label size
+i64Arr = fromList
+
+type U32Arr label size = ScalarArray label size Word32
+
+u32Arr :: (KnownNat size,KnownSymbol label)
+       => [Word32] -> U32Arr label size
+u32Arr = fromList
+
+type I32Arr label size = ScalarArray label size Int32
+
+i32Arr :: (KnownNat size,KnownSymbol label)
+       => [Int32] -> I32Arr label size
+i32Arr = fromList
+
+type U16Arr label size = ScalarArray label size Word16
+
+u16Arr :: (KnownNat size,KnownSymbol label)
+       => [Word16] -> U16Arr label size
+u16Arr = fromList
+
+type I16Arr label size = ScalarArray label size Int16
+
+i16Arr :: (KnownNat size,KnownSymbol label)
+       => [Int16] -> I16Arr label size
+i16Arr = fromList
+
+type U8Arr label size = ScalarArray label size Word8
+
+u8Arr :: (KnownNat size,KnownSymbol label)
+      => [Word8] -> U8Arr label size
+u8Arr = fromList
+
+type I8Arr label size = ScalarArray label size Int8
+
+i8Arr :: (KnownNat size,KnownSymbol label)
+      => [Int8] -> I8Arr label size
+i8Arr = fromList
+
+-- | A box field that is an array of 'Scalar's with a type level label. Use the
+-- type aliases, e.g.
+-- 'U8Arr','I8Arr','U16Arr','I16Arr','U32Arr','I32Arr','U64Arr','I64Arr' from
+-- above. Use the smart constructors, e.g.
+-- 'u8Arr','i8Arr','u16Arr','i16Arr','u32Arr','i32Arr','u64Arr','i64Arr' .
+newtype ScalarArray (label :: k) (len :: Nat) o where
+        ScalarArray :: Vec.Vector n o -> ScalarArray label n o
+
+instance (Num o,IsBoxContent (Scalar o label),KnownNat (len :: Nat)) => IsBoxContent (ScalarArray label len o) where
+  boxSize (ScalarArray vec) =
+    fromIntegral (Vec.length vec) * boxSize (Scalar 0 :: Scalar o label)
+  boxBuilder (ScalarArray vec) =
+    Vec.foldl' mappend
+               mempty
+               (Vec.map (boxBuilder . mkScalar) vec)
+    where mkScalar :: o -> Scalar o label
+          mkScalar = Scalar
+
+-- | Internal function
+fromList :: forall label n o.
+            (KnownSymbol label,KnownNat n)
+         => [o] -> ScalarArray label n o
+fromList l =
+  ScalarArray $
+  case Vec.fromList l of
+    Nothing ->
+      error $
+      printf "Invalid number of array elements for array %s. Got length: %d elments, expected %d."
+             (show (symbolVal (Proxy :: Proxy label)))
+             (length l)
+             (natVal (Proxy :: Proxy n))
+    Just v -> v
+
+-- * Constant fields
+
+-- | Wrapper around a field, e.g. a 'Scalar' or 'ScalarArray', with a type level
+-- value. The wrapped content must implement 'FromTypeLit'.
+data Constant o v where
+        Constant :: Constant o v
+
+instance (IsBoxContent o,FromTypeLit o v) => IsBoxContent (Constant o v) where
+  boxSize = boxSize . fromTypeLit
+  boxBuilder = boxBuilder . fromTypeLit
+
+-- * Template Fields
+
+-- | Fields with default values that can be overriden with custom value. Like
+-- 'Constant' this is a wrapper around a field, e.g. a 'Scalar' or
+-- 'ScalarArray', with a type level default value. The wrapped content must
+-- implement 'FromTypeLit'.
+data Template o v where
+        Default :: Template o v
+        Custom :: o -> Template o v
+
+instance (IsBoxContent o,FromTypeLit o v) => IsBoxContent (Template o v) where
+  boxSize = boxSize . fromTypeLit
+  boxBuilder d@Default = boxBuilder $ fromTypeLit d
+  boxBuilder (Custom o) = boxBuilder o
+
+-- * Conversion from type-level numbers and lists to values
+
+-- | Types that can be constructed from type level value representations.
+class FromTypeLit o v  where
+  fromTypeLit :: proxy o v -> o
+
+instance (SingI arr,Num o,SingKind [Nat],KnownNat len,len ~ Length arr) => FromTypeLit (ScalarArray label len o) (arr :: [Nat]) where
+  fromTypeLit _ =
+    let s = sing :: Sing arr
+        vs :: [Integer]
+        vs = fromSing s
+        vs' :: [o]
+        vs' = fromIntegral <$> vs
+    in ScalarArray (fromJust (Vec.fromList vs'))
+
+-- * Box concatenation
+
+-- | Box content composition
+data Extend a b =
+  Extend a
+         b
+
+-- | An operator for 'Extend'.
+type a :+ b = Extend a b
+
+-- | An operator for 'Extend'.
+(<+>) :: a -> b -> Extend a b
+(<+>) = Extend
+
+instance (IsBoxContent p,IsBoxContent c) => IsBoxContent (Extend p c) where
+  boxSize (Extend p c) = boxSize p + boxSize c
+  boxBuilder (Extend p c) = boxBuilder p <> boxBuilder c
diff --git a/src/Data/ByteString/IsoBaseFileFormat/Boxes/FileType.hs b/src/Data/ByteString/IsoBaseFileFormat/Boxes/FileType.hs
--- a/src/Data/ByteString/IsoBaseFileFormat/Boxes/FileType.hs
+++ b/src/Data/ByteString/IsoBaseFileFormat/Boxes/FileType.hs
@@ -3,14 +3,17 @@
 import Data.ByteString.IsoBaseFileFormat.Boxes.Box
 
 -- | File Type Box
-type FileTypeBox = Box "ftyp"
 
-instance BoxRules "ftyp"
+instance BoxRules FileType
 
+instance IsBoxType' FileType where
+  type BoxContent FileType = FileType
+  toBoxType' _ = StdType "ftyp"
+
 -- | Create a 'FileTypeBox' from a major brand, a minor version and a list of
 -- compatible brands
-fileTypeBox :: FileType -> FileTypeBox
-fileTypeBox = box
+fileTypeBox :: FileType -> Box' FileType
+fileTypeBox = closedBox
 
 -- | Contents of a 'ftyp' box are some 'FourCc' /brands/ and a version.
 data FileType =
@@ -19,6 +22,7 @@
            ,compatibleBrands :: [FourCc]}
 
 instance IsBoxContent FileType where
-  boxSize (FileType maj ver comps) = boxSize maj + 4 + sum (boxSize <$> comps)
+  boxSize (FileType maj _ver comps) =
+    boxSize maj + 4 + sum (boxSize <$> comps)
   boxBuilder (FileType maj ver comps) =
     boxBuilder maj <> word32BE ver <> mconcat (boxBuilder <$> comps)
diff --git a/src/Data/ByteString/IsoBaseFileFormat/Boxes/FullBox.hs b/src/Data/ByteString/IsoBaseFileFormat/Boxes/FullBox.hs
new file mode 100644
--- /dev/null
+++ b/src/Data/ByteString/IsoBaseFileFormat/Boxes/FullBox.hs
@@ -0,0 +1,99 @@
+-- | Full Boxes
+module Data.ByteString.IsoBaseFileFormat.Boxes.FullBox
+       (FullBox(), fullBox, closedFullBox, BoxVersion, BoxFlags(..), Versioned(..)) where
+
+import Data.ByteString.IsoBaseFileFormat.Boxes.Box
+import Data.ByteString.IsoBaseFileFormat.Boxes.BoxFields
+
+-- | A 'FullBox' contains an extra version and a flags field. In this
+-- implementation it is wrapped around the rest of the box content. This
+-- enforces that the 'FullBox' header fields are always at the beginning - at
+-- least as long as this module hides the 'FullBox' constructor ;)
+data FullBox version t where
+        FullBox ::
+          BoxVersion version -> BoxFlags 24 -> t -> FullBox version t
+
+instance (KnownNat version,IsBoxContent t) => IsBoxContent (FullBox version t) where
+  boxSize (FullBox _ f c) = 1 + boxSize f + boxSize c
+  boxBuilder (FullBox v f c) = boxBuilder v <> boxBuilder f <> boxBuilder c
+
+-- | Create a 'FullBox' from a 'BoxVersion' and 'BoxFlags'
+fullBox
+  :: (IsBoxType' t,ValidBoxes t ts,BoxContent t ~ FullBox version c)
+  => BoxVersion version -> BoxFlags 24 -> c -> Boxes ts -> Box' t
+fullBox version fs cnt = Box' (FullBox version fs cnt)
+
+-- | Create a 'FullBox' from a 'BoxVersion' and 'BoxFlags' without nested boxes.
+closedFullBox
+  :: (IsBoxType' t,ValidBoxes t '[],BoxContent t ~ FullBox version c)
+  => BoxVersion version -> BoxFlags 24 -> c -> Box' t
+closedFullBox version fs cnt = closedBox (FullBox version fs cnt)
+
+-- | The box version (in a 'FullBox') is a single byte
+type BoxVersion v = Template (U8 "fullbox-version") v
+
+-- | Two alternative representations based on a /version/ index.
+--   Use this for box content that can be either 32 or 64 bit.
+data Versioned v0 v1 (version :: Nat) where
+  V0 :: IsBoxContent v0 => v0 -> Versioned v0 v1 0
+  V1 :: IsBoxContent v1 => v1 -> Versioned v0 v1 1
+
+instance IsBoxContent (Versioned v0 v1 version) where
+  boxSize (V0 c) = boxSize c
+  boxSize (V1 c) = boxSize c
+  boxBuilder (V0 c) = boxBuilder c
+  boxBuilder (V1 c) = boxBuilder c
+
+-- | In addition to a 'BoxVersion' there can be 24 bits for custom flags etc in
+-- a 'FullBox'.
+newtype BoxFlags bits =
+  BoxFlags Integer
+  deriving (Eq,Show,Num)
+
+-- | Internal function that creates a bit mask with all bits in a 'BoxFlags' set
+-- to 1.
+boxFlagBitMask :: KnownNat bits
+               => BoxFlags bits -> Integer
+boxFlagBitMask px = 2 ^ natVal px - 1
+
+-- | Internal function that masks-out all bits higher than 'bits'.
+cropBits :: KnownNat bits
+         => BoxFlags bits -> BoxFlags bits
+cropBits f@(BoxFlags b) = BoxFlags (b .&. boxFlagBitMask f)
+
+-- | Get the number of bytes required to store a number of bits.
+instance KnownNat bits => IsBoxContent (BoxFlags bits) where
+  boxSize f =
+    let minBytes = fromInteger $ natVal f `div` 8
+        modBytes = fromInteger $ natVal f `mod` 8
+    in BoxSize $ minBytes + signum modBytes
+  boxBuilder f@(BoxFlags b) =
+    let bytes =
+          let (BoxSize bytes') = boxSize f
+          in fromIntegral bytes'
+        wordSeq n
+          | n <= bytes =
+            word8 (fromIntegral (shiftR b ((bytes - n) * 8) .&. 255)) <>
+            wordSeq (n + 1)
+          | otherwise = mempty
+    in wordSeq 1
+
+instance KnownNat bits => Bits (BoxFlags bits) where
+  (.&.) (BoxFlags l) (BoxFlags r) = cropBits $ BoxFlags $ l .&. r
+  (.|.) (BoxFlags l) (BoxFlags r) = cropBits $ BoxFlags $ l .&. r
+  xor (BoxFlags l) (BoxFlags r) = cropBits $ BoxFlags $ xor l r
+  complement (BoxFlags x) = cropBits $ BoxFlags $ complement x
+  shift (BoxFlags x) = cropBits . BoxFlags . shift x
+  rotateL = error "TODO rotateL"
+  rotateR = error "TODO rotateR"
+  bitSize = fromInteger . natVal
+  bitSizeMaybe = Just . fromInteger . natVal
+  isSigned _ = False
+  testBit f n =
+    let (BoxFlags b) = cropBits f
+    in testBit b n
+  bit = cropBits . BoxFlags . bit
+  popCount f =
+    let (BoxFlags b) = cropBits f
+    in popCount b
+  zeroBits = BoxFlags 0
diff --git a/src/Data/ByteString/IsoBaseFileFormat/Boxes/MediaData.hs b/src/Data/ByteString/IsoBaseFileFormat/Boxes/MediaData.hs
--- a/src/Data/ByteString/IsoBaseFileFormat/Boxes/MediaData.hs
+++ b/src/Data/ByteString/IsoBaseFileFormat/Boxes/MediaData.hs
@@ -4,18 +4,15 @@
 import qualified Data.ByteString as B
 
 -- | Media data box
-type MediaDataBox = Box "mdat"
-
-instance BoxRules "mdat"
+data MediaData
 
--- | Create a 'MediaDataBox' from a strict 'ByteString'
-mediaDataBox :: MediaData -> MediaDataBox
-mediaDataBox = box
+instance BoxRules MediaData
 
--- | Contents of a 'mdat' box are just bytes from a strict 'ByteString'
-newtype MediaData =
-  MediaData B.ByteString
+instance IsBoxType' MediaData where
+  -- | Contents of a 'mdat' box are just bytes from a strict 'ByteString'
+  type BoxContent MediaData = B.ByteString
+  toBoxType' _ = StdType "mdat"
 
-instance IsBoxContent MediaData where
-  boxSize (MediaData bs) = fromIntegral $ B.length bs
-  boxBuilder (MediaData bs) = byteString bs
+-- | Create a 'MediaDataBox' from a strict 'ByteString'
+mediaDataBox :: B.ByteString -> Box' MediaData
+mediaDataBox = closedBox
diff --git a/src/Data/ByteString/IsoBaseFileFormat/Boxes/MovieBox.hs b/src/Data/ByteString/IsoBaseFileFormat/Boxes/MovieBox.hs
--- a/src/Data/ByteString/IsoBaseFileFormat/Boxes/MovieBox.hs
+++ b/src/Data/ByteString/IsoBaseFileFormat/Boxes/MovieBox.hs
@@ -1,32 +1,156 @@
+{-# LANGUAGE UndecidableInstances #-}
+
 -- | Meta data for a presentation of a /movie/.
 module Data.ByteString.IsoBaseFileFormat.Boxes.MovieBox where
 
 import Data.ByteString.IsoBaseFileFormat.Boxes.Box
--- Uncomment this to see what the custom compiler errors look like:
+import Data.ByteString.IsoBaseFileFormat.Boxes.BoxFields
+import Data.ByteString.IsoBaseFileFormat.Boxes.FullBox
+
+-- | Compose a set of boxes into a 'MovieBox'
 --
-import Data.ByteString.IsoBaseFileFormat.Boxes.Skip
+-- Example:
+--
+-- >  xxx :: Box' Movie
+-- >  xxx = movieBox $
+-- >         Nested (movieHeaderBox (...))
+-- >         :. (trackBox $
+-- >              Nested (trackHeaderBox (TrackHeader ...))
+-- >              :. trackReferenceBox (TrackReference ...)
+-- >              :. trackGroupingIndication (TrackGroupingInd ...))
+--
+movieBox :: ValidBoxes (Movie version) ts
+         => Boxes ts -> Box' (Movie version)
+movieBox = containerBox
 
--- xxx :: Box "moov"
--- xxx = movieBox
---         (movieBoxContainer :- skipBox (Skip 100))
--- | The metadata for a presentation, a single 'MovieBox' which occurs only once
+-- | The metadata for a presentation, a single 'Movie' which occurs only once
 -- and top-level. It is pretty empty on it's own, but it contains nested boxes
 -- with all the relevant meta data.
-type MovieBox = Box "moov"
+data Movie (version :: Nat)
 
-instance BoxRules "moov" where
-  type RequiredNestedBoxes "moov" = '["mvhd", "trak"]
+instance IsBoxType' (Movie version) where
+  toBoxType' _ = StdType "moov"
 
--- | A movie parent box. Combine with the nested 'Boxes' using 'boxes' or '(^-)'
---
--- Example:
--- >  xxx :: Box "moov"
--- >  xxx = movieBox
--- >         ^- Nested (movieHeaderBox (MovieHeader ...))
--- >                   :- (trackBox
--- >                       ^- Nested (trackHeaderBox (TrackHeader ...))
--- >                                 :- trackReferenceBox (TrackReference ...)
--- >                                 :- trackGroupingIndication (TrackGroupingInd ...))
---
-movieBox :: ParentBox "moov"
-movieBox = emptyParentBox
+instance BoxRules (Movie version) where
+  type RequiredNestedBoxes (Movie version) = '[MovieHeader version]
+
+-- * @mvhd@ Box
+
+-- | Construct a 'MovieHeader' box.
+movieHeaderBox
+  :: KnownNat version
+  => MovieHeader version -> Box' (MovieHeader version)
+movieHeaderBox = closedFullBox Default 0
+
+-- | Movie meta data, indexed by a version.
+data MovieHeader (version :: Nat) where
+        MovieHeader ::
+            KnownNat version =>
+               Versioned MovieHeaderTimesV0
+                         MovieHeaderTimesV1
+                         version
+            :+ Template (I32 "rate") 0x00010000
+            :+ Template (I16 "volume") 0x0100
+            :+ Constant (I16 "reserved") 0
+            :+ Constant (I32Arr "reserved" 2) '[0,0]
+            :+ Constant (I32Arr "matrix" 9)
+                        '[65536, 0, 0, 0, 65536, 0, 0, 0, 1073741824]
+            :+ Template (U32Arr "pre_defined" 6) '[0,0,0,0,0,0]
+            :+ U32 "next_track_ID"
+            -> MovieHeader version
+
+-- | Time and timing information about a movie (32bit version).
+type MovieHeaderTimesV0 = MovieHeaderTimes (Scalar Word32)
+
+-- | Time and timing information about a movie (64bit version).
+type MovieHeaderTimesV1 = MovieHeaderTimes (Scalar Word64)
+
+-- | Time and timing information about a movie.
+type MovieHeaderTimes uint =
+      uint "creation_time"
+   :+ uint "modification_time"
+   :+ U32 "timescale"
+   :+ uint "duration"
+
+instance IsBoxContent (MovieHeader version) where
+  boxSize (MovieHeader c) = boxSize c
+  boxBuilder (MovieHeader c) = boxBuilder c
+
+instance KnownNat version => IsBoxType' (MovieHeader version) where
+  toBoxType' _ = StdType "mvhd"
+  type BoxContent (MovieHeader version) = FullBox version (MovieHeader version)
+
+instance BoxRules (MovieHeader version) where
+  type IsTopLevelBox (MovieHeader version) = 'False
+  type GetCardinality (MovieHeader version) any = 'ExactlyOnce
+  type RestrictedTo (MovieHeader version) = 'Just '[Movie version]
+
+-- * @trak@ Box
+
+-- | Compose a 'Track' box from the given boxes.
+track :: ValidBoxes (Track version) ts
+      => Boxes ts -> Box' (Track version)
+track = containerBox
+
+-- | Container box for tracks.
+data Track (version :: Nat)
+
+instance IsBoxType' (Track version) where
+  toBoxType' _ = StdType "trak"
+
+instance BoxRules (Track version) where
+  type IsTopLevelBox (Track version) = 'False
+  type GetCardinality (Track version) any = 'ExactlyOnce
+  type RestrictedTo (Track version) = 'Just '[Movie version]
+
+-- * @tkhd@ Box
+
+
+-- | Create a 'TrackHeader' box.
+trackHeader
+  :: KnownNat version => TrackHeader version -> Box' (TrackHeader version)
+trackHeader = closedFullBox Default 0
+
+-- | Track meta data, indexed by a version.
+data TrackHeader (version :: Nat) where
+        TrackHeader ::
+               Versioned TrackHeaderTimesV0
+                         TrackHeaderTimesV1
+                         version
+            :+ Constant (I32Arr "reserved" 2) '[0, 0]
+            :+ Template (I16 "layer") 0
+            :+ Template (I16 "alternate_group") 0
+            :+ Template (I16 "volume") 256
+            :+ Constant (I16 "reserved") 0
+            :+ Template (I32Arr "matrix" 9)
+                        '[65536, 0, 0, 0, 65536, 0, 0, 0, 1073741824]
+            :+ I32 "width"
+            :+ I32 "height"
+            -> TrackHeader version
+
+-- | Time and timing information about a track (32bit version).
+type TrackHeaderTimesV0 = TrackHeaderTimes (Scalar Word32)
+
+-- | Time and timing information about a track (64bit version).
+type TrackHeaderTimesV1 = TrackHeaderTimes (Scalar Word64)
+
+-- | Time and timing information about a track.
+type TrackHeaderTimes uint =
+      uint "creation_time"
+   :+ uint "modification_time"
+   :+ U32 "track_ID"
+   :+ Constant (U32 "reserved") 0
+   :+ uint "duration"
+
+instance IsBoxContent (TrackHeader version) where
+  boxSize (TrackHeader c) = boxSize c
+  boxBuilder (TrackHeader c) = boxBuilder c
+
+instance KnownNat version => IsBoxType' (TrackHeader version) where
+  type BoxContent (TrackHeader version) = FullBox version (TrackHeader version)
+  toBoxType' _ = StdType "tkhd"
+
+instance BoxRules (TrackHeader version) where
+  type IsTopLevelBox (TrackHeader version) = 'False
+  type GetCardinality (TrackHeader version) any = 'ExactlyOnce
+  type RestrictedTo (TrackHeader version) = 'Just '[Track version]
diff --git a/src/Data/ByteString/IsoBaseFileFormat/Boxes/ProgressiveDownloadInformation.hs b/src/Data/ByteString/IsoBaseFileFormat/Boxes/ProgressiveDownloadInformation.hs
--- a/src/Data/ByteString/IsoBaseFileFormat/Boxes/ProgressiveDownloadInformation.hs
+++ b/src/Data/ByteString/IsoBaseFileFormat/Boxes/ProgressiveDownloadInformation.hs
@@ -2,24 +2,23 @@
        where
 
 import Data.ByteString.IsoBaseFileFormat.Boxes.Box
+import Data.ByteString.IsoBaseFileFormat.Boxes.BoxFields
+import Data.ByteString.IsoBaseFileFormat.Boxes.FullBox
 
 -- | A Box with progressive download information
-type ProgressiveDownloadInformationBox = Box "pdin"
+data ProgressiveDownload
 
-instance BoxRules "pdin"
+instance BoxRules ProgressiveDownload
 
--- | Create a 'pdin' box
-progressiveDownloadInformationBox
-  :: ProgressiveDownloadInformation -> ProgressiveDownloadInformationBox
-progressiveDownloadInformationBox = fullBox (BoxVersion 0) zeroBits
+instance IsBoxType' ProgressiveDownload where
+  type BoxContent ProgressiveDownload = FullBox 0 ProgressiveDownloadContent
+  toBoxType' _ = StdType "pdin"
 
 -- | Information for progressive media data download/playback encompasses the
 -- delay for initial playback and expected download bit rate.
-data ProgressiveDownloadInformation =
-  ProgressiveDownloadInformation {pdinRate :: Word32 -- ^ Effective download bitrate in byte/sec
-                                 ,pdinDelay :: Word32 -- ^ Delay in milli seconds
-                                 }
+type ProgressiveDownloadContent = U32 "rate" :+ U32 "delay"
 
-instance IsBoxContent ProgressiveDownloadInformation where
-  boxSize _ = 8
-  boxBuilder pdin = word32BE (pdinRate pdin) <> word32BE (pdinDelay pdin)
+-- | Construct a @pdin@ box.
+pdinBox
+  :: ProgressiveDownloadContent -> Box' ProgressiveDownload
+pdinBox = closedFullBox Default 0
diff --git a/src/Data/ByteString/IsoBaseFileFormat/Boxes/Skip.hs b/src/Data/ByteString/IsoBaseFileFormat/Boxes/Skip.hs
--- a/src/Data/ByteString/IsoBaseFileFormat/Boxes/Skip.hs
+++ b/src/Data/ByteString/IsoBaseFileFormat/Boxes/Skip.hs
@@ -1,20 +1,21 @@
+-- | A filler box with a specific size.
 module Data.ByteString.IsoBaseFileFormat.Boxes.Skip where
 
 import Data.ByteString.IsoBaseFileFormat.Boxes.Box
 
--- | A filler box, the contents are skipped
-type SkipBox = Box "skip"
+-- | Contents of a 'skip' box are just any number of filler bytes.
+newtype Skip = Skip Int
 
-instance BoxRules "skip" where
-  type RestrictedTo "skip" = 'Nothing
+instance IsBoxType' Skip where
+  type BoxContent Skip = Skip
+  toBoxType' _ = StdType "skip"
 
--- | Create a 'SkipBox' with a given size.
-skipBox :: Skip -> SkipBox
-skipBox = box
+instance BoxRules Skip where
+  type RestrictedTo Skip = 'Nothing
 
--- | Contents of a 'skip' box are just any number of filler bytes.
-newtype Skip =
-  Skip Int
+-- | Create a 'Skip' with a given size.
+skipBox :: Skip -> Box' Skip
+skipBox = closedBox
 
 instance IsBoxContent Skip where
   boxSize (Skip bs) = fromIntegral bs
diff --git a/src/Data/ByteString/IsoBaseFileFormat/Builder.hs b/src/Data/ByteString/IsoBaseFileFormat/Builder.hs
--- a/src/Data/ByteString/IsoBaseFileFormat/Builder.hs
+++ b/src/Data/ByteString/IsoBaseFileFormat/Builder.hs
@@ -1,11 +1,20 @@
 -- | This module re-exports all modules needed to build /ISOBMFF/ documents.
 module Data.ByteString.IsoBaseFileFormat.Builder
-  (module Data.ByteString.IsoBaseFileFormat.Builder, module X)
+  (module X)
   where
 
 import Data.ByteString.IsoBaseFileFormat.Boxes.Box as X
+import Data.ByteString.IsoBaseFileFormat.Boxes.BoxFields as X
 import Data.ByteString.IsoBaseFileFormat.Boxes.FileType as X
+import Data.ByteString.IsoBaseFileFormat.Boxes.FullBox as X
 import Data.ByteString.IsoBaseFileFormat.Boxes.MediaData as X
 import Data.ByteString.IsoBaseFileFormat.Boxes.MovieBox as X
 import Data.ByteString.IsoBaseFileFormat.Boxes.ProgressiveDownloadInformation as X
 import Data.ByteString.IsoBaseFileFormat.Boxes.Skip as X
+
+import Data.Int as X
+import Data.Kind as X  (Type, Constraint)
+import Data.Maybe as X
+import Data.Type.Bool as X
+import Data.Type.Equality as X
+import Text.Printf as X
