diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,14 @@
 Brick changelog
 ---------------
 
+0.61
+----
+
+API changes:
+ * Brick.Forms got `editShowableFieldWithValidate`, a generalization
+   of `editShowableField` that allows the caller to specify an
+   additional validation function (thanks Ben Selfridge)
+
 0.60.2
 ------
 
diff --git a/README.md b/README.md
--- a/README.md
+++ b/README.md
@@ -71,6 +71,7 @@
 | [`ghcup`](https://www.haskell.org/ghcup/) | A TUI for `ghcup`, the Haskell toolchain manager |
 | [`cbookview`](https://github.com/mlang/chessIO) | A TUI for exploring polyglot chess opening book files |
 | [`thock`](https://github.com/rmehri01/thock) | A modern TUI typing game featuring online racing against friends |
+| [`fifteen`](https://github.com/benjaminselfridge/fifteen) | An implementation of the [15 puzzle](https://en.wikipedia.org/wiki/15_puzzle) |
 
 These third-party packages also extend `brick`:
 
diff --git a/brick.cabal b/brick.cabal
--- a/brick.cabal
+++ b/brick.cabal
@@ -1,5 +1,5 @@
 name:                brick
-version:             0.60.2
+version:             0.61
 synopsis:            A declarative terminal user interface library
 description:
   Write terminal user interfaces (TUIs) painlessly with 'brick'! You
@@ -114,7 +114,7 @@
     Brick.Types.Internal
     Brick.Widgets.Internal
 
-  build-depends:       base <= 4.15,
+  build-depends:       base < 4.16.0.0,
                        vty >= 5.31,
                        transformers,
                        data-clist >= 0.1,
diff --git a/docs/guide.rst b/docs/guide.rst
--- a/docs/guide.rst
+++ b/docs/guide.rst
@@ -1355,6 +1355,11 @@
 later validate that state and convert it back into the approprate type
 for storage in ``UserInfo``.
 
+The form value itself -- of type ``Form`` -- must be stored in your
+application state. You should only ever call ``newForm`` when you need
+to initialize a totally new form. Once initialized, the form needs to be
+kept around and updated by event handlers in order to work.
+
 For example, if the initial ``UserInfo`` value's ``_age`` field has the
 value ``0``, the ``editShowableField`` will call ``show`` on ``0``,
 convert that to ``Text``, and initialize the editor for ``_age`` with
diff --git a/programs/MouseDemo.hs b/programs/MouseDemo.hs
--- a/programs/MouseDemo.hs
+++ b/programs/MouseDemo.hs
@@ -95,8 +95,8 @@
     let T.Location pos = loc
     M.continue $ st & lastReportedClick .~ Just (n, loc)
                     & edit %~ E.applyEdit (if n == TextBox then moveCursor (swap pos) else id)
-appEvent st (T.MouseUp _ _ _) = M.continue $ st & lastReportedClick .~ Nothing
-appEvent st (T.VtyEvent (V.EvMouseUp _ _ _)) = M.continue $ st & lastReportedClick .~ Nothing
+appEvent st (T.MouseUp {}) = M.continue $ st & lastReportedClick .~ Nothing
+appEvent st (T.VtyEvent (V.EvMouseUp {})) = M.continue $ st & lastReportedClick .~ Nothing
 appEvent st (T.VtyEvent (V.EvKey V.KUp [V.MCtrl])) = M.vScrollBy (M.viewportScroll Prose) (-1) >> M.continue st
 appEvent st (T.VtyEvent (V.EvKey V.KDown [V.MCtrl])) = M.vScrollBy (M.viewportScroll Prose) 1 >> M.continue st
 appEvent st (T.VtyEvent (V.EvKey V.KEsc [])) = M.halt st
diff --git a/src/Brick/BorderMap.hs b/src/Brick/BorderMap.hs
--- a/src/Brick/BorderMap.hs
+++ b/src/Brick/BorderMap.hs
@@ -165,8 +165,8 @@
     }
     where
     bounds' = neighbors coordinates'
-    values' = pure gc
-        <*> _coordinates m
+    values' = gc
+        <$> _coordinates m
         <*> coordinates'
         <*> bounds'
         <*> _values m
diff --git a/src/Brick/Forms.hs b/src/Brick/Forms.hs
--- a/src/Brick/Forms.hs
+++ b/src/Brick/Forms.hs
@@ -3,9 +3,9 @@
 {-# LANGUAGE OverloadedStrings #-}
 {-# LANGUAGE CPP #-}
 {-# LANGUAGE ScopedTypeVariables #-}
--- | NOTE: This API is experimental and will probably change. Please try
--- it out! Feedback is very much appreciated, and your patience in the
--- face of breaking API changes is also appreciated! It's also worth
+-- | Note - This API is experimental and will probably change. Please
+-- try it out! Feedback is very much appreciated, and your patience in
+-- the face of breaking API changes is also appreciated! It's also worth
 -- bearing in mind that this API is designed to support a narrow range
 -- of use cases. If you find that you need more customization than this
 -- offers, then you will need to consider building your own layout and
@@ -32,6 +32,10 @@
 -- 'FormField's combined for a single 'FormFieldState' (e.g. a radio
 -- button sequence).
 --
+-- To use a 'Form', you must include it within your application state
+-- type. You can use 'formState' to access the underlying s whenever you
+-- need it. See @programs/FormDemo.hs@ for a complete working example.
+--
 -- Also note that, by default, forms and their field inputs are
 -- concatenated together in a 'vBox'. This can be customized on a
 -- per-field basis and for the entire form by using the functions
@@ -65,6 +69,7 @@
   -- * Simple form field constructors
   , editTextField
   , editShowableField
+  , editShowableFieldWithValidate
   , editPasswordField
   , radioField
   , checkboxField
@@ -83,6 +88,7 @@
 where
 
 import Graphics.Vty hiding (showCursor)
+import Control.Monad ((<=<))
 #if !(MIN_VERSION_base(4,11,0))
 import Data.Monoid
 #endif
@@ -188,7 +194,8 @@
                       } -> FormFieldState s e n
 
 -- | A form: a sequence of input fields that manipulate the fields of an
--- underlying state that you choose.
+-- underlying state that you choose. This value must be stored in the
+-- Brick application's state.
 --
 -- Type variables are as follows:
 --
@@ -557,6 +564,38 @@
 editShowableField stLens n =
     let ini = T.pack . show
         val = readMaybe . T.unpack . T.intercalate "\n"
+        limit = Just 1
+        renderText = txt . T.unlines
+    in editField stLens n limit ini val renderText id
+
+-- | A form field using a single-line editor to edit the 'Show' representation
+-- of a state field value of type @a@. This automatically uses its 'Read'
+-- instance to validate the input, and also accepts an additional user-defined
+-- pass for validation. This field is mostly useful in cases where the
+-- user-facing representation of a value matches the 'Show' representation
+-- exactly, such as with 'Int', but you don't want to accept just /any/ 'Int'.
+--
+-- This field responds to all events handled by 'editor', including
+-- mouse events.
+editShowableFieldWithValidate :: (Ord n, Show n, Read a, Show a)
+                              => Lens' s a
+                              -- ^ The state lens for this value.
+                              -> n
+                              -- ^ The resource name for the input field.
+                              -> (a -> Bool)
+                              -- ^ Additional validation step for input.
+                              -- 'True' indicates that the value is
+                              -- valid.
+                              -> s
+                              -- ^ The initial form state.
+                              -> FormFieldState s e n
+editShowableFieldWithValidate stLens n isValid =
+    let ini = T.pack . show
+        val ls = do
+            val <- readMaybe $ T.unpack $ T.intercalate "\n" ls
+            if isValid val
+               then return val
+               else Nothing
         limit = Just 1
         renderText = txt . T.unlines
     in editField stLens n limit ini val renderText id
diff --git a/tests/Main.hs b/tests/Main.hs
--- a/tests/Main.hs
+++ b/tests/Main.hs
@@ -60,16 +60,16 @@
 prop_compare l r = compare l r == compare (lower l) (lower r)
 
 prop_applicativeIdentity :: I -> Bool
-prop_applicativeIdentity v = (pure id <*> v) == v
+prop_applicativeIdentity v = (id <$> v) == v
 
 prop_applicativeComposition :: IMap (O -> O) -> IMap (O -> O) -> IMap O -> Bool
-prop_applicativeComposition u v w = (pure (.) <*> u <*> v <*> w) == (u <*> (v <*> w))
+prop_applicativeComposition u v w = ((.) <$> u <*> v <*> w) == (u <*> (v <*> w))
 
 prop_applicativeHomomorphism :: (O -> O) -> O -> Bool
-prop_applicativeHomomorphism f x = (pure f <*> pure x :: I) == pure (f x)
+prop_applicativeHomomorphism f x = (f <$> pure x :: I) == pure (f x)
 
 prop_applicativeInterchange :: IMap (O -> O) -> O -> Bool
-prop_applicativeInterchange u y = (u <*> pure y) == (pure ($ y) <*> u)
+prop_applicativeInterchange u y = (u <*> pure y) == (($ y) <$> u)
 
 prop_empty :: Bool
 prop_empty = lower (IMap.empty :: I) == IntMap.empty
