diff --git a/CHANGELOG b/CHANGELOG
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,3 +1,6 @@
+- 0.6.1.0
+    * Add the `null` query to Text.Blaze.Internal.
+
 - 0.6.0.0
     * Add the operator (!?) for nicely setting conditional attributes
 
diff --git a/blaze-markup.cabal b/blaze-markup.cabal
--- a/blaze-markup.cabal
+++ b/blaze-markup.cabal
@@ -1,5 +1,5 @@
 Name:         blaze-markup
-Version:      0.6.0.0
+Version:      0.6.1.0
 Homepage:     http://jaspervdj.be/blaze
 Bug-Reports:  http://github.com/jaspervdj/blaze-markup/issues
 License:      BSD3
@@ -51,7 +51,7 @@
 
   Build-depends:
     HUnit                      >= 1.2 && < 1.3,
-    QuickCheck                 >= 2.4 && < 2.7,
+    QuickCheck                 >= 2.4 && < 2.8,
     containers                 >= 0.3 && < 0.6,
     test-framework             >= 0.4 && < 0.9,
     test-framework-hunit       >= 0.3 && < 0.4,
diff --git a/src/Text/Blaze/Internal.hs b/src/Text/Blaze/Internal.hs
--- a/src/Text/Blaze/Internal.hs
+++ b/src/Text/Blaze/Internal.hs
@@ -58,10 +58,15 @@
       -- * Modifying Markup elements
     , contents
     , external
+
+      -- * Querying Markup elements
+    , null
     ) where
 
+import Prelude hiding (null)
 import Data.Monoid (Monoid, mappend, mempty, mconcat)
 import Unsafe.Coerce (unsafeCoerce)
+import qualified Data.List as List
 
 import Data.ByteString.Char8 (ByteString)
 import Data.Text (Text)
@@ -463,3 +468,29 @@
 contents (AddAttribute _ _ _ c)     = contents c
 contents (AddCustomAttribute _ _ c) = contents c
 contents _                          = Empty
+
+-- | Check if a 'Markup' value is completely empty (renders to the empty
+-- string).
+null :: MarkupM a -> Bool
+null markup = case markup of
+    Parent _ _ _ _           -> False
+    CustomParent _ _         -> False
+    Leaf _ _ _               -> False
+    CustomLeaf _ _           -> False
+    Content c                -> emptyChoiceString c
+    Append c1 c2             -> null c1 && null c2
+    AddAttribute _ _ _ c     -> null c
+    AddCustomAttribute _ _ c -> null c
+    Empty                    -> True
+  where
+    emptyChoiceString cs = case cs of
+        Static ss                -> emptyStaticString ss
+        String s                 -> List.null s
+        Text t                   -> T.null t
+        ByteString bs            -> B.null bs
+        PreEscaped c             -> emptyChoiceString c
+        External c               -> emptyChoiceString c
+        AppendChoiceString c1 c2 -> emptyChoiceString c1 && emptyChoiceString c2
+        EmptyChoiceString        -> True
+
+    emptyStaticString = B.null . getUtf8ByteString
diff --git a/tests/Text/Blaze/Tests.hs b/tests/Text/Blaze/Tests.hs
--- a/tests/Text/Blaze/Tests.hs
+++ b/tests/Text/Blaze/Tests.hs
@@ -1,16 +1,17 @@
 {-# LANGUAGE OverloadedStrings, FlexibleInstances, TypeSynonymInstances #-}
-{-# OPTIONS_GHC -fno-warn-orphans #-}
+{-# OPTIONS_GHC -fno-warn-orphans        #-}
+{-# OPTIONS_GHC -fno-warn-unused-do-bind #-}
 module Text.Blaze.Tests
     ( tests
     ) where
 
-import Prelude hiding (div, id)
-import Data.Monoid (mempty)
+import Prelude hiding (div, id, null)
+import Data.Monoid (mempty, mappend)
 import Control.Monad (replicateM)
 import Control.Applicative ((<$>))
 import Data.Word (Word8)
 import Data.Char (ord)
-import Data.List (isInfixOf)
+import qualified Data.List as List
 
 import Test.Framework (Test)
 import Test.HUnit (Assertion, (@=?))
@@ -37,6 +38,8 @@
 
         , testCase     "conditional attributes"    conditionalAttributes
         , testCase     "contents 1"                contents1
+        , testCase     "empty 1"                   empty1
+        , testCase     "empty 2"                   empty2
         ]
 
 -- | The left identity Monoid law.
@@ -101,7 +104,7 @@
 -- | Check if the "</" sequence does not appear in @<script>@ or @<style>@ tags.
 --
 externalEndSequence :: String -> Bool
-externalEndSequence = not . isInfixOf "</" . LBC.unpack
+externalEndSequence = not . List.isInfixOf "</" . LBC.unpack
                     . renderUsingUtf8 . external . string
 
 -- | Check that the "<>" characters are well-nested.
@@ -121,7 +124,7 @@
   where
     html = do
         p !? (4 > length [()], class_ "foo") $ "Hello"
-        p !? (null [()], class_ "bar") !? (True, id "2nd") $ "World"
+        p !? (List.null [()], class_ "bar") !? (True, id "2nd") $ "World"
 
 contents1 :: Assertion
 contents1 = "Hello World!" @=? renderUsingUtf8 (contents html)
@@ -132,6 +135,21 @@
         img ! name "An image"
         p "World!"
 
+empty1 :: Assertion
+empty1 = True @=? null html
+  where
+    html :: Markup
+    html = do
+        ""
+        ""
+        mempty
+
+empty2 :: Assertion
+empty2 = False @=? null html
+  where
+    html :: Markup
+    html = "" `mappend` "" `mappend` p "a"
+
 -- Show instance for the HTML type, so we can debug.
 --
 instance Show Markup where
@@ -157,7 +175,7 @@
 --
 arbitraryMarkup :: Int       -- ^ Maximum depth.
               -> Gen Markup  -- ^ Resulting arbitrary HTML snippet.
-arbitraryMarkup depth = do 
+arbitraryMarkup depth = do
     -- Choose the size (width) of this element.
     size <- choose (0, 3)
 
