diff --git a/CHANGELOG b/CHANGELOG
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,3 +1,37 @@
+- 0.8.4.2 (2020-05-09)
+    * Bump `Cabal` lower bound to 1.10
+
+- 0.8.4.1 (2020-05-09)
+    * Bump `semigroups` dependency upper bound to 0.20
+
+- 0.8.4.0 (2017-07-18)
+    * GHC 8.4 support
+    * Bump `containers` to 0.6
+    * Bump `time` to 1.9
+    * Bump `QuickCheck` to 2.11
+
+- 0.8.3.0 (2017-12-26)
+    * Fix input element values for choice forms
+    * Bump `time` to 1.8
+    * Bump `HUnit` to 1.6
+    * Bump `QuickCheck` to 2.10
+
+- 0.8.2.0
+    * Update `QuickCheck` version bound
+    * Updated CHoice type to allow for multiple choices
+
+- 0.8.1.1
+    * Bump `time` dependency to allow up to `time-1.6`
+
+- 0.8.1.0
+    * Add support for a list of files via `<input type="file" multiple />`
+
+- 0.8.0.1
+    * Bump `HUnit` dependency to allow up to `HUnit-1.3`
+
+- 0.8.0.0
+    * Lots of constraint changes due to Applicative => Monad
+
 - 0.7.1.5
     * Bump `time` dependency to allow up to `time-1.5`, for tests as well
     * Bump `QuickCheck` dependency to allow up to `QuickCheck-2.8`
diff --git a/digestive-functors.cabal b/digestive-functors.cabal
--- a/digestive-functors.cabal
+++ b/digestive-functors.cabal
@@ -1,5 +1,5 @@
 Name:     digestive-functors
-Version:  0.7.1.5
+Version:  0.8.4.2
 Synopsis: A practical formlet library
 
 Description:
@@ -39,14 +39,16 @@
 Maintainer:    Jasper Van der Jeugt <m@jaspervdj.be>
 Category:      Web
 Build-type:    Simple
-Cabal-version: >= 1.8
+Cabal-version: >= 1.10
 
 Extra-source-files:
   CHANGELOG
 
 Library
-  Hs-source-dirs: src
-  Ghc-options:    -Wall -fwarn-tabs
+  Default-language:   Haskell2010
+  Hs-source-dirs:     src
+  Ghc-options:        -Wall -fwarn-tabs
+  Default-extensions: CPP
 
   Exposed-modules:
     Text.Digestive
@@ -61,46 +63,57 @@
     Text.Digestive.Form.Internal.Field
 
   Build-depends:
-    base       >= 4       && < 5,
-    bytestring >= 0.9     && < 0.11,
-    containers >= 0.3     && < 0.6,
-    mtl        >= 1.1.0.0 && < 3,
-    old-locale >= 1.0     && < 1.1,
-    text       >= 0.10    && < 1.3,
-    time       >= 1.4     && < 1.6
-  Extensions: CPP
+    base                  >= 4       && < 5,
+    bytestring            >= 0.9     && < 0.11,
+    containers            >= 0.3     && < 0.7,
+    mtl                   >= 1.1.0.0 && < 3,
+    old-locale            >= 1.0     && < 1.1,
+    semigroups            >= 0.16    && < 0.20,
+    text                  >= 0.10    && < 1.3,
+    time                  >= 1.4     && < 1.10
 
 Test-suite digestive-functors-tests
-  Type:           exitcode-stdio-1.0
-  Hs-source-dirs: src tests
-  Main-is:        TestSuite.hs
-  Ghc-options:    -Wall
+  Default-language: Haskell2010
+  Type:             exitcode-stdio-1.0
+  Hs-source-dirs:   src tests
+  Main-is:          TestSuite.hs
+  Ghc-options:      -Wall
 
   Other-modules:
-      Text.Digestive.Field.Tests
-      Text.Digestive.Field.QTests
-      Text.Digestive.Form.Encoding.Tests
-      Text.Digestive.Form.Encoding.QTests
-      Text.Digestive.Form.List.QTests
-      Text.Digestive.Form.QTests
-      Text.Digestive.Tests.Fixtures
-      Text.Digestive.View.Tests
-      Text.Digestive.Types.QTests
+    Text.Digestive.Field.QTests
+    Text.Digestive.Field.Tests
+    Text.Digestive.Form
+    Text.Digestive.Form.Encoding
+    Text.Digestive.Form.Encoding.QTests
+    Text.Digestive.Form.Encoding.Tests
+    Text.Digestive.Form.Internal
+    Text.Digestive.Form.Internal.Field
+    Text.Digestive.Form.List
+    Text.Digestive.Form.List.QTests
+    Text.Digestive.Form.QTests
+    Text.Digestive.Ref
+    Text.Digestive.Tests.Fixtures
+    Text.Digestive.Types
+    Text.Digestive.Types.QTests
+    Text.Digestive.Util
+    Text.Digestive.View
+    Text.Digestive.View.Tests
 
   Build-depends:
-    HUnit                      >= 1.2 && < 1.3,
-    QuickCheck                 >= 2.5 && < 2.9,
+    HUnit                      >= 1.2 && < 1.7,
+    QuickCheck                 >= 2.5 && < 2.12,
     test-framework             >= 0.4 && < 0.9,
     test-framework-hunit       >= 0.3 && < 0.4,
     test-framework-quickcheck2 >= 0.3 && < 0.4,
     -- Copied from regular dependencies:
-    base       >= 4       && < 5,
-    bytestring >= 0.9     && < 0.11,
-    containers >= 0.3     && < 0.6,
-    mtl        >= 1.1.0.0 && < 3,
-    old-locale >= 1.0     && < 1.1,
-    text       >= 0.10    && < 1.3,
-    time       >= 1.4     && < 1.6
+    base                  >= 4       && < 5,
+    bytestring            >= 0.9     && < 0.11,
+    containers            >= 0.3     && < 0.7,
+    mtl                   >= 1.1.0.0 && < 3,
+    old-locale            >= 1.0     && < 1.1,
+    semigroups            >= 0.16    && < 0.19,
+    text                  >= 0.10    && < 1.3,
+    time                  >= 1.4     && < 1.10
 
 Source-repository head
   Type:     git
diff --git a/src/Text/Digestive/Form.hs b/src/Text/Digestive/Form.hs
--- a/src/Text/Digestive/Form.hs
+++ b/src/Text/Digestive/Form.hs
@@ -1,9 +1,9 @@
 --------------------------------------------------------------------------------
+{-# LANGUAGE CPP                       #-}
 {-# LANGUAGE ExistentialQuantification #-}
 {-# LANGUAGE GADTs                     #-}
 {-# LANGUAGE OverloadedStrings         #-}
 {-# LANGUAGE Rank2Types                #-}
-{-# LANGUAGE CPP                       #-}
 -- | End-user interface - provides the main functionality for
 -- form creation and validation. For an interface for front-end
 -- implementation, see "View".
@@ -21,12 +21,21 @@
     , choice'
     , choiceWith
     , choiceWith'
+    , choiceMultiple
+    , choiceMultiple'
+    , choiceWithMultiple
+    , choiceWithMultiple'
     , groupedChoice
     , groupedChoice'
     , groupedChoiceWith
     , groupedChoiceWith'
+    , groupedChoiceMultiple
+    , groupedChoiceMultiple'
+    , groupedChoiceWithMultiple
+    , groupedChoiceWithMultiple'
     , bool
     , file
+    , fileMultiple
 
       -- * Optional forms
     , optionalText
@@ -64,13 +73,15 @@
 import           Control.Applicative
 import           Control.Monad                      (liftM, liftM2)
 import           Data.List                          (findIndex)
-import           Data.Maybe                         (fromMaybe)
+import           Data.Maybe                         (fromMaybe, listToMaybe, catMaybes)
+import           Data.Monoid                        (Monoid)
 import           Data.Text                          (Text)
 import qualified Data.Text                          as T
 import           Data.Time
 #if !MIN_VERSION_time(1,5,0)
 import           System.Locale
 #endif
+import           Prelude
 
 
 --------------------------------------------------------------------------------
@@ -89,33 +100,33 @@
 
 --------------------------------------------------------------------------------
 -- | Returns a 'Formlet' which may optionally take a default text
-text :: Formlet v m Text
+text :: (Monad m, Monoid v) => Formlet v m Text
 text def = Pure $ Text $ fromMaybe "" def
 
 
 --------------------------------------------------------------------------------
 -- | Identical to "text" but takes a String
-string :: Monad m => Formlet v m String
+string :: (Monad m, Monoid v) => Formlet v m String
 string = fmap T.unpack . text . fmap T.pack
 
 
 --------------------------------------------------------------------------------
 -- | Returns a 'Formlet' for a parseable and serializable value type
-stringRead :: (Monad m, Read a, Show a) => v -> Formlet v m a
+stringRead :: (Monad m, Monoid v, Read a, Show a) => v -> Formlet v m a
 stringRead err = transform (readTransform err) . string . fmap show
 
 
 --------------------------------------------------------------------------------
--- | Returns a 'Formlet' for a value restricted to
+-- | Returns a 'Formlet' for a value restricted to a single value from
 -- the provided list of value-message tuples
-choice :: (Eq a, Monad m) => [(a, v)] -> Formlet v m a
+choice :: (Eq a, Monad m, Monoid v) => [(a, v)] -> Formlet v m a
 choice items def = choiceWith (zip makeRefs items) def
 
 
 --------------------------------------------------------------------------------
 -- | Sometimes there is no good 'Eq' instance for 'choice'. In this case, you
 -- can use this function, which takes an index in the list as default.
-choice' :: Monad m => [(a, v)] -> Maybe Int -> Form v m a
+choice' :: (Monad m, Monoid v) => [(a, v)] -> Maybe Int -> Form v m a
 choice' items def = choiceWith' (zip makeRefs items) def
 
 
@@ -124,7 +135,8 @@
 -- resulting HTML instead of the default @[0 ..]@. This fixes some race
 -- conditions that might otherwise appear, e.g. if new choice items are added to
 -- some database while a user views and submits the form...
-choiceWith :: (Eq a, Monad m) => [(Text, (a, v))] -> Formlet v m a
+choiceWith
+    :: (Eq a, Monad m, Monoid v) => [(Text, (a, v))] -> Formlet v m a
 choiceWith items def = choiceWith' items def'
   where
     def' = def >>= (\d -> findIndex ((== d) . fst . snd) items)
@@ -132,15 +144,55 @@
 
 --------------------------------------------------------------------------------
 -- | A version of 'choiceWith' for when there is no good 'Eq' instance.
-choiceWith' :: Monad m => [(Text, (a, v))] -> Maybe Int -> Form v m a
-choiceWith' items def = fmap fst $ Pure $ Choice [("", items)] def'
+choiceWith'
+    :: (Monad m, Monoid v) => [(Text, (a, v))] -> Maybe Int -> Form v m a
+choiceWith' []    _   = error "choice expects a list with at least one item in it"
+choiceWith' items def = fromMaybe defaultItem . listToMaybe . map fst <$> (Pure $ Choice [("", items)] [def'])
   where
+    defaultItem = fst $ snd $ items !! def'
     def' = fromMaybe 0 def
 
 
 --------------------------------------------------------------------------------
--- | Returns a 'Formlet' for named groups of choices.
-groupedChoice :: (Eq a, Monad m) => [(Text, [(a, v)])] -> Formlet v m a
+-- | Returns a 'Formlet' for a value restricted to multiple values from
+-- the provided list of value-message tuples.  Intended for use with the
+-- @multiple@ attribute for select elements.  Allows for an empty result.
+choiceMultiple :: (Eq a, Monad m, Monoid v) => [(a, v)] -> Formlet v m [a]
+choiceMultiple items def = choiceWithMultiple (zip makeRefs items) def
+
+
+--------------------------------------------------------------------------------
+-- | Sometimes there is no good 'Eq' instance for 'choice'. In this case, you
+-- can use this function, which takes an index in the list as default.
+choiceMultiple' :: (Monad m, Monoid v) => [(a, v)] -> Maybe [Int] -> Form v m [a]
+choiceMultiple' items def = choiceWithMultiple' (zip makeRefs items) def
+
+
+--------------------------------------------------------------------------------
+-- | Allows you to assign your own values: these values will be used in the
+-- resulting HTML instead of the default @[0 ..]@. This fixes some race
+-- conditions that might otherwise appear, e.g. if new choice items are added to
+-- some database while a user views and submits the form...
+choiceWithMultiple
+    :: (Eq a, Monad m, Monoid v) => [(Text, (a, v))] -> Formlet v m [a]
+choiceWithMultiple items def = choiceWithMultiple' items def'
+  where
+    def' = def >>= Just . catMaybes . map (\d -> findIndex ((== d) . fst . snd) items)
+
+
+--------------------------------------------------------------------------------
+-- | A version of 'choiceWithMultiple' for when there is no good 'Eq' instance.
+choiceWithMultiple'
+    :: (Monad m, Monoid v) => [(Text, (a, v))] -> Maybe [Int] -> Form v m [a]
+choiceWithMultiple' items def = map fst <$> (Pure $ Choice [("", items)] def')
+  where
+    def' = fromMaybe [] def
+
+
+--------------------------------------------------------------------------------
+-- | Returns a 'Formlet' for a single value from named groups of choices.
+groupedChoice
+    :: (Eq a, Monad m, Monoid v) => [(Text, [(a, v)])] -> Formlet v m a
 groupedChoice items def =
     groupedChoiceWith (mkGroupedRefs items makeRefs) def
 
@@ -148,11 +200,13 @@
 --------------------------------------------------------------------------------
 -- | Sometimes there is no good 'Eq' instance for 'choice'. In this case, you
 -- can use this function, which takes an index in the list as default.
-groupedChoice' :: Monad m => [(Text, [(a, v)])] -> Maybe Int -> Form v m a
+groupedChoice'
+    :: (Monad m, Monoid v) => [(Text, [(a, v)])] -> Maybe Int -> Form v m a
 groupedChoice' items def =
     groupedChoiceWith' (mkGroupedRefs items makeRefs) def
 
 
+--------------------------------------------------------------------------------
 mkGroupedRefs :: [(Text, [a])]
               -> [Text]
               -> [(Text, [(Text, a)])]
@@ -168,7 +222,7 @@
 -- resulting HTML instead of the default @[0 ..]@. This fixes some race
 -- conditions that might otherwise appear, e.g. if new choice items are added to
 -- some database while a user views and submits the form...
-groupedChoiceWith :: (Eq a, Monad m)
+groupedChoiceWith :: (Eq a, Monad m, Monoid v)
                   => [(Text, [(Text, (a, v))])]
                   -> Formlet v m a
 groupedChoiceWith items def = groupedChoiceWith' items def'
@@ -179,34 +233,92 @@
 
 --------------------------------------------------------------------------------
 -- | Low-level support for grouped choice.
-groupedChoiceWith' :: Monad m
+groupedChoiceWith' :: (Monad m, Monoid v)
                    => [(Text, [(Text, (a, v))])]
                    -> Maybe Int
                    -> Form v m a
-groupedChoiceWith' items def = fmap fst $ Pure $ Choice items def'
+groupedChoiceWith' items def =
+  case concatMap snd items of
+    [] -> error "groupedChoice expects a list with at least one item in it"
+    _  -> head . map fst <$> (Pure $ Choice items def')
   where
-    def' = fromMaybe 0 def
+    def' = case def of
+      Just x  -> [x]
+      Nothing -> [0]
 
 
 --------------------------------------------------------------------------------
+-- | Returns a 'Formlet' for multiple values from named groups of choices.
+-- Intended for use with the @multiple@ attribute for select elements that
+-- have optgroups.  Allows for an empty result.
+groupedChoiceMultiple
+    :: (Eq a, Monad m, Monoid v) => [(Text, [(a, v)])] -> Formlet v m [a]
+groupedChoiceMultiple items def =
+    groupedChoiceWithMultiple (mkGroupedRefs items makeRefs) def
+
+
+--------------------------------------------------------------------------------
+-- | Sometimes there is no good 'Eq' instance for 'choice'. In this case, you
+-- can use this function, which takes an index in the list as default.
+groupedChoiceMultiple'
+    :: (Monad m, Monoid v) => [(Text, [(a, v)])] -> Maybe [Int] -> Form v m [a]
+groupedChoiceMultiple' items def =
+    groupedChoiceWithMultiple' (mkGroupedRefs items makeRefs) def
+
+
+--------------------------------------------------------------------------------
+-- | Allows you to assign your own values: these values will be used in the
+-- resulting HTML instead of the default @[0 ..]@. This fixes some race
+-- conditions that might otherwise appear, e.g. if new choice items are added to
+-- some database while a user views and submits the form...
+groupedChoiceWithMultiple :: (Eq a, Monad m, Monoid v)
+                  => [(Text, [(Text, (a, v))])]
+                  -> Formlet v m [a]
+groupedChoiceWithMultiple items def = groupedChoiceWithMultiple' items def'
+  where
+    def' = def >>= Just . catMaybes . map (\d -> findIndex ((== d) . fst . snd) $
+                            concat $ map snd items)
+
+
+--------------------------------------------------------------------------------
+-- | Low-level support for grouped choice.
+groupedChoiceWithMultiple' :: (Monad m, Monoid v)
+                   => [(Text, [(Text, (a, v))])]
+                   -> Maybe [Int]
+                   -> Form v m [a]
+groupedChoiceWithMultiple' items def = map fst <$> (Pure $ Choice items def')
+  where
+    def' = case def of
+      Just x  -> x
+      Nothing -> []
+
+--------------------------------------------------------------------------------
 -- | Returns a 'Formlet' for binary choices
-bool :: Formlet v m Bool
+bool :: (Monad m, Monoid v) => Formlet v m Bool
 bool = Pure . Bool . fromMaybe False
 
 
 --------------------------------------------------------------------------------
 -- | Returns a 'Formlet' for file selection
-file :: Form v m (Maybe FilePath)
-file = Pure File
+file :: (Monad m, Monoid v) => Form v m (Maybe FilePath)
+file = listToMaybe <$> Pure File
 
 
 --------------------------------------------------------------------------------
+-- | Returns a 'Formlet' for multiple file selection.  Intended for use with
+-- the @multiple@ attribute, which allows for multiple files to be uploaded
+-- with a single input element.
+fileMultiple :: (Monad m, Monoid v) => Form v m [FilePath]
+fileMultiple = Pure File
+
+
+--------------------------------------------------------------------------------
 -- | Validate the results of a form with a simple predicate
 --
 -- Example:
 --
 -- > check "Can't be empty" (not . null) (string Nothing)
-check :: Monad m
+check :: (Monad m, Monoid v)
       => v            -- ^ Error message (if fail)
       -> (a -> Bool)  -- ^ Validating predicate
       -> Form v m a   -- ^ Form to validate
@@ -216,7 +328,7 @@
 
 --------------------------------------------------------------------------------
 -- | Version of 'check' which allows monadic validations
-checkM :: Monad m => v -> (a -> m Bool) -> Form v m a -> Form v m a
+checkM :: (Monad m, Monoid v) => v -> (a -> m Bool) -> Form v m a -> Form v m a
 checkM err predicate form = validateM f form
   where
     f x = do
@@ -236,14 +348,14 @@
 -- >
 -- > char :: Monad m => Form m String Char
 -- > char = validate head' (string Nothing)
-validate :: Monad m => (a -> Result v b) -> Form v m a -> Form v m b
+validate :: (Monad m, Monoid v) => (a -> Result v b) -> Form v m a -> Form v m b
 validate = validateM . (return .)
 
 --------------------------------------------------------------------------------
 -- | Same as 'validate', but works with forms of the form:
 --
 -- >  Form v m (Maybe a)
--- 
+--
 -- .
 --
 -- Example: taking the first character of an optional input string
@@ -254,19 +366,22 @@
 -- >
 -- > char :: Monad m => Form m String (Maybe Char)
 -- > char = validateOptional head' (optionalString Nothing)
-validateOptional :: Monad m => (a -> Result v b) -> Form v m (Maybe a) -> Form v m (Maybe b)
+validateOptional
+    :: (Monad m, Monoid v)
+    => (a -> Result v b) -> Form v m (Maybe a) -> Form v m (Maybe b)
 validateOptional f = validate (forOptional f)
 
 --------------------------------------------------------------------------------
 -- | Version of 'validate' which allows monadic validations
-validateM :: Monad m => (a -> m (Result v b)) -> Form v m a -> Form v m b
+validateM
+    :: (Monad m, Monoid v) => (a -> m (Result v b)) -> Form v m a -> Form v m b
 validateM = transform
 
 --------------------------------------------------------------------------------
 -- | Allows for the composition of independent validation functions.
 --
 -- For example, let's validate an even integer between 0 and 100:
--- 
+--
 -- > form :: Monad m => Form Text m FormData
 -- > ... -- some fields
 -- > <*> "smallEvenInteger" .: validate (notEmpty >=> integer >=> even >=> greaterThan 0 >=> lessThanOrEq 100) (text Nothing)
@@ -282,17 +397,17 @@
 --
 -- .
 --
--- This will validate our smallEvenInteger correctly, but there is a problem. 
+-- This will validate our smallEvenInteger correctly, but there is a problem.
 -- If a user enters an odd number greater than 100, only
 --
 -- > "number must be even"
 --
 --
--- will be returned. It would make for a better user experience if 
+-- will be returned. It would make for a better user experience if
 --
 -- > ["number must be even", "number must be less than 100"]
 --
--- was returned instead. This can be accomplished by rewriting our form to be: 
+-- was returned instead. This can be accomplished by rewriting our form to be:
 --
 -- > form :: Monad m => Form [Text] m FormData
 -- > ... -- some fields
@@ -302,7 +417,7 @@
 -- .
 --
 -- If we want to collapse our list of errors into a single 'Text', we can do something like:
--- 
+--
 -- > form :: Monad m => Form Text m FormData
 -- > ... -- some fields
 -- > <*> "smallEvenInteger" .: validate (notEmpty >=> integer >=> commaSeperated . conditions [even, greaterThan 0, lessThanOrEq 100]) (text Nothing)
@@ -319,7 +434,7 @@
                              --
                              -- > conditions [even,  greaterThan 0] -1
                              --
-                             -- is specified to return 
+                             -- is specified to return
                              --
                              -- > Error ["must be even", "must be greater than 0"]
                              --
@@ -348,7 +463,7 @@
 -- | Create a text form with an optional default text which
 -- returns nothing if no optional text was set, and no input
 -- was retrieved.
-optionalText :: Monad m => Maybe Text -> Form v m (Maybe Text)
+optionalText :: (Monad m, Monoid v) => Maybe Text -> Form v m (Maybe Text)
 optionalText def = validate opt (text def)
   where
     opt t
@@ -358,13 +473,13 @@
 
 --------------------------------------------------------------------------------
 -- | Identical to 'optionalText', but uses Strings
-optionalString :: Monad m => Maybe String -> Form v m (Maybe String)
+optionalString :: (Monad m, Monoid v) => Maybe String -> Form v m (Maybe String)
 optionalString = fmap (fmap T.unpack) . optionalText . fmap T.pack
 
 
 --------------------------------------------------------------------------------
 -- | Identical to 'optionalText' for parseable and serializable values.
-optionalStringRead :: (Monad m, Read a, Show a)
+optionalStringRead :: (Monad m, Monoid v, Read a, Show a)
                    => v -> Maybe a -> Form v m (Maybe a)
 optionalStringRead err = transform readTransform' . optionalString . fmap show
   where
@@ -374,17 +489,22 @@
 
 --------------------------------------------------------------------------------
 -- Helper function for attempted parsing, with custom error messages
-readTransform :: (Monad m, Read a) => v -> String -> m (Result v a)
+readTransform :: (Monad m, Monoid v, Read a) => v -> String -> m (Result v a)
 readTransform err = return . maybe (Error err) return . readMaybe
 
 
 --------------------------------------------------------------------------------
--- | Dynamic lists
-listOf :: Monad m
-       => Formlet v m a
-       -> Formlet v m [a]
+-- | Dynamic lists.
+--
+-- The type is a less restrictive version of:
+--
+-- > Formlet a -> Formlet [a]
+listOf :: (Monad m, Monoid v)
+       => (Maybe a -> Form v m b)
+       -> (Maybe [a] -> Form v m [b])
 listOf single def =
     List (fmap single defList) (indicesRef .: listIndices ixs)
+
   where
     ixs = case def of
         Nothing -> [0]
@@ -395,7 +515,7 @@
 
 --------------------------------------------------------------------------------
 -- Manipulatable indices
-listIndices :: Monad m => [Int] -> Form v m [Int]
+listIndices :: (Monad m, Monoid v) => [Int] -> Form v m [Int]
 listIndices = fmap parseIndices . text . Just . unparseIndices
 
 
diff --git a/src/Text/Digestive/Form/Encoding.hs b/src/Text/Digestive/Form/Encoding.hs
--- a/src/Text/Digestive/Form/Encoding.hs
+++ b/src/Text/Digestive/Form/Encoding.hs
@@ -13,6 +13,7 @@
 import           Control.Monad.Identity             (Identity)
 import           Data.Maybe                         (mapMaybe)
 import           Data.Monoid                        (Monoid (..), mconcat)
+import           Data.Semigroup                     (Semigroup (..))
 
 
 --------------------------------------------------------------------------------
@@ -37,12 +38,19 @@
 
 
 --------------------------------------------------------------------------------
+-- Semigroup instance for encoding types: prefer UrlEncoded, but fallback to
+-- MultiPart when needed
+instance Semigroup FormEncType where
+    UrlEncoded <> x = x
+    MultiPart  <> _ = MultiPart
+
+
+--------------------------------------------------------------------------------
 -- Monoid instance for encoding types: prefer UrlEncoded, but fallback to
 -- MultiPart when needed
 instance Monoid FormEncType where
-    mempty               = UrlEncoded
-    mappend UrlEncoded x = x
-    mappend MultiPart  _ = MultiPart
+    mempty  = UrlEncoded
+    mappend = (<>)
 
 
 --------------------------------------------------------------------------------
diff --git a/src/Text/Digestive/Form/Internal.hs b/src/Text/Digestive/Form/Internal.hs
--- a/src/Text/Digestive/Form/Internal.hs
+++ b/src/Text/Digestive/Form/Internal.hs
@@ -97,7 +97,7 @@
 
 
 --------------------------------------------------------------------------------
-instance Monad m => Functor (FormTree t v m) where
+instance (Monad m, Monoid v) => Functor (FormTree t v m) where
     fmap = transform . (return .) . (return .)
 
 
@@ -157,7 +157,7 @@
 
 --------------------------------------------------------------------------------
 -- | Map on the value type
-transform :: Monad m
+transform :: (Monad m, Monoid v)
           => (a -> m (Result v b)) -> FormTree t v m a -> FormTree t v m b
 transform f (Map g x) = Map (\y -> g y `bindResult` f) x
 transform f x         = Map f x
@@ -392,11 +392,11 @@
     Just x'  -> case (f x') of
         Success x'' -> Success (Just x'')
         Error   x'' -> Error x''
-         
 
+
 --------------------------------------------------------------------------------
 -- | Utility: bind for 'Result' inside another monad
-bindResult :: Monad m
+bindResult :: (Monad m, Monoid v)
            => m (Result v a) ->
            (a -> m (Result v b)) ->
            m (Result v b)
@@ -409,7 +409,7 @@
 
 --------------------------------------------------------------------------------
 -- | Debugging purposes
-debugFormPaths :: Monad m => FormTree Identity v m a -> [Path]
+debugFormPaths :: (Monad m, Monoid v) => FormTree Identity v m a -> [Path]
 debugFormPaths (Pure _)       = [[]]
 debugFormPaths (App x y)      = debugFormPaths x ++ debugFormPaths y
 debugFormPaths (Map _ x)      = debugFormPaths x
diff --git a/src/Text/Digestive/Form/Internal/Field.hs b/src/Text/Digestive/Form/Internal/Field.hs
--- a/src/Text/Digestive/Form/Internal/Field.hs
+++ b/src/Text/Digestive/Form/Internal/Field.hs
@@ -14,7 +14,9 @@
 
 --------------------------------------------------------------------------------
 import           Control.Arrow        (second)
-import           Data.Maybe           (fromMaybe, listToMaybe)
+import           Data.Maybe           (listToMaybe, mapMaybe, catMaybes)
+import           Data.Functor         ((<$>))
+import           Data.List            (findIndex)
 import           Data.Text            (Text)
 
 
@@ -27,12 +29,12 @@
 data Field v a where
     Singleton :: a -> Field v a
     Text      :: Text -> Field v Text
-    -- A list of identifier, value, view. Then we have the default index in
-    -- the list. The return value has the actual value as well as the index in
-    -- the list.
-    Choice    :: [(Text, [(Text, (a, v))])] -> Int -> Field v (a, Int)
+    -- A list of (group name, [(identifier, (value, view))]).
+    -- Then we have the default index in the list.
+    -- The return value has the actual value as well as the index in the list.
+    Choice    :: [(Text, [(Text, (a, v))])] -> [Int] -> Field v [(a, Int)]
     Bool      :: Bool -> Field v Bool
-    File      :: Field v (Maybe FilePath)
+    File      :: Field v [FilePath]
 
 
 --------------------------------------------------------------------------------
@@ -59,22 +61,24 @@
 evalField _    _                 (Singleton x) = x
 evalField _    (TextInput x : _) (Text _)      = x
 evalField _    _                 (Text x)      = x
-evalField _    (TextInput x : _) (Choice ls' y) =
-  let ls = concat (map snd ls') in
-    fromMaybe (fst (snd (ls !! y)), y) $ do
-        -- Expects input in the form of "foo.bar.2". This is not needed for
-        -- <select> fields, but we need it for labels for radio buttons.
-        t      <- listToMaybe $ reverse $ toPath x
-        (c, i) <- lookupIdx t ls
-        return (fst c, i)
-evalField _    _                 (Choice ls' x) =
-  let ls = concat (map snd ls') in
-    (fst (snd (ls !! x)), x)
+evalField _ ts@(TextInput _ : _) (Choice ls _) =
+  let ls' = concat (map snd ls) in
+    catMaybes $
+      map (\(TextInput x) ->
+        (\i -> (fst $ snd $ ls' !! i, i)) <$> findIndex (isSelectedChoice x . fst) ls') ts
+evalField Get  _                 (Choice ls x) =
+  -- this populates the default values when displaying a view
+  let ls' = concat (map snd ls) in
+    map (\i -> (fst $ snd $ ls' !! i, i)) x
+evalField Post _                 (Choice _  _) = []
 evalField Get  _                 (Bool x)      = x
 evalField Post (TextInput x : _) (Bool _)      = x == "on"
 evalField Post _                 (Bool _)      = False
-evalField Post (FileInput x : _) File          = Just x
-evalField _    _                 File          = Nothing
+evalField Post xs                File          = mapMaybe maybeFile xs
+  where
+    maybeFile (FileInput x) = Just x
+    maybeFile _             = Nothing
+evalField _    _                 File          = []
 
 
 --------------------------------------------------------------------------------
@@ -89,12 +93,15 @@
 
 
 --------------------------------------------------------------------------------
--- | Retrieve the value and position of the value referenced to by the given
--- key in an association list, if the key is in the list.
-lookupIdx :: Eq k => k -> [(k, v)] -> Maybe (v, Int)
-lookupIdx key = go 0
-  where
-    go _  []        = Nothing
-    go !i ((k, v) : xs)
-        | key == k  = Just (v, i)
-        | otherwise = go (i + 1) xs
+-- | Determines if the choiceVal is equal to the selectedVal.  Works with older
+-- versions that submit "foo.bar.2" rather than "2" for Choice fields.
+-- > isSelectedChoice "0" "0" == True
+-- > isSelectedChoice "foo.bar.0" "0" == True
+-- > isSelectedChoice "foo.bar.10" "0" == False
+isSelectedChoice :: Text -> Text -> Bool
+isSelectedChoice selectedVal choiceVal
+  | selectedVal == choiceVal = True
+  | otherwise =
+    case listToMaybe (reverse $ toPath selectedVal) of
+      Just x -> x == choiceVal
+      _      -> False
diff --git a/src/Text/Digestive/Types.hs b/src/Text/Digestive/Types.hs
--- a/src/Text/Digestive/Types.hs
+++ b/src/Text/Digestive/Types.hs
@@ -1,4 +1,6 @@
 --------------------------------------------------------------------------------
+{-# LANGUAGE FlexibleContexts  #-}
+{-# LANGUAGE FlexibleInstances #-}
 {-# LANGUAGE OverloadedStrings #-}
 -- | Core types used internally
 module Text.Digestive.Types
@@ -14,7 +16,7 @@
 
 
 --------------------------------------------------------------------------------
-import           Control.Applicative (Applicative(..))
+import           Control.Applicative (Applicative (..))
 import           Data.Monoid         (Monoid, mappend)
 
 
@@ -48,7 +50,7 @@
 
 
 --------------------------------------------------------------------------------
-instance Monad (Result v) where
+instance Monoid v => Monad (Result v) where
     return x          = Success x
     (Error x)   >>= _ = Error x
     (Success x) >>= f = f x
diff --git a/src/Text/Digestive/View.hs b/src/Text/Digestive/View.hs
--- a/src/Text/Digestive/View.hs
+++ b/src/Text/Digestive/View.hs
@@ -57,6 +57,7 @@
 import           Control.Arrow                      (second)
 import           Control.Monad.Identity             (Identity)
 import           Data.List                          (isPrefixOf)
+import           Data.Monoid                        (Monoid)
 import           Data.Text                          (Text)
 import qualified Data.Text                          as T
 
@@ -73,12 +74,12 @@
 -- | Finalized form - handles the form, error messages and input.
 -- Internally handles the addressing of individual fields.
 data View v = forall a m. Monad m => View
-    { viewName     :: Text
-    , viewContext  :: Path
-    , viewForm     :: FormTree Identity v m a
-    , viewInput    :: [(Path, FormInput)]
-    , viewErrors   :: [(Path, v)]
-    , viewMethod   :: Method
+    { viewName    :: Text
+    , viewContext :: Path
+    , viewForm    :: FormTree Identity v m a
+    , viewInput   :: [(Path, FormInput)]
+    , viewErrors  :: [(Path, v)]
+    , viewMethod  :: Method
     }
 
 
@@ -204,13 +205,12 @@
     eval' :: Field v b -> [(Text, v, Bool)]
     eval' field = case field of
         Choice xs didx ->
-            let idx = snd $ evalField method givenInput (Choice xs didx)
-            in map (\(i, (k, (_, v))) -> (k, v, i == idx)) $
+            let idx = map snd $ evalField method givenInput (Choice xs didx)
+            in map (\(i, (k, (_, v))) -> (k, v, i `elem` idx)) $
                  zip [0 ..] $ concat $ map snd xs
         f           -> error $ T.unpack ref ++ ": expected (Choice _ _), " ++
             "but got: (" ++ show f ++ ")"
 
-
 --------------------------------------------------------------------------------
 -- | Returns a list of (groupName, [(identifier, view, selected?)])
 fieldInputChoiceGroup :: forall v. Text
@@ -225,12 +225,12 @@
     eval' :: Field v b -> [(Text, [(Text, v, Bool)])]
     eval' field = case field of
         Choice xs didx ->
-            let idx = snd $ evalField method givenInput (Choice xs didx)
+            let idx = map snd $ evalField method givenInput (Choice xs didx)
             in merge idx xs [0..]
         f           -> error $ T.unpack ref ++ ": expected (Choice _ _), " ++
             "but got: (" ++ show f ++ ")"
 
-merge :: Int
+merge :: [Int]
       -> [(Text, [(Text, (a, v))])]
       -> [Int]
       -> [(Text, [(Text, v, Bool)])]
@@ -238,7 +238,7 @@
 merge idx (g:gs) is = cur : merge idx gs b
   where
     (a,b) = splitAt (length $ snd g) is
-    cur = (fst g, map (\(i, (k, (_, v))) -> (k, v, i == idx)) $ zip a (snd g))
+    cur = (fst g, map (\(i, (k, (_, v))) -> (k, v, i `elem` idx)) $ zip a (snd g))
 
 --------------------------------------------------------------------------------
 -- | Returns True/False based on the field referred to by the given
@@ -259,14 +259,14 @@
 
 --------------------------------------------------------------------------------
 -- | Return the FilePath referred to by the given serialized path, if set.
-fieldInputFile :: forall v. Text -> View v -> Maybe FilePath
+fieldInputFile :: forall v. Text -> View v -> [FilePath]
 fieldInputFile ref (View _ _ form input _ method) =
     queryField path form eval'
   where
     path       = toPath ref
     givenInput = lookupInput path input
 
-    eval' :: Field v b -> Maybe FilePath
+    eval' :: Field v b -> [FilePath]
     eval' field = case field of
         File -> evalField method givenInput File
         f    -> error $ T.unpack ref ++ ": expected (File), " ++
@@ -333,5 +333,5 @@
 
 --------------------------------------------------------------------------------
 -- | Retrieve all paths of the contained form
-debugViewPaths :: View v -> [Path]
+debugViewPaths :: Monoid v => View v -> [Path]
 debugViewPaths (View _ _ form _ _ _) = debugFormPaths form
diff --git a/tests/Text/Digestive/Form/QTests.hs b/tests/Text/Digestive/Form/QTests.hs
--- a/tests/Text/Digestive/Form/QTests.hs
+++ b/tests/Text/Digestive/Form/QTests.hs
@@ -22,8 +22,10 @@
 import           Control.Monad
 import           Control.Monad.Identity
 import           Data.Maybe
+import           Data.Monoid                          (Monoid)
 import           Data.Text                            (Text, pack)
 
+
 --------------------------------------------------------------------------------
 
 tests :: Test
@@ -65,7 +67,7 @@
 
 --------------------------------------------------------------------------------
 -- Limited arbitrary instance for form trees
-instance (Monad t, Monad m, Arbitrary a) => Arbitrary (FormTree t v m a)
+instance (Monad t, Monad m, Monoid v, Arbitrary a) => Arbitrary (FormTree t v m a)
    where arbitrary = sized (innerarb $ liftM Pure arbitrary)
             where innerarb g 0 = g
                   innerarb g n = innerarb g' (n-1)
diff --git a/tests/Text/Digestive/Tests/Fixtures.hs b/tests/Text/Digestive/Tests/Fixtures.hs
--- a/tests/Text/Digestive/Tests/Fixtures.hs
+++ b/tests/Text/Digestive/Tests/Fixtures.hs
@@ -59,21 +59,32 @@
 
 
 --------------------------------------------------------------------------------
-data Type = Water | Fire | Leaf
+data Type = Water | Fire | Leaf | Rock
     deriving (Eq, Show)
 
 
 --------------------------------------------------------------------------------
+typeChoices :: [(Type, Text)]
+typeChoices = [(Water, "Water"), (Fire, "Fire"), (Leaf, "Leaf"), (Rock, "Rock")]
+
+
+--------------------------------------------------------------------------------
 typeForm :: Monad m => Form Text m Type
-typeForm = choice [(Water, "Water"), (Fire, "Fire"), (Leaf, "Leaf")] Nothing
+typeForm = choice typeChoices Nothing
 
 
 --------------------------------------------------------------------------------
+weaknessForm :: Monad m => Form Text m [Type]
+weaknessForm = choiceMultiple typeChoices Nothing
+
+
+--------------------------------------------------------------------------------
 data Pokemon = Pokemon
-    { pokemonName  :: Text
-    , pokemonLevel :: Maybe Int
-    , pokemonType  :: Type
-    , pokemonRare  :: Bool
+    { pokemonName     :: Text
+    , pokemonLevel    :: Maybe Int
+    , pokemonType     :: Type
+    , pokemonWeakness :: [Type]
+    , pokemonRare     :: Bool
     } deriving (Eq, Show)
 
 
@@ -93,10 +104,11 @@
 --------------------------------------------------------------------------------
 pokemonForm :: Form Text TrainerM Pokemon
 pokemonForm = Pokemon
-    <$> "name"  .: validate isPokemon (text Nothing)
-    <*> "level" .: levelForm
-    <*> "type"  .: typeForm
-    <*> "rare"  .: bool Nothing
+    <$> "name"     .: validate isPokemon (text Nothing)
+    <*> "level"    .: levelForm
+    <*> "type"     .: typeForm
+    <*> "weakness" .: weaknessForm
+    <*> "rare"     .: bool Nothing
   where
     definitelyNoPokemon = ["dog", "cat"]
     isPokemon name
@@ -176,9 +188,9 @@
 
 --------------------------------------------------------------------------------
 canCatch :: Catch -> Bool
-canCatch (Catch (Pokemon _ _ _ False) _)      = True
-canCatch (Catch (Pokemon _ _ _ True)  Ultra)  = True
-canCatch (Catch (Pokemon _ _ _ True)  Master) = True
+canCatch (Catch (Pokemon _ _ _ _ False) _)      = True
+canCatch (Catch (Pokemon _ _ _ _ True)  Ultra)  = True
+canCatch (Catch (Pokemon _ _ _ _ True)  Master) = True
 canCatch _                                    = False
 
 
diff --git a/tests/Text/Digestive/View/Tests.hs b/tests/Text/Digestive/View/Tests.hs
--- a/tests/Text/Digestive/View/Tests.hs
+++ b/tests/Text/Digestive/View/Tests.hs
@@ -33,18 +33,22 @@
 tests :: Test
 tests = testGroup "Text.Digestive.View.Tests"
     [ testCase "Simple postForm" $ (@=?)
-        (Just (Pokemon "charmander" (Just 5) Fire False)) $
+        (Just (Pokemon "charmander" (Just 5) Fire [Water, Rock] False)) $
         snd $ runTrainerM $ postForm "f" pokemonForm $ testEnv
-            [ ("f.name",  "charmander")
-            , ("f.level", "5")
-            , ("f.type",  "type.1")
+            [ ("f.name",     "charmander")
+            , ("f.level",    "5")
+            , ("f.type",     "type.1")
+            , ("f.weakness", "0") -- NOTE: this is the newer style of Choice values
+            , ("f.weakness", "3")
             ]
 
     , testCase "optional unspecified" $ (@=?)
-        (Just (Pokemon "magmar" Nothing Fire False)) $
+        (Just (Pokemon "magmar" Nothing Fire [Water, Rock] False)) $
         snd $ runTrainerM $ postForm "f" pokemonForm $ testEnv
-            [ ("f.name",  "magmar")
-            , ("f.type",  "type.1")
+            [ ("f.name",     "magmar")
+            , ("f.type",     "type.1")
+            , ("f.weakness", "weakness.0") -- NOTE: this is the older style of Choice values
+            , ("f.weakness", "weakness.3")
             ]
 
     , testCase "stringRead float" $ (@=?)
@@ -91,12 +95,14 @@
             postForm "f" pokemonForm $ testEnv [("f.type",  "type.2")]
 
     , testCase "Nested postForm" $ (@=?)
-        (Just (Catch (Pokemon "charmander" (Just 5) Fire False) Ultra)) $
+        (Just (Catch (Pokemon "charmander" (Just 5) Fire [Water, Rock] False) Ultra)) $
         snd $ runTrainerM $ postForm "f" catchForm $ testEnv
-            [ ("f.pokemon.name",  "charmander")
-            , ("f.pokemon.level", "5")
-            , ("f.pokemon.type",  "type.1")
-            , ("f.ball",          "ball.2")
+            [ ("f.pokemon.name",     "charmander")
+            , ("f.pokemon.level",    "5")
+            , ("f.pokemon.type",     "type.1")
+            , ("f.pokemon.weakness", "weakness.0")
+            , ("f.pokemon.weakness", "weakness.3")
+            , ("f.ball",             "ball.2")
             ]
 
     , testCase "subView errors" $ (@=?)
@@ -118,11 +124,11 @@
                 ]
 
     , testCase "subViews length" $ (@=?)
-        4 $
+        5 $
         length $ subViews $ runTrainerM $ getForm "f" pokemonForm
 
     , testCase "subViews after subView length" $ (@=?)
-        4 $
+        5 $
         length $ subViews $ subView "pokemon" $
             runTrainerM $ getForm "f" catchForm
 
