diff --git a/README.md b/README.md
--- a/README.md
+++ b/README.md
@@ -107,6 +107,8 @@
   rendered verbatim.  (Note that no escaping is done;
   the assumption is that the calling program will escape
   the strings appropriately for the output format.)
+- If the value of the variable is a boolean value, it
+  will be rendered as `true` if true, or as empty if false.
 - If the value is a list, the values will be concatenated.
 - If the value is a map, the string `true` will be rendered.
 - Every other value will be rendered as the empty string.
@@ -116,8 +118,6 @@
 
 - If the value is a number, it will be rendered as an
   integer if possible, otherwise as a floating-point number.
-- If the value is a JSON boolean, it will be rendered as `true`
-  if true, and as the empty string if false.
 
 
 ## Conditionals
@@ -126,10 +126,17 @@
 matched delimiters) and ends with `endif` (enclosed in matched
 delimiters).  It may optionally contain an `else` (enclosed in
 matched delimiters).  The `if` section is used if
-`variable` has a non-empty value, otherwise the `else`
-section is used (if present).  (Note that even the
-string `false` counts as a true value.) Examples:
+`variable` has a true value, otherwise the `else`
+section is used (if present).  The following values
+count as true:
 
+- any map
+- any array containing at least one true value
+- any nonempty string (even `false`)
+- boolean True
+
+Examples:
+
 ```
 $if(foo)$bar$endif$
 
@@ -187,7 +194,7 @@
 
 A for loop begins with `for(variable)` (enclosed in
 matched delimiters) and ends with `endfor` (enclosed in matched
-delimiters.
+delimiters).
 
 - If `variable` is an array, the material inside the loop will
   be evaluated repeatedly, with `variable` being set to each
diff --git a/changelog.md b/changelog.md
--- a/changelog.md
+++ b/changelog.md
@@ -1,5 +1,51 @@
 # doctemplates
 
+# 0.11.0.1
+
+  * Bump version bounds for doclayout.
+
+  * Fix typos.
+
+# 0.11
+
+  * Remove HsYAML depenedency.
+
+  * Remove upper bound for criterion.
+
+## 0.10.0.2
+
+  * Use doclayout 0.4.
+
+## 0.10.0.1
+
+  * Don't rely on aeson Object being implemented as a HashMap.
+    This change is needed for doctemplates to compile against aeson 2.0.0.0.
+
+## 0.10
+
+  * Change rendering and conditional behavior with booleans.
+    Previously, `$if(foo)$` evaluated to false iff `foo`
+    would render as the empty string. This forced us to render
+    a boolean False value as an empty string, rather than `false`.
+    And this has caused various problems with templates
+    (#16, jgm/pandoc#7402).  Now, boolean False values render as
+    `false` -- just as True values render as `true`.  And conditionals
+    are now sensitive to booleans, so `$if(foo)$` evaluates to false
+    when `foo` is a boolean False value, even though it would render
+    as the nonempty string `false`.
+
+## 0.9
+
+  * Add BoolVal constructor to Val.  This gives a smoother
+    interface with JSON and YAML.  [API change]
+
+  * Remove overlapping instances by generalizing
+    `ToContext String String` and `FromContext String String`
+    to `TemplateTarget [a] => ToContext [a] [a]` and
+    `TemplateTarget [a] => FromContext [a] [a]`.
+    Remove the instance `ToContext String (Doc String)`.
+    Remove redundant constraints.  (#9, favonia) [API change]
+
 ## 0.8.3
 
   * Properly handle nested loops (#15).  Previously "it" was
diff --git a/doctemplates.cabal b/doctemplates.cabal
--- a/doctemplates.cabal
+++ b/doctemplates.cabal
@@ -1,5 +1,5 @@
 name:                doctemplates
-version:             0.8.3
+version:             0.11.0.1
 synopsis:            Pandoc-style document templates
 description:         This is the text templating system used by pandoc.
                      It supports variable interpolation, iteration,
@@ -32,15 +32,13 @@
                        safe,
                        text-conversions,
                        aeson,
-                       HsYAML >= 0.2 && < 0.3,
                        text,
-                       doclayout >= 0.3 && < 0.4,
+                       doclayout >= 0.4 && < 0.6,
                        containers,
                        vector,
                        filepath,
                        parsec,
                        mtl,
-                       unordered-containers,
                        scientific
   if !impl(ghc >= 8.0)
     build-depends:     semigroups == 0.18.*
@@ -53,7 +51,7 @@
   main-is:             test.hs
   build-depends:       base,
                        doctemplates,
-                       doclayout >= 0.3 && < 0.4,
+                       doclayout >= 0.4 && < 0.6,
                        containers,
                        aeson,
                        Glob,
@@ -72,9 +70,9 @@
   Main-Is:         bench.hs
   Hs-Source-Dirs:  bench
   Build-Depends:   doctemplates,
-                   doclayout >= 0.3 && < 0.4,
+                   doclayout >= 0.4 && < 0.6,
                    base >= 4.8 && < 5,
-                   criterion >= 1.0 && < 1.6,
+                   criterion >= 1.0,
                    filepath,
                    aeson,
                    text,
diff --git a/src/Text/DocTemplates.hs b/src/Text/DocTemplates.hs
--- a/src/Text/DocTemplates.hs
+++ b/src/Text/DocTemplates.hs
@@ -104,6 +104,8 @@
     verbatim. (Note that no escaping is done; the assumption is that the
     calling program will escape the strings appropriately for the output
     format.)
+-   If the value of the variable is a boolean value, it will be rendered
+    as @true@ if true, or as empty if false.
 -   If the value is a list, the values will be concatenated.
 -   If the value is a map, the string @true@ will be rendered.
 -   Every other value will be rendered as the empty string.
@@ -113,17 +115,21 @@
 
 -   If the value is a number, it will be rendered as an integer if
     possible, otherwise as a floating-point number.
--   If the value is a JSON boolean, it will be rendered as @true@ if
-    true, and as the empty string if false.
 
 == Conditionals
 
 A conditional begins with @if(variable)@ (enclosed in matched
 delimiters) and ends with @endif@ (enclosed in matched delimiters). It
 may optionally contain an @else@ (enclosed in matched delimiters). The
-@if@ section is used if @variable@ has a non-empty value, otherwise the
-@else@ section is used (if present). (Note that even the string @false@
-counts as a true value.) Examples:
+@if@ section is used if @variable@ has a true value, otherwise the
+@else@ section is used (if present). The following values count as true:
+
+-   any map
+-   any array containing at least one true value
+-   any nonempty string (even @false@)
+-   boolean True
+
+Examples:
 
 > $if(foo)$bar$endif$
 >
diff --git a/src/Text/DocTemplates/Internal.hs b/src/Text/DocTemplates/Internal.hs
--- a/src/Text/DocTemplates/Internal.hs
+++ b/src/Text/DocTemplates/Internal.hs
@@ -37,7 +37,6 @@
 
 import Data.Text.Conversions (FromText(..), ToText(..))
 import Data.Aeson (Value(..), ToJSON(..), FromJSON(..), Result(..), fromJSON)
-import Data.YAML (ToYAML(..), FromYAML(..), Node(..), Scalar(..))
 import Control.Monad.Identity
 import qualified Control.Monad.State.Strict as S
 import Data.Char (chr, ord)
@@ -53,7 +52,6 @@
 import GHC.Generics (Generic)
 import Data.Text (Text)
 import qualified Data.Map as M
-import qualified Data.HashMap.Strict as H
 import qualified Data.Vector as V
 import Data.Scientific (floatingOrInteger)
 import Data.List (intersperse)
@@ -129,7 +127,7 @@
   mappend = (<>)
 
 type TemplateTarget a =
-  (Monoid a, IsString a, HasChars a, ToText a, FromText a)
+  (HasChars a, ToText a, FromText a)
 
 -- | A 'Context' defines values for template's variables.
 newtype Context a = Context { unContext :: M.Map Text (Val a) }
@@ -141,6 +139,7 @@
     SimpleVal  (Doc a)
   | ListVal    [Val a]
   | MapVal     (Context a)
+  | BoolVal    Bool
   | NullVal
   deriving (Show, Traversable, Foldable, Functor, Data, Typeable)
 
@@ -166,25 +165,19 @@
 instance ToContext a a => ToContext a (Doc a) where
   toVal     = SimpleVal
 
--- This is needed because otherwise the compiler tries to
--- match on ToContext a [b], with a = b = Char, even though
--- we don't have ToContext Char Char.  I don't understand why.
-instance {-# OVERLAPPING #-} ToContext String String where
-  toVal    = SimpleVal . DL.literal
-
-instance {-# OVERLAPPING #-} ToContext String (Doc String) where
-  toVal    = SimpleVal
-
 instance ToContext a b => ToContext a [b] where
   toVal     = ListVal . map toVal
 
+instance {-# OVERLAPPING #-} TemplateTarget [a] => ToContext [a] [a] where
+  toVal    = SimpleVal . DL.literal
+
 instance ToContext a b => ToContext a (M.Map Text b) where
   toVal     = MapVal . toContext
   toContext = Context . M.map toVal
 
 instance TemplateTarget a => ToContext a Bool where
-  toVal True  = SimpleVal "true"
-  toVal False = NullVal
+  toVal True  = BoolVal True
+  toVal False = BoolVal False
 
 instance TemplateTarget a => ToContext a Value where
   toContext x = case fromJSON x of
@@ -212,10 +205,7 @@
   fromVal (SimpleVal x) = Just (DL.render Nothing x)
   fromVal _             = Nothing
 
--- This is needed because otherwise the compiler tries to
--- match on FromContext a [b], with a = b = Char, even though
--- we don't have FromContext Char Char.  I don't understand why.
-instance {-# OVERLAPPING #-} FromContext String String where
+instance {-# OVERLAPPING #-} TemplateTarget [a] => FromContext [a] [a] where
   fromVal (SimpleVal x) = Just (DL.render Nothing x)
   fromVal _             = Nothing
 
@@ -232,9 +222,8 @@
                               case floatingOrInteger n of
                                   Left (r :: Double)   -> show r
                                   Right (i :: Integer) -> show i
-      Bool True   -> return $ SimpleVal "true"
-      Object o    -> MapVal . Context . M.fromList . H.toList <$>
-                       mapM parseJSON o
+      Bool b      -> return $ BoolVal b
+      Object _    -> MapVal . Context <$> parseJSON v
       _           -> return NullVal
 
 instance TemplateTarget a => FromJSON (Context a) where
@@ -244,28 +233,6 @@
       MapVal o -> return o
       _        -> fail "Expecting MapVal"
 
-instance TemplateTarget a => FromYAML (Val a) where
-  parseYAML v =
-    case v of
-      Mapping _ _ m -> MapVal . Context . M.fromList <$>
-                           mapM (\(key, val) -> do
-                                  val' <- parseYAML val
-                                  key' <- parseYAML key
-                                  return (key', val')) (M.toList m)
-      Sequence _ _ xs -> ListVal <$> mapM parseYAML xs
-      Scalar _ (SStr t) -> return $ SimpleVal $ fromString . fromText $ t
-      Scalar _ (SFloat n) -> return $ SimpleVal $ fromString . show $ n
-      Scalar _ (SInt n) -> return $ SimpleVal $ fromString . show $ n
-      Scalar _ (SBool True) -> return $ SimpleVal "true"
-      _           -> return NullVal
-
-instance TemplateTarget a => FromYAML (Context a) where
-  parseYAML v = do
-    val <- parseYAML v
-    case val of
-      MapVal o -> return o
-      _        -> fail "Expecting MapVal"
-
 instance TemplateTarget a => ToJSON (Context a) where
   toJSON (Context m) = toJSON m
 
@@ -274,15 +241,7 @@
   toJSON (MapVal m) = toJSON m
   toJSON (ListVal xs) = toJSON xs
   toJSON (SimpleVal d) = toJSON $ toText $ DL.render Nothing d
-
-instance TemplateTarget a => ToYAML (Context a) where
-  toYAML (Context m) = toYAML m
-
-instance TemplateTarget a => ToYAML (Val a) where
-  toYAML NullVal = toYAML (Nothing :: Maybe Text)
-  toYAML (MapVal m) = toYAML m
-  toYAML (ListVal xs) = toYAML xs
-  toYAML (SimpleVal d) = toYAML $ toText $ DL.render Nothing d
+  toJSON (BoolVal b) = toJSON b
 
 mapDoc :: TemplateTarget a => (Doc a -> Doc a) -> Val a -> Val a
 mapDoc f val =
@@ -290,6 +249,7 @@
     SimpleVal d        -> SimpleVal (f d)
     MapVal (Context m) -> MapVal (Context $ M.map (mapDoc f) m)
     ListVal xs         -> ListVal $ map (mapDoc f) xs
+    BoolVal b          -> BoolVal b
     NullVal            -> NullVal
 
 mapText :: TemplateTarget a => (Text -> Text) -> Val a -> Val a
@@ -303,6 +263,7 @@
            SimpleVal d        -> T.length . toText $ DL.render Nothing d
            MapVal (Context m) -> M.size m
            ListVal xs         -> length xs
+           BoolVal _          -> 0
            NullVal            -> 0
 applyPipe ToUppercase val = mapText T.toUpper val
 applyPipe ToLowercase val = mapText T.toLower val
@@ -400,18 +361,34 @@
     Just v' -> multiLookup vs v'
 multiLookup _ _ = NullVal
 
-resolveVariable :: TemplateTarget a => Variable -> Context a -> [Doc a]
+-- The Bool indicates whether it's a true or false value.
+data Resolved a = Resolved Bool [Doc a]
+   deriving (Show, Read, Data, Typeable, Generic, Eq, Ord,
+             Foldable, Traversable, Functor)
+
+instance Semigroup (Resolved a) where
+  Resolved b1 x1 <> Resolved b2 x2 = Resolved (b1 || b2) (x1 <> x2)
+
+instance Monoid (Resolved a) where
+  mappend = (<>)
+  mempty = Resolved False []
+
+resolveVariable :: TemplateTarget a
+                => Variable -> Context a -> Resolved a
 resolveVariable v ctx = resolveVariable' v (MapVal ctx)
 
-resolveVariable' :: TemplateTarget a => Variable -> Val a -> [Doc a]
+resolveVariable' :: TemplateTarget a
+                 => Variable -> Val a -> Resolved a
 resolveVariable' v val =
   case applyPipes (varPipes v) $ multiLookup (varParts v) val of
-    ListVal xs    -> concatMap (resolveVariable' mempty) xs
+    ListVal xs    -> mconcat $ map (resolveVariable' mempty) xs
     SimpleVal d
-      | DL.isEmpty d -> []
-      | otherwise    -> [removeFinalNl d]
-    MapVal _      -> ["true"]
-    NullVal       -> []
+      | DL.isEmpty d -> Resolved False []
+      | otherwise    -> Resolved True [removeFinalNl d]
+    MapVal _      -> Resolved True ["true"]
+    BoolVal True  -> Resolved True ["true"]
+    BoolVal False -> Resolved False ["false"]
+    NullVal       -> Resolved False []
 
 removeFinalNl :: Doc a -> Doc a
 removeFinalNl DL.NewLine        = mempty
@@ -456,12 +433,13 @@
 renderTemp :: forall a . TemplateTarget a
            => Template a -> Context a -> RenderState (Doc a)
 renderTemp (Literal t) _ = updateColumn t
-renderTemp (Interpolate v) ctx = updateColumn $ mconcat $ resolveVariable v ctx
+renderTemp (Interpolate v) ctx =
+  case resolveVariable v ctx of
+    Resolved _ xs -> updateColumn (mconcat xs)
 renderTemp (Conditional v ift elset) ctx =
-  let res = resolveVariable v ctx
-   in case res of
-        [] -> renderTemp elset ctx
-        _  -> renderTemp ift ctx
+  case resolveVariable v ctx of
+    Resolved False _ -> renderTemp elset ctx
+    Resolved True _  -> renderTemp ift ctx
 renderTemp (Iterate v t sep) ctx = do
   xs <- withVariable v ctx (renderTemp t)
   sep' <- renderTemp sep ctx
diff --git a/test/boolean.test b/test/boolean.test
new file mode 100644
--- /dev/null
+++ b/test/boolean.test
@@ -0,0 +1,13 @@
+{ "foo": true,
+  "bar": false
+}
+.
+$foo$
+$bar$
+$if(foo)$XXX$else$YYY$endif$
+$if(bar)$XXX$else$YYY$endif$
+.
+true
+false
+XXX
+YYY
diff --git a/test/keyval.txt b/test/keyval.txt
deleted file mode 100644
--- a/test/keyval.txt
+++ /dev/null
@@ -1,1 +0,0 @@
-
