diff --git a/ChangeLog.md b/ChangeLog.md
--- a/ChangeLog.md
+++ b/ChangeLog.md
@@ -1,5 +1,20 @@
-# Changelog
-
-## 1.6.0
-
-* Added `Yesod.Form.MultiInput` which supports multi-input forms without needing to submit the form to add an input field [#1601](https://github.com/yesodweb/yesod/pull/1601)
+# Changelog
+
+## 1.7.0
+
+[#1707](https://github.com/yesodweb/yesod/pull/1707)
+
+* Added delete buttons
+* Added support for custom text or icons inside add/delete buttons
+* Added new presets for Bootstrap + Font Awesome icons
+* Added support for more complex fields that have multiple parts stuch as radio fields
+* Improved support for fields that rely on hidden inputs like WYSIWYG editors
+* Fixed redundant class in existing Bootstrap presets
+* Fixed styling not applying to error messages on individual fields
+* Tooltips now show once at the top of the multi-field group when using `amulti`
+
+## 1.6.0
+
+[#1601](https://github.com/yesodweb/yesod/pull/1601)
+
+* Added `Yesod.Form.MultiInput` which supports multi-input forms without needing to submit the form to add an input field
diff --git a/LICENSE b/LICENSE
--- a/LICENSE
+++ b/LICENSE
@@ -1,20 +1,20 @@
-Copyright (c) 2019 James Burton
-
-Permission is hereby granted, free of charge, to any person obtaining
-a copy of this software and associated documentation files (the
-"Software"), to deal in the Software without restriction, including
-without limitation the rights to use, copy, modify, merge, publish,
-distribute, sublicense, and/or sell copies of the Software, and to
-permit persons to whom the Software is furnished to do so, subject to
-the following conditions:
-
-The above copyright notice and this permission notice shall be
-included in all copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
-EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
-MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
-NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
-LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
-OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
-WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+Copyright (c) 2019 James Burton
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/README.md b/README.md
--- a/README.md
+++ b/README.md
@@ -1,7 +1,5 @@
-## yesod-form-multi
-
-Support for creating forms in which the user can specify how many inputs to submit. Includes support for enforcing a minimum number of values.
-Intended as an alternative to `Yesod.Form.MassInput`.
-
-# Limitations
-- If the user adds too many fields then there is currently no support for a "delete button" although fields submitted empty are considered to be deleted.
+## yesod-form-multi
+
+Support for creating forms in which the user can specify how many inputs to submit. Includes support for enforcing a minimum number of values.
+
+Intended as an alternative to `Yesod.Form.MassInput`.
diff --git a/Setup.lhs b/Setup.lhs
--- a/Setup.lhs
+++ b/Setup.lhs
@@ -1,7 +1,7 @@
-#!/usr/bin/env runhaskell
-
-> module Main where
-> import Distribution.Simple
-
-> main :: IO ()
-> main = defaultMain
+#!/usr/bin/env runhaskell
+
+> module Main where
+> import Distribution.Simple
+
+> main :: IO ()
+> main = defaultMain
diff --git a/Yesod/Form/MultiInput.hs b/Yesod/Form/MultiInput.hs
--- a/Yesod/Form/MultiInput.hs
+++ b/Yesod/Form/MultiInput.hs
@@ -1,294 +1,514 @@
-{-# LANGUAGE RecordWildCards #-}
-{-# LANGUAGE OverloadedStrings #-}
-{-# LANGUAGE TemplateHaskell #-}
-{-# LANGUAGE MultiParamTypeClasses #-}
-{-# LANGUAGE TypeFamilies #-}
-{-# LANGUAGE QuasiQuotes  #-}
-{-# LANGUAGE FlexibleContexts #-}
-{-# LANGUAGE FlexibleInstances #-}
-{-# LANGUAGE CPP #-}
-
--- | A module providing a means of creating multiple input forms without
--- the need to submit the form to generate a new input field unlike
--- in "MassInput".
-module Yesod.Form.MultiInput
-    ( MultiSettings (..)
-    , MultiView (..)
-    , mmulti
-    , amulti
-    , bs3Settings
-    , bs4Settings
-    ) where
-
-import Control.Arrow (second)
-import Control.Monad (liftM)
-import Control.Monad.Trans.RWS (ask, tell)
-import qualified Data.Map as Map
-import Data.Maybe (fromJust, listToMaybe, fromMaybe)
-import Data.Text (Text)
-import qualified Data.Text as T
-import Yesod.Core
-import Yesod.Form.Fields (intField)
-import Yesod.Form.Functions
-import Yesod.Form.Types
-
-#ifdef MIN_VERSION_shakespeare(2,0,18)
-#if MIN_VERSION_shakespeare(2,0,18)
-#else
-import Text.Julius (ToJavascript (..))
-instance ToJavascript String where toJavascript = toJavascript . toJSON
-instance ToJavascript Text where toJavascript = toJavascript . toJSON
-#endif
-#endif
-
--- @since 1.6.0
-data MultiSettings site = MultiSettings
-    { msAddClass :: Text -- ^ Class to be applied to the "add another" button.
-    , msErrWidget :: Maybe (Html -> WidgetFor site ()) -- ^ Only used in applicative forms. Create a widget for displaying errors.
-    }
-
--- @since 1.6.0
-data MultiView site = MultiView
-    { mvCounter :: FieldView site -- ^ Hidden counter field.
-    , mvFields :: [FieldView site] -- ^ Input fields.
-    , mvAddBtn :: FieldView site -- ^ Button to add another field.
-    }
-
--- | 'MultiSettings' for Bootstrap 3.
---
--- @since 1.6.0
-bs3Settings :: MultiSettings site
-bs3Settings = MultiSettings "btn btn-default" (Just errW)
-    where
-        errW err = 
-            [whamlet|
-                <span .help-block .error-block>#{err}
-            |]
-
--- | 'MultiSettings' for Bootstrap 4.
---
--- @since 1.6.0
-bs4Settings :: MultiSettings site
-bs4Settings = MultiSettings "btn btn-basic" (Just errW)
-    where
-        errW err =
-            [whamlet|
-                <div .invalid-feedback>#{err}
-            |]
-
--- | Applicative equivalent of 'mmulti'.
---
--- @since 1.6.0
-amulti :: (site ~ HandlerSite m, MonadHandler m, RenderMessage site FormMessage)
-    => Field m a
-    -> FieldSettings site
-    -> [a]
-    -> Int
-    -> MultiSettings site
-    -> AForm m [a]
-amulti field fs defs minVals ms = formToAForm $
-    liftM (second return) mform
-    where
-        mform = do
-            (fr, MultiView {..}) <- mmulti field fs defs minVals ms
-
-            let widget = do
-                    [whamlet|
-                        ^{fvInput mvCounter}
-
-                        $forall fv <- mvFields
-                            ^{fvInput fv}
-
-                            $maybe err <- fvErrors fv
-                                $maybe errW <- msErrWidget ms
-                                    ^{errW err}
-
-                        ^{fvInput mvAddBtn}
-                    |]
-                (fv : _) = mvFields
-                view = FieldView
-                    { fvLabel = fvLabel fv
-                    , fvTooltip = Nothing
-                    , fvId = fvId fv
-                    , fvInput = widget
-                    , fvErrors = fvErrors mvAddBtn
-                    , fvRequired = False
-                    }
-            
-            return (fr, view)
-
--- | Converts a form field into a monadic form containing an arbitrary
--- number of the given fields as specified by the user. Returns a list
--- of results, failing if the length of the list is less than the minimum
--- requested values.
---
--- @since 1.6.0
-mmulti :: (site ~ HandlerSite m, MonadHandler m, RenderMessage site FormMessage)
-    => Field m a
-    -> FieldSettings site
-    -> [a]
-    -> Int
-    -> MultiSettings site
-    -> MForm m (FormResult [a], MultiView site)
-mmulti field fs@FieldSettings {..} defs minVals ms = do
-    fieldClass <- newFormIdent
-    let fs' = fs {fsAttrs = addClass fieldClass fsAttrs}
-        minVals' = if minVals < 0 then 0 else minVals
-    mhelperMulti field fs' fieldClass defs minVals' ms
-
--- Helper function, does most of the work for mmulti.
-mhelperMulti :: (site ~ HandlerSite m, MonadHandler m, RenderMessage site FormMessage)
-    => Field m a
-    -> FieldSettings site
-    -> Text
-    -> [a]
-    -> Int
-    -> MultiSettings site
-    -> MForm m (FormResult [a], MultiView site)
-mhelperMulti field@Field {..} fs@FieldSettings {..} fieldClass defs minVals MultiSettings {..} = do
-    mp <- askParams
-    (_, site, langs) <- ask
-    name <- maybe newFormIdent return fsName
-    theId <- maybe newFormIdent return fsId
-    cName <- newFormIdent
-    cid <- newFormIdent
-    addBtnId <- newFormIdent 
-
-    let mr2 = renderMessage site langs
-        cDef = length defs
-        cfs = FieldSettings "" Nothing (Just cid) (Just cName) [("hidden", "true")]
-        mkName i = name `T.append` (T.pack $ '-' : show i)
-        mkId i = theId `T.append` (T.pack $ '-' : show i)
-        mkNames c = [(mkName i, mkId i) | i <- [0 .. c]]
-        onMissingSucc _ _ = FormSuccess Nothing
-        onMissingFail m l = FormFailure [renderMessage m l MsgValueRequired]
-        isSuccNothing r = case r of
-            FormSuccess Nothing -> True
-            _ -> False
-
-    mfs <- askFiles
-    
-    -- get counter value (starts counting from 0)
-    cr@(cRes, _) <- case mp of
-        Nothing -> return (FormMissing, Right cDef)
-        Just p -> mkRes intField cfs p mfs cName onMissingFail FormSuccess
-
-    -- generate counter view
-    cView <- mkView intField cfs cr cid cName True
-
-    let counter = case cRes of
-            FormSuccess c -> c
-            _             -> cDef
-
-    -- get results of fields
-    results <- case mp of
-        Nothing -> return $
-            if cDef == 0
-                then [(FormMissing, Left "")]
-                else [(FormMissing, Right d) | d <- defs]
-        Just p -> mapM (\n -> mkRes field fs p mfs n onMissingSucc (FormSuccess . Just)) (map fst $ mkNames counter)
-
-    -- generate field views
-    (rs, fvs) <- do
-        let mkView' ((n,i), r@(res, _)) = do
-                fv <- mkView field fs r i n False
-                return (res, fv)
-            xs = zip (mkNames counter) results
-            notSuccNothing (_, (r,_)) = not $ isSuccNothing r
-            ys = case filter notSuccNothing xs of
-                [] -> [((mkName 0, mkId 0), (FormSuccess Nothing, Left ""))] -- always need at least one value to generate a field
-                zs -> zs
-        rvs <- mapM mkView' ys
-        return $ unzip rvs
-    
-    -- check values
-    let rs' = [ fmap fromJust r | r <- rs
-                                , not $ isSuccNothing r ]
-        err = T.pack $ "Please enter at least " ++ show minVals ++ " values."
-        (res, tooFewVals) = 
-            case foldr (<*>) (FormSuccess []) (map (fmap $ (:)) rs') of
-                FormSuccess xs ->
-                    if length xs < minVals
-                        then (FormFailure [err], True)
-                        else (FormSuccess xs, False)
-                fRes -> (fRes, False)
-    
-        -- create add button
-        btnWidget = do
-            [whamlet|
-                <button ##{addBtnId} .#{msAddClass} type="button">Add Another
-            |]
-            toWidget
-                [julius|
-                    var extraFields = 0;
-                    $("#" + #{addBtnId}).click(function() {
-                        extraFields++;
-                        var newNumber = parseInt(#{show counter}) + extraFields;
-                        $("#" + #{cid}).val(newNumber);
-                        var newName = #{name} + "-" + newNumber;
-                        var newId = #{theId} + "-" + newNumber;
-                        
-                        var newElem = $("." + #{fieldClass}).first().clone();
-                        newElem.val("").attr('name', newName).attr('id', newId);
-                        newElem.insertBefore("#" + #{addBtnId})
-                    });
-                |]
-
-        btnView = FieldView
-            { fvLabel = toHtml $ mr2 ("" :: Text)
-            , fvTooltip = Nothing
-            , fvId = addBtnId
-            , fvInput = btnWidget
-            , fvErrors = if tooFewVals then Just $ toHtml err else Nothing
-            , fvRequired = False
-            }
-
-    return (res, MultiView cView fvs btnView)
-
--- Search for the given field's name in the environment,
--- parse any values found and construct a FormResult.
-mkRes :: (site ~ HandlerSite m, MonadHandler m)
-    => Field m a
-    -> FieldSettings site
-    -> Env
-    -> Maybe FileEnv
-    -> Text
-    -> (site -> [Text] -> FormResult b)
-    -> (a -> FormResult b)
-    -> MForm m (FormResult b, Either Text a)
-mkRes Field {..} FieldSettings {..} p mfs name onMissing onFound = do
-    tell fieldEnctype
-    (_, site, langs) <- ask
-    let mvals = fromMaybe [] $ Map.lookup name p
-        files = fromMaybe [] $ mfs >>= Map.lookup name
-    emx <- lift $ fieldParse mvals files
-    return $ case emx of
-        Left msg -> (FormFailure [renderMessage site langs msg], maybe (Left "") Left (listToMaybe mvals))
-        Right mx ->
-            case mx of
-                Nothing -> (onMissing site langs, Left "") 
-                Just x -> (onFound x, Right x)
-
--- Generate a FieldView for the given field with the given result.
-mkView :: (site ~ HandlerSite m, MonadHandler m)
-    => Field m a
-    -> FieldSettings site
-    -> (FormResult b, Either Text a)
-    -> Text
-    -> Text
-    -> Bool
-    -> MForm m (FieldView site)
-mkView Field {..} FieldSettings {..} (res, val) theId name isReq = do
-    (_, site, langs) <- ask
-    let mr2 = renderMessage site langs
-    return $ FieldView
-        { fvLabel = toHtml $ mr2 fsLabel
-        , fvTooltip = fmap toHtml $ fmap mr2 fsTooltip
-        , fvId = theId
-        , fvInput = fieldView theId name fsAttrs val isReq
-        , fvErrors =
-            case res of
-                FormFailure [e] -> Just $ toHtml e
-                _ -> Nothing
-        , fvRequired = isReq
+{-# LANGUAGE RecordWildCards #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE TemplateHaskell #-}
+{-# LANGUAGE MultiParamTypeClasses #-}
+{-# LANGUAGE TypeFamilies #-}
+{-# LANGUAGE QuasiQuotes  #-}
+{-# LANGUAGE FlexibleContexts #-}
+{-# LANGUAGE FlexibleInstances #-}
+{-# LANGUAGE CPP #-}
+
+-- | A module providing a means of creating multiple input forms without
+-- the need to submit the form to generate a new input field unlike
+-- in "MassInput".
+module Yesod.Form.MultiInput
+    ( MultiSettings (..)
+    , MultiView (..)
+    , mmulti
+    , amulti
+    , bs3Settings
+    , bs3FASettings
+    , bs4Settings
+    , bs4FASettings
+    ) where
+
+import Control.Arrow (second)
+import Control.Monad (liftM)
+import Control.Monad.Trans.RWS (ask, tell)
+import qualified Data.Map as Map
+import Data.Maybe (fromJust, listToMaybe, fromMaybe, isJust)
+import Data.Text (Text)
+import qualified Data.Text as T
+import Text.Julius (rawJS)
+import Yesod.Core
+import Yesod.Form.Fields (intField)
+import Yesod.Form.Functions
+import Yesod.Form.Types
+
+#ifdef MIN_VERSION_shakespeare(2,0,18)
+#if MIN_VERSION_shakespeare(2,0,18)
+#else
+import Text.Julius (ToJavascript (..))
+instance ToJavascript String where toJavascript = toJavascript . toJSON
+instance ToJavascript Text where toJavascript = toJavascript . toJSON
+#endif
+#endif
+
+-- | By default delete buttons have a @margin-left@ property of @0.75rem@.
+-- You can override this by specifying an alternative value in a class
+-- which is then passed inside 'MultiSettings'.
+--
+-- @since 1.7.0
+data MultiSettings site = MultiSettings
+    { msAddClass :: !Text -- ^ Class to be applied to the "add another" button.
+    , msDelClass :: !Text -- ^ Class to be applied to the "delete" button.
+    , msTooltipClass :: Text -- ^ Only used in applicative forms. Class to be applied to the tooltip.
+    , msWrapperErrClass :: !Text -- ^ Class to be applied to the wrapper if it's field has an error.
+    , msAddInner :: !(Maybe Html) -- ^ Inner Html of add button, defaults to "Add Another". Useful for adding icons inside buttons.
+    , msDelInner :: !(Maybe Html) -- ^ Inner Html of delete button, defaults to "Delete". Useful for adding icons inside buttons.
+    , msErrWidget :: Maybe (Html -> WidgetFor site ()) -- ^ Only used in applicative forms. Create a widget for displaying errors.
+    }
+
+-- | The general structure of each individually generated field is as follows.
+-- There is an external wrapper element containing both an inner wrapper and any
+-- error messages that apply to that specific field. The inner wrapper contains
+-- both the field and it's corresponding delete button.
+--
+-- The structure is illustrated by the following:
+-- 
+-- > <div .#{wrapperClass}>
+-- >     <div .#{wrapperClass}-inner>
+-- >         ^{fieldWidget}
+-- >         ^{deleteButton}
+-- >     ^{maybeErrorMessages}
+--
+-- Each wrapper element has the same class which is automatically generated. This class
+-- is returned in the 'MultiView' should you wish to change the styling. The inner wrapper
+-- uses the same class followed by @-inner@. By default the wrapper and inner wrapper has
+-- classes are as follows:
+-- 
+-- > .#{wrapperClass} {
+-- >     margin-bottom: 1rem;
+-- > }
+-- >
+-- > .#{wrapperClass}-inner {
+-- >     display: flex;
+-- >     flex-direction: row;
+-- > }
+--
+-- @since 1.7.0
+data MultiView site = MultiView
+    { mvCounter :: FieldView site -- ^ Hidden counter field.
+    , mvFields :: [FieldView site] -- ^ Input fields.
+    , mvAddBtn :: FieldView site -- ^ Button to add another field.
+    , mvWrapperClass :: Text -- ^ Class applied to a div wrapping each field with it's delete button.
+    }
+
+-- | 'MultiSettings' for Bootstrap 3.
+--
+-- @since 1.6.0
+bs3Settings :: MultiSettings site
+bs3Settings = MultiSettings
+    "btn btn-default"
+    "btn btn-danger"
+    "help-block"
+    "has-error"
+    Nothing Nothing (Just errW)
+    where
+        errW err = 
+            [whamlet|
+                <span .help-block>#{err}
+            |]
+
+-- | 'MultiSettings' for Bootstrap 4.
+--
+-- @since 1.6.0
+bs4Settings :: MultiSettings site
+bs4Settings = MultiSettings
+    "btn btn-secondary"
+    "btn btn-danger"
+    "form-text text-muted"
+    "has-error"
+    Nothing Nothing (Just errW)
+    where
+        errW err =
+            [whamlet|
+                <div .invalid-feedback>#{err}
+            |]
+
+-- | 'MultiSettings' for Bootstrap 3 with Font Awesome 5 Icons.
+-- Uses @fa-plus@ for the add button and @fa-trash-alt@ for the delete button.
+--
+-- @since 1.7.0
+bs3FASettings :: MultiSettings site
+bs3FASettings = MultiSettings
+    "btn btn-default"
+    "btn btn-danger"
+    "help-block"
+    "has-error"
+    addIcon delIcon (Just errW)
+    where
+        addIcon = Just [shamlet|<i class="fas fa-plus">|]
+        delIcon = Just [shamlet|<i class="fas fa-trash-alt">|]
+        errW err = 
+            [whamlet|
+                <span .help-block>#{err}
+            |]
+
+-- | 'MultiSettings' for Bootstrap 4 with Font Awesome 5 Icons.
+-- Uses @fa-plus@ for the add button and @fa-trash-alt@ for the delete button.
+--
+-- @since 1.7.0
+bs4FASettings :: MultiSettings site
+bs4FASettings = MultiSettings
+    "btn btn-secondary"
+    "btn btn-danger"
+    "form-text text-muted"
+    "has-error"
+    addIcon delIcon (Just errW)
+    where
+        addIcon = Just [shamlet|<i class="fas fa-plus">|]
+        delIcon = Just [shamlet|<i class="fas fa-trash-alt">|]
+        errW err =
+            [whamlet|
+                <div .invalid-feedback>#{err}
+            |]
+
+-- | Applicative equivalent of 'mmulti'.
+--
+-- Note about tooltips:
+-- Rather than displaying the tooltip alongside each field the
+-- tooltip is displayed once at the top of the multi-field set.
+--
+-- @since 1.6.0
+amulti :: (site ~ HandlerSite m, MonadHandler m, RenderMessage site FormMessage)
+    => Field m a
+    -> FieldSettings site
+    -> [a]
+    -> Int
+    -> MultiSettings site
+    -> AForm m [a]
+amulti field fs defs minVals ms = formToAForm $
+    liftM (second return) mform
+    where
+        mform = do
+            (fr, MultiView {..}) <- mmulti field fs defs minVals ms
+
+            let (fv : _) = mvFields
+                widget = do
+                    [whamlet|
+                        $maybe tooltip <- fvTooltip fv
+                            <small .#{msTooltipClass ms}>#{tooltip}
+
+                        ^{fvInput mvCounter}
+
+                        $forall fv <- mvFields
+                            ^{fvInput fv}
+
+                        ^{fvInput mvAddBtn}
+                    |]
+                view = FieldView
+                    { fvLabel = fvLabel fv
+                    , fvTooltip = Nothing
+                    , fvId = fvId fv
+                    , fvInput = widget
+                    , fvErrors = fvErrors mvAddBtn
+                    , fvRequired = False
+                    }
+            
+            return (fr, view)
+
+-- | Converts a form field into a monadic form containing an arbitrary
+-- number of the given fields as specified by the user. Returns a list
+-- of results, failing if the length of the list is less than the minimum
+-- requested values.
+--
+-- @since 1.6.0
+mmulti :: (site ~ HandlerSite m, MonadHandler m, RenderMessage site FormMessage)
+    => Field m a
+    -> FieldSettings site
+    -> [a]
+    -> Int
+    -> MultiSettings site
+    -> MForm m (FormResult [a], MultiView site)
+mmulti field fs defs minVals' ms = do
+    wrapperClass <- lift newIdent
+    let minVals = if minVals' < 0 then 0 else minVals'
+    mhelperMulti field fs wrapperClass defs minVals ms
+
+-- Helper function, does most of the work for mmulti.
+mhelperMulti :: (site ~ HandlerSite m, MonadHandler m, RenderMessage site FormMessage)
+    => Field m a
+    -> FieldSettings site
+    -> Text
+    -> [a]
+    -> Int
+    -> MultiSettings site
+    -> MForm m (FormResult [a], MultiView site)
+mhelperMulti field@Field {..} fs@FieldSettings {..} wrapperClass defs minVals MultiSettings {..} = do
+    mp <- askParams
+    (_, site, langs) <- ask
+    name <- maybe newFormIdent return fsName
+    theId <- lift $ maybe newIdent return fsId
+    cName <- newFormIdent
+    cid <- lift newIdent
+    addBtnId <- lift newIdent
+    delBtnPrefix <- lift newIdent
+
+    let mr2 = renderMessage site langs
+        cDef = length defs
+        cfs = FieldSettings "" Nothing (Just cid) (Just cName) [("hidden", "true")]
+        mkName i = name `T.append` (T.pack $ '-' : show i)
+        mkId i = theId `T.append` (T.pack $ '-' : show i)
+        mkNames c = [(i, (mkName i, mkId i)) | i <- [0 .. c]]
+        onMissingSucc _ _ = FormSuccess Nothing
+        onMissingFail m l = FormFailure [renderMessage m l MsgValueRequired]
+        isSuccNothing r = case r of
+            FormSuccess Nothing -> True
+            _ -> False
+
+    mfs <- askFiles
+    
+    -- get counter value (starts counting from 0)
+    cr@(cRes, _) <- case mp of
+        Nothing -> return (FormMissing, Right cDef)
+        Just p -> mkRes intField cfs p mfs cName onMissingFail FormSuccess
+
+    -- generate counter view
+    cView <- mkView intField cfs cr Nothing Nothing msWrapperErrClass cid cName True
+
+    let counter = case cRes of
+            FormSuccess c -> c
+            _             -> cDef
+
+    -- get results of fields
+    results <- case mp of
+        Nothing -> return $
+            if cDef == 0
+                then [(FormMissing, Left "")]
+                else [(FormMissing, Right d) | d <- defs]
+        Just p -> mapM
+            (\n -> mkRes field fs p mfs n onMissingSucc (FormSuccess . Just))
+            (map (fst . snd) $ mkNames counter)
+
+    -- delete button
+
+    -- The delFunction is included down with the add button rather than with
+    -- each delete button to ensure that the function only gets included once.
+    let delFunction = toWidget
+            [julius|
+                function deleteField_#{rawJS theId}(wrapper) {
+                    var numFields = $('.#{rawJS wrapperClass}').length;
+
+                    if (numFields == 1)
+                    {
+                        wrapper.find("*").each(function() {
+                            removeVals($(this));
+                        });
+                    }
+                    else
+                        wrapper.remove();
+                }
+
+                function removeVals(e) {
+                    // input types where we don't want to reset the value
+                    const keepValueTypes = ["radio", "checkbox", "button"];
+
+                    // uncheck any checkboxes or radio fields and empty any text boxes
+                    if(e.prop('checked') == true)
+                        e.prop('checked', false);
+
+                    if(!keepValueTypes.includes(e.prop('type')))
+                        e.val("").trigger("change");
+                        // trigger change is to ensure WYSIWYG editors are updated
+                        // when their hidden code field is cleared
+                }
+            |]
+
+        mkDelBtn fieldId = do
+            let delBtnId = delBtnPrefix `T.append` fieldId
+            [whamlet|
+                <button ##{delBtnId} .#{msDelClass} style="margin-left: 0.75rem" type="button">
+                    $maybe inner <- msDelInner
+                        #{inner}
+                    $nothing
+                        Delete
+            |]
+            toWidget
+                [julius|
+                    $('##{rawJS delBtnId}').click(function() {
+                        var field = $('##{rawJS fieldId}');
+                        deleteField_#{rawJS theId}(field.parents('.#{rawJS wrapperClass}'));
+                    });                    
+                |]
+
+    -- generate field views
+    (rs, fvs) <- do
+        let mkView' ((c, (n,i)), r@(res, _)) = do
+                let del = Just (mkDelBtn i, wrapperClass, c)
+                fv <- mkView field fs r del msErrWidget msWrapperErrClass i n True
+                return (res, fv)
+            xs = zip (mkNames counter) results
+            notSuccNothing (_, (r,_)) = not $ isSuccNothing r
+            ys = case filter notSuccNothing xs of
+                [] -> [((0, (mkName 0, mkId 0)), (FormSuccess Nothing, Left ""))] -- always need at least one value to generate a field
+                zs -> zs
+        rvs <- mapM mkView' ys
+        return $ unzip rvs
+    
+    -- check values
+    let rs' = [ fmap fromJust r | r <- rs
+                                , not $ isSuccNothing r ]
+        err = T.pack $ "Please enter at least " ++ show minVals ++ " values."
+        (res, tooFewVals) = 
+            case foldr (<*>) (FormSuccess []) (map (fmap $ (:)) rs') of
+                FormSuccess xs ->
+                    if length xs < minVals
+                        then (FormFailure [err], True)
+                        else (FormSuccess xs, False)
+                fRes -> (fRes, False)
+    
+        -- create add button
+        -- also includes some styling / functions that we only want to include once
+        btnWidget = do
+            [whamlet|
+                <button ##{addBtnId} .#{msAddClass} type="button">
+                    $maybe inner <- msAddInner
+                        #{inner}
+                    $nothing
+                        Add Another
+            |]
+            toWidget
+                [lucius|
+                    .#{wrapperClass} {
+                        margin-bottom: 1rem;
+                    }
+                    .#{wrapperClass}-inner {
+                        display: flex;
+                        flex-direction: row;
+                    }
+                |]
+            delFunction -- function used by delete buttons, included here so that it only gets included once
+            toWidget
+                [julius|
+                    var extraFields_#{rawJS theId} = 0;
+                    $('##{rawJS addBtnId}').click(function() {
+                        extraFields_#{rawJS theId}++;
+                        var newNumber = parseInt(#{show counter}) + extraFields_#{rawJS theId};
+                        $("#" + #{cid}).val(newNumber);
+                        var newName = #{name} + "-" + newNumber;
+                        var newId = #{theId} + "-" + newNumber;
+                        var newDelId = #{delBtnPrefix} + newId;
+                        
+                        // get new wrapper and remove old error messages
+                        var newWrapper = $('.#{rawJS wrapperClass}').first().clone();
+                        newWrapper.children( ':not(.#{rawJS wrapperClass}-inner)' ).remove();
+                        newWrapper.removeClass(#{msWrapperErrClass});
+
+                        // get counter from wrapper
+                        var oldCount = newWrapper.data("counter");
+                        var oldName = #{name} + "-" + oldCount;
+                        var oldId = #{theId} + "-" + oldCount;
+                        var oldDelBtn = #{delBtnPrefix} + oldId;
+
+                        // replace any id, name or for attributes that began with
+                        // the old values and replace them with the new values
+                        var idRegex = new RegExp("^" + oldId);
+                        var nameRegex = new RegExp("^" + oldName);
+
+                        var els = newWrapper.find("*");
+                        els.each(function() {
+                            var e = $(this);
+
+                            if(e.prop('id') != undefined)
+                                e.prop('id', e.prop('id').replace(idRegex, newId));
+
+                            if(e.prop('name') != undefined)
+                                e.prop('name', e.prop('name').replace(nameRegex, newName));
+
+                            if(e.prop('for') != undefined)
+                                e.prop('for', e.prop('for').replace(idRegex, newId)); // radio fields use id in for attribute
+
+                            removeVals(e);
+                        });
+
+                        // set new counter on wrapper
+                        newWrapper.attr("data-counter", newNumber);
+
+                        var newDelBtn = newWrapper.find('[id^=#{rawJS delBtnPrefix}]');
+                        newDelBtn.prop('id', newDelId);
+                        newDelBtn.click(() => deleteField_#{rawJS theId}(newWrapper));
+
+                        newWrapper.insertBefore('##{rawJS addBtnId}');
+                    });
+                |]
+
+        btnView = FieldView
+            { fvLabel = toHtml $ mr2 ("" :: Text)
+            , fvTooltip = Nothing
+            , fvId = addBtnId
+            , fvInput = btnWidget
+            , fvErrors = if tooFewVals then Just $ toHtml err else Nothing
+            , fvRequired = False
+            }
+
+    return (res, MultiView cView fvs btnView wrapperClass)
+
+-- Search for the given field's name in the environment,
+-- parse any values found and construct a FormResult.
+mkRes :: (site ~ HandlerSite m, MonadHandler m)
+    => Field m a
+    -> FieldSettings site
+    -> Env
+    -> Maybe FileEnv
+    -> Text
+    -> (site -> [Text] -> FormResult b)
+    -> (a -> FormResult b)
+    -> MForm m (FormResult b, Either Text a)
+mkRes Field {..} FieldSettings {..} p mfs name onMissing onFound = do
+    tell fieldEnctype
+    (_, site, langs) <- ask
+    let mvals = fromMaybe [] $ Map.lookup name p
+        files = fromMaybe [] $ mfs >>= Map.lookup name
+    emx <- lift $ fieldParse mvals files
+    return $ case emx of
+        Left msg -> (FormFailure [renderMessage site langs msg], maybe (Left "") Left (listToMaybe mvals))
+        Right mx ->
+            case mx of
+                Nothing -> (onMissing site langs, Left "") 
+                Just x -> (onFound x, Right x)
+
+-- Generate a FieldView for the given field with the given result.
+mkView :: (site ~ HandlerSite m, MonadHandler m)
+    => Field m a
+    -> FieldSettings site
+    -> (FormResult b, Either Text a)
+    -- Delete button widget, class for div wrapping each field with it's delete button and counter value for that field.
+    -- Nothing if the field passed doesn't need a delete button e.g. if it is the counter field.
+    -> Maybe (WidgetFor site (), Text, Int)
+    -> Maybe (Html -> WidgetFor site ()) -- Function to display error messages.
+    -> Text
+    -> Text
+    -> Text
+    -> Bool
+    -> MForm m (FieldView site)
+mkView Field {..} FieldSettings {..} (res, val) mdel merrW errClass theId name isReq = do
+    (_, site, langs) <- ask
+    let mr2 = renderMessage site langs
+        merr = case res of
+                FormFailure [e] -> Just $ toHtml e
+                _ -> Nothing
+        fv' = fieldView theId name fsAttrs val isReq
+        fv = do
+            [whamlet|
+                $maybe (delBtn, wrapperClass, counter) <- mdel
+                    <div .#{wrapperClass} :isJust merr:.#{errClass} data-counter=#{counter}>
+                        <div .#{wrapperClass}-inner>
+                            ^{fv'}
+                            ^{delBtn}
+                            
+                        $maybe err <- merr
+                            $maybe errW <- merrW
+                                ^{errW err}
+                        
+                $nothing
+                    ^{fv'}
+            |]
+    return $ FieldView
+        { fvLabel = toHtml $ mr2 fsLabel
+        , fvTooltip = fmap toHtml $ fmap mr2 fsTooltip
+        , fvId = theId
+        , fvInput = fv
+        , fvErrors = merr
+        , fvRequired = isReq
         }
diff --git a/yesod-form-multi.cabal b/yesod-form-multi.cabal
--- a/yesod-form-multi.cabal
+++ b/yesod-form-multi.cabal
@@ -1,38 +1,39 @@
-name:            yesod-form-multi
-version:         1.6.0
-license:         MIT
-license-file:    LICENSE
-author:          James Burton <jamesejburton@gmail.com>
-maintainer:      James Burton <jamesejburton@gmail.com>
-synopsis:        Multi-input form handling for Yesod Web Framework
-category:        Web, Yesod
-stability:       Stable
-cabal-version:   >= 1.8
-build-type:      Simple
-homepage:        http://www.yesodweb.com/
-description:     API docs and the README are available at <http://www.stackage.org/package/yesod-form-multi>.
-extra-source-files: ChangeLog.md
-                    README.md
-
-flag network-uri
-  description: Get Network.URI from the network-uri package
-  default: True
-
-library
-    build-depends:   base                  >= 4        && < 5
-                   , containers            >= 0.2
-                   , shakespeare           >= 2.0
-                   , text                  >= 0.9
-                   , transformers          >= 0.2.2
-                   , yesod-core            >= 1.6      && < 1.7
-                   , yesod-form            >= 1.6      && < 1.7
-
-    if flag(network-uri)
-      build-depends: network-uri >= 2.6
-
-    exposed-modules: Yesod.Form.MultiInput
-    ghc-options:     -Wall
-
-source-repository head
-  type:     git
-  location: https://github.com/yesodweb/yesod
+name:            yesod-form-multi
+version:         1.7.0
+license:         MIT
+license-file:    LICENSE
+author:          James Burton <jamesejburton@gmail.com>
+maintainer:      James Burton <jamesejburton@gmail.com>
+synopsis:        Multi-input form handling for Yesod Web Framework
+category:        Web, Yesod
+stability:       Stable
+cabal-version:   >= 1.10
+build-type:      Simple
+homepage:        http://www.yesodweb.com/
+description:     API docs and the README are available at <http://www.stackage.org/package/yesod-form-multi>.
+extra-source-files: ChangeLog.md
+                    README.md
+
+flag network-uri
+  description: Get Network.URI from the network-uri package
+  default: True
+
+library
+    default-language: Haskell2010
+    build-depends:   base                  >= 4.10     && < 5
+                   , containers            >= 0.2
+                   , shakespeare           >= 2.0
+                   , text                  >= 0.9
+                   , transformers          >= 0.2.2
+                   , yesod-core            >= 1.6      && < 1.7
+                   , yesod-form            >= 1.6      && < 1.7
+
+    if flag(network-uri)
+      build-depends: network-uri >= 2.6
+
+    exposed-modules: Yesod.Form.MultiInput
+    ghc-options:     -Wall
+
+source-repository head
+  type:     git
+  location: https://github.com/yesodweb/yesod
