diff --git a/descriptive.cabal b/descriptive.cabal
--- a/descriptive.cabal
+++ b/descriptive.cabal
@@ -1,5 +1,5 @@
 name:                descriptive
-version:             0.9.0
+version:             0.9.1
 synopsis:            Self-describing consumers/parsers; forms, cmd-line args, JSON, etc.
 description:         Self-describing consumers/parsers. See the README.md for more information. It is currently EXPERIMENTAL.
 stability:           Experimental
diff --git a/src/Descriptive/Form.hs b/src/Descriptive/Form.hs
--- a/src/Descriptive/Form.hs
+++ b/src/Descriptive/Form.hs
@@ -7,6 +7,7 @@
 module Descriptive.Form
   (-- * Combinators
    input
+  ,validate
   -- * Description
   ,Form (..)
   )
@@ -20,13 +21,13 @@
 import           Data.Text (Text)
 
 -- | Form descriptor.
-data Form
+data Form d
   = Input !Text
-  | Constraint !Text
+  | Constraint !d
   deriving (Show,Eq)
 
 -- | Consume any input value.
-input :: Monad m => Text -> Consumer (Map Text Text) Form m Text
+input :: Monad m => Text -> Consumer (Map Text Text) (Form d) m Text
 input name =
   consumer (return d)
            (do s <- get
@@ -34,3 +35,27 @@
                          Nothing -> Continued d
                          Just a -> Succeeded a))
   where d = Unit (Input name)
+
+-- | Validate a form input with a description of what's required.
+validate :: Monad m
+         => d                           -- ^ Description of what it expects.
+         -> (a -> StateT s m (Maybe b)) -- ^ Attempt to parse the value.
+         -> Consumer s (Form d) m a     -- ^ Consumer to add validation to.
+         -> Consumer s (Form d) m b     -- ^ A new validating consumer.
+validate d' check =
+  wrap (liftM wrapper)
+       (\d p ->
+          do s <- get
+             r <- p
+             case r of
+               (Failed e) -> return (Failed e)
+               (Continued e) ->
+                 return (Continued (wrapper e))
+               (Succeeded a) ->
+                 do r' <- check a
+                    case r' of
+                      Nothing ->
+                        do doc <- withStateT (const s) d
+                           return (Continued (wrapper doc))
+                      Just a' -> return (Succeeded a'))
+  where wrapper = Wrap (Constraint d')
diff --git a/src/test/Main.hs b/src/test/Main.hs
--- a/src/test/Main.hs
+++ b/src/test/Main.hs
@@ -94,15 +94,15 @@
                     [("password2","gob"),("password","go"),("token","woot")]) ==
          Succeeded "woot")
   where login =
-          validate
-            (Form.Constraint "confirmed password (entered the same twice)")
-            (\(x,y) ->
-               return (if x == y
-                          then Just y
-                          else Nothing))
-            ((,) <$>
-             Form.input "password" <*>
-             Form.input "password2") <|>
+          Form.validate
+                 "confirmed password (entered the same twice)"
+                 (\(x,y) ->
+                    return (if x == y
+                               then Just y
+                               else Nothing))
+                 ((,) <$>
+                  Form.input "password" <*>
+                  Form.input "password2") <|>
           Form.input "token"
 
 --------------------------------------------------------------------------------
