diff --git a/Text/Hamlet.hs b/Text/Hamlet.hs
--- a/Text/Hamlet.hs
+++ b/Text/Hamlet.hs
@@ -24,6 +24,8 @@
     , HtmlUrlI18n
     , ihamlet
     , ihamletFile
+      -- * Type classes
+    , ToAttributes (..)
       -- * Internal, for making more
     , HamletSettings
     , hamletWithSettings
@@ -45,7 +47,33 @@
 import Text.Blaze (Html, preEscapedText, toHtml)
 import qualified Data.Foldable as F
 import Control.Monad (mplus)
+import Data.Monoid (mempty, mappend)
+import Control.Arrow ((***))
 
+-- | Convert some value to a list of attribute pairs.
+class ToAttributes a where
+    toAttributes :: a -> [(Text, Text)]
+instance ToAttributes (Text, Text) where
+    toAttributes = return
+instance ToAttributes (String, String) where
+    toAttributes (k, v) = [(pack k, pack v)]
+instance ToAttributes [(Text, Text)] where
+    toAttributes = id
+instance ToAttributes [(String, String)] where
+    toAttributes = map (pack *** pack)
+
+attrsToHtml :: [(Text, Text)] -> Html
+attrsToHtml =
+    foldr go mempty
+  where
+    go (k, v) rest =
+        toHtml " "
+        `mappend` preEscapedText k
+        `mappend` preEscapedText (pack "=\"")
+        `mappend` toHtml v
+        `mappend` preEscapedText (pack "\"")
+        `mappend` rest
+
 type Render url = url -> [(Text, Text)] -> Text
 type Translate msg = msg -> Html
 
@@ -165,6 +193,9 @@
         Nothing -> error "Message interpolation used, but no message renderer provided"
         Just wrender -> wrender $ \render ->
             return $ hrFromHtml hr `AppE` (render `AppE` derefToExp scope d)
+contentToExp _ hr scope (ContentAttrs d) = do
+    html <- [|attrsToHtml . toAttributes|]
+    return $ hrFromHtml hr `AppE` (html `AppE` derefToExp scope d)
 
 shamlet :: QuasiQuoter
 shamlet = hamletWithSettings htmlRules defaultHamletSettings
diff --git a/Text/Hamlet/Parse.hs b/Text/Hamlet/Parse.hs
--- a/Text/Hamlet/Parse.hs
+++ b/Text/Hamlet/Parse.hs
@@ -22,7 +22,7 @@
 import Text.ParserCombinators.Parsec hiding (Line)
 import Data.Set (Set)
 import qualified Data.Set as Set
-import Data.Maybe (mapMaybe)
+import Data.Maybe (mapMaybe, fromMaybe)
 
 data Result v = Error String | Ok v
     deriving (Show, Eq, Read, Data, Typeable)
@@ -42,6 +42,7 @@
              | ContentUrl Bool Deref -- ^ bool: does it include params?
              | ContentEmbed Deref
              | ContentMsg Deref
+             | ContentAttrs Deref
     deriving (Show, Eq, Read, Data, Typeable)
 
 data Line = LineForall Deref Binding
@@ -55,9 +56,10 @@
           | LineOf [Ident]
           | LineTag
             { _lineTagName :: String
-            , _lineAttr :: [(Maybe Deref, String, [Content])]
+            , _lineAttr :: [(Maybe Deref, String, Maybe [Content])]
             , _lineContent :: [Content]
             , _lineClasses :: [(Maybe Deref, [Content])]
+            , _lineAttrs :: [Deref]
             }
           | LineContent [Content]
     deriving (Eq, Show, Read)
@@ -236,15 +238,22 @@
         tagClass (Just d) <|> tagAttrib (Just d)
     tagClass x = char '.' >> (TagClass . (,) x) <$> tagAttribValue NotInQuotes
     tagAttrib cond = do
-        s <- many1 $ noneOf " \t=\r\n>"
-        v <- (char '=' >> tagAttribValue NotInQuotesAttr) <|> return []
+        s <- many1 $ noneOf " \t=\r\n><"
+        v <- (char '=' >> Just <$> tagAttribValue NotInQuotesAttr) <|> return Nothing
         return $ TagAttrib (cond, s, v)
-    tag' = foldr tag'' ("div", [], [])
-    tag'' (TagName s) (_, y, z) = (s, y, z)
-    tag'' (TagIdent s) (x, y, z) = (x, (Nothing, "id", s) : y, z)
-    tag'' (TagClass s) (x, y, z) = (x, y, s : z)
-    tag'' (TagAttrib s) (x, y, z) = (x, s : y, z)
 
+    tagAttrs = do
+        _ <- char '*'
+        d <- between (char '{') (char '}') parseDeref
+        return $ TagAttribs d
+
+    tag' = foldr tag'' ("div", [], [], [])
+    tag'' (TagName s) (_, y, z, as) = (s, y, z, as)
+    tag'' (TagIdent s) (x, y, z, as) = (x, (Nothing, "id", Just s) : y, z, as)
+    tag'' (TagClass s) (x, y, z, as) = (x, y, s : z, as)
+    tag'' (TagAttrib s) (x, y, z, as) = (x, s : y, z, as)
+    tag'' (TagAttribs s) (x, y, z, as) = (x, y, z, s : as)
+
     ident :: Parser Ident
     ident = Ident <$> many1 (alphaNum <|> char '_' <|> char '\'')
 
@@ -264,19 +273,21 @@
         _ <- char '<'
         name' <- many  $ noneOf " \t.#\r\n!>"
         let name = if null name' then "div" else name'
-        xs <- many $ try ((many $ oneOf " \t") >>
-              (tagIdent <|> tagCond <|> tagClass Nothing <|> tagAttrib Nothing))
-        _ <- many $ oneOf " \t"
-        c <- (eol >> return []) <|> (char '>' >> content InContent)
-        let (tn, attr, classes) = tag' $ TagName name : xs
+        xs <- many $ try ((many $ oneOf " \t\r\n") >>
+              (tagIdent <|> tagCond <|> tagClass Nothing <|> tagAttrs <|> tagAttrib Nothing))
+        _ <- many $ oneOf " \t\r\n"
+        _ <- char '>'
+        c <- content InContent
+        let (tn, attr, classes, attrsd) = tag' $ TagName name : xs
         if '/' `elem` tn
           then fail "A tag name may not contain a slash. Perhaps you have a closing tag in your HTML."
-          else return $ LineTag tn attr c classes
+          else return $ LineTag tn attr c classes attrsd
 
 data TagPiece = TagName String
               | TagIdent [Content]
               | TagClass (Maybe Deref, [Content])
-              | TagAttrib (Maybe Deref, String, [Content])
+              | TagAttrib (Maybe Deref, String, Maybe [Content])
+              | TagAttribs Deref
     deriving Show
 
 data ContentRule = InQuotes | NotInQuotes | NotInQuotesAttr | InContent
@@ -330,9 +341,9 @@
     cases <- mapM getOf inside
     rest' <- nestToDoc set rest
     Ok $ DocCase d cases : rest'
-nestToDoc set (Nest (LineTag tn attrs content classes) inside:rest) = do
+nestToDoc set (Nest (LineTag tn attrs content classes attrsD) inside:rest) = do
     let attrFix (x, y, z) = (x, y, [(Nothing, z)])
-    let takeClass (a, "class", b) = Just (a, b)
+    let takeClass (a, "class", b) = Just (a, fromMaybe [] b)
         takeClass _ = Nothing
     let clazzes = classes ++ mapMaybe takeClass attrs
     let notClass (_, x, _) = x /= "class"
@@ -340,7 +351,7 @@
     let attrs' =
             case clazzes of
               [] -> map attrFix noclass
-              _ -> (Nothing, "class", clazzes)
+              _ -> (Nothing, "class", map (second Just) clazzes)
                        : map attrFix noclass
     let closeStyle =
             if not (null content) || not (null inside)
@@ -361,6 +372,7 @@
     rest' <- nestToDoc set rest
     Ok $ start
        : attrs''
+      ++ map (DocContent . ContentAttrs) attrsD
       ++ seal
        : map DocContent content
       ++ inside'
@@ -408,12 +420,12 @@
     ds <- nestToDoc set ns
     return $ compressDoc ds
 
-attrToContent :: (Maybe Deref, String, [(Maybe Deref, [Content])]) -> [Doc]
+attrToContent :: (Maybe Deref, String, [(Maybe Deref, Maybe [Content])]) -> [Doc]
 attrToContent (Just cond, k, v) =
     [DocCond [(cond, attrToContent (Nothing, k, v))] Nothing]
 attrToContent (Nothing, k, []) = [DocContent $ ContentRaw $ ' ' : k]
-attrToContent (Nothing, k, [(Nothing, [])]) = [DocContent $ ContentRaw $ ' ' : k]
-attrToContent (Nothing, k, [(Nothing, v)]) =
+attrToContent (Nothing, k, [(Nothing, Nothing)]) = [DocContent $ ContentRaw $ ' ' : k]
+attrToContent (Nothing, k, [(Nothing, Just v)]) =
     DocContent (ContentRaw (' ' : k ++ "=\""))
   : map DocContent v
   ++ [DocContent $ ContentRaw "\""]
@@ -423,16 +435,16 @@
     ++ go' (last v)
     ++ [DocContent $ ContentRaw "\""]
   where
-    go (Nothing, x) = map DocContent x ++ [DocContent $ ContentRaw " "]
+    go (Nothing, x) = map DocContent (fromMaybe [] x) ++ [DocContent $ ContentRaw " "]
     go (Just b, x) =
         [ DocCond
-            [(b, map DocContent x ++ [DocContent $ ContentRaw " "])]
+            [(b, map DocContent (fromMaybe [] x) ++ [DocContent $ ContentRaw " "])]
             Nothing
         ]
-    go' (Nothing, x) = map DocContent x
+    go' (Nothing, x) = maybe [] (map DocContent) x
     go' (Just b, x) =
         [ DocCond
-            [(b, map DocContent x)]
+            [(b, maybe [] (map DocContent) x)]
             Nothing
         ]
 
diff --git a/Text/Hamlet/RT.hs b/Text/Hamlet/RT.hs
--- a/Text/Hamlet/RT.hs
+++ b/Text/Hamlet/RT.hs
@@ -83,6 +83,9 @@
         return $ SDTemplate y
     convert (DocContent ContentMsg{}) =
         error "Runtime hamlet does not currently support message interpolation"
+    convert (DocContent ContentAttrs{}) =
+        error "Runtime hamlet does not currently support attrs interpolation"
+
     convert x@(DocCond conds els) = do
         conds' <- mapM go conds
         els' <- maybe (return []) (mapM convert) els
diff --git a/hamlet.cabal b/hamlet.cabal
--- a/hamlet.cabal
+++ b/hamlet.cabal
@@ -1,14 +1,14 @@
 name:            hamlet
-version:         0.10.9.1
+version:         1.0.0
 license:         BSD3
 license-file:    LICENSE
 author:          Michael Snoyman <michael@snoyman.com>
 maintainer:      Michael Snoyman <michael@snoyman.com>
 synopsis:        Haml-like template files that are compile-time checked
 description:
-    Hamlet gives you a type-safe tool for generating HTML code. It works via Quasi-Quoting, and generating extremely efficient output code. The syntax is white-space sensitive, and it helps you avoid cross-site scripting issues and 404 errors. Please see the documentation at <http://docs.yesodweb.com/book/hamlet/> for more details.
+    Hamlet gives you a type-safe tool for generating HTML code. It works via Quasi-Quoting, and generating extremely efficient output code. The syntax is white-space sensitive, and it helps you avoid cross-site scripting issues and 404 errors. Please see the documentation at <http://www.yesodweb.com/book/shakespearean-templates> for more details.
     .
-    Here is a quick overview of hamlet html. Due to haddock escaping issues, we can't properly show variable insertion, but we are still going to show some conditionals. Please see <http://www.yesodweb.com/book/templates> for a thorough description
+    Here is a quick overview of hamlet html. Due to haddock escaping issues, we can't properly show variable insertion, but we are still going to show some conditionals. Please see <http://www.yesodweb.com/book/shakespearean-templates> for a thorough description
     .
     > !!!
     > <html>
@@ -25,7 +25,7 @@
 stability:       Stable
 cabal-version:   >= 1.8
 build-type:      Simple
-homepage:        http://www.yesodweb.com/book/templates
+homepage:        http://www.yesodweb.com/book/shakespearean-templates
 extra-source-files:
   test/hamlets/double-foralls.hamlet
   test/hamlets/external-debug.hamlet
@@ -41,14 +41,14 @@
 
 library
     build-depends:   base             >= 4       && < 5
-                   , shakespeare      >= 0.10.2  && < 0.12
+                   , shakespeare      >= 1.0     && < 1.1
                    , bytestring       >= 0.9     && < 0.10
                    , template-haskell
                    , blaze-html       >= 0.4     && < 0.5
                    , parsec           >= 2       && < 4
                    , failure          >= 0.1     && < 0.3
                    , text             >= 0.7     && < 0.12
-                   , containers       >= 0.2     && < 0.5
+                   , containers       >= 0.2
                    , blaze-builder    >= 0.2     && < 0.4
                    , process          >= 1.0     && < 1.2
     exposed-modules: Text.Hamlet
@@ -64,7 +64,7 @@
     type: exitcode-stdio-1.0
 
     ghc-options:   -Wall
-    build-depends: hamlet           >= 0.10    && < 0.11
+    build-depends: hamlet
                  , base             >= 4       && < 5
                  , parsec           >= 2       && < 4
                  , containers       >= 0.2     && < 0.5
diff --git a/test/HamletTest.hs b/test/HamletTest.hs
--- a/test/HamletTest.hs
+++ b/test/HamletTest.hs
@@ -83,7 +83,7 @@
 
   , it "ignores a blank line" $ do
     helper "<p>foo</p>" [hamlet|
-<p
+<p>
 
     foo
 
@@ -96,7 +96,7 @@
   , it "hamlet angle bracket syntax" $
       helper "<p class=\"foo\" height=\"100\"><span id=\"bar\" width=\"50\">HELLO</span></p>"
         [hamlet|
-<p.foo height="100"
+<p.foo height="100">
     <span #bar width=50>HELLO
 |]
 
@@ -158,7 +158,7 @@
     helper "<p>1</p><p>2 not ignored</p>" [hamlet|
 <p>1
 <!-- ignored comment -->
-<p
+<p>
     2
     <!-- ignored --> not ignored<!-- ignored -->
 |]
@@ -192,13 +192,13 @@
 
   , it "conditional class" $ do
       helper "<p class=\"current\"></p>" 
-        [hamlet|<p :False:.ignored :True:.current|]
+        [hamlet|<p :False:.ignored :True:.current>|]
 
       helper "<p class=\"1 3 2 4\"></p>"
-        [hamlet|<p :True:.1 :True:class=2 :False:.a :False:class=b .3 class=4|]
+        [hamlet|<p :True:.1 :True:class=2 :False:.a :False:class=b .3 class=4>|]
 
       helper "<p class=\"foo bar baz\"></p>"
-        [hamlet|<p class=foo class=bar class=baz|]
+        [hamlet|<p class=foo class=bar class=baz>|]
 
 
 
@@ -265,10 +265,12 @@
     $forall num <- [1, 2, 3]
         <li>#{show num}
 |]
+{-
   , it "infix operators" $
       helper "5" [hamlet|#{show $ (4 + 5) - (2 + 2)}|]
   , it "infix operators with parens" $
       helper "5" [hamlet|#{show (2 + 3)}|]
+      -}
   , it "doctypes" $ helper "<!DOCTYPE html>\n<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">\n" [hamlet|
 $doctype 5
 $doctype strict
@@ -338,6 +340,20 @@
         <dt>#{name}
         <dd>#{show age}
 |]
+
+  , it "multiline tags" $ helper
+      "<foo bar=\"baz\" bin=\"bin\">content</foo>" [hamlet|
+<foo bar=baz
+     bin=bin>content
+|]
+  , let attrs = [("bar", "baz"), ("bin", "<>\"&")]
+     in it "*{...} attributes" $ helper
+      "<foo bar=\"baz\" bin=\"&lt;&gt;&quot;&amp;\">content</foo>" [hamlet|
+<foo *{attrs}>content
+|]
+  , it "blank attr values" $ helper
+      "<foo bar=\"\" baz bin=\"\"></foo>"
+      [hamlet|<foo bar="" baz bin=>|]
   ]
 
 data Pair = Pair String Int
@@ -425,11 +441,11 @@
 caseTag :: Assertion
 caseTag = do
     helper "<p class=\"foo\"><div id=\"bar\">baz</div></p>" [hamlet|
-<p .foo
+<p .foo>
   <#bar>baz
 |]
     helper "<p class=\"foo.bar\"><div id=\"bar\">baz</div></p>" [hamlet|
-<p class=foo.bar
+<p class=foo.bar>
   <#bar>baz
 |]
 
@@ -560,30 +576,23 @@
 |]
 
 caseScriptNotEmpty :: Assertion
-caseScriptNotEmpty = helper "<script></script>" [hamlet|<script|]
+caseScriptNotEmpty = helper "<script></script>" [hamlet|<script>|]
 
 caseMetaEmpty :: Assertion
 caseMetaEmpty = do
-    helper "<meta>" [hamlet|<meta|]
-    helper "<meta/>" [xhamlet|<meta|]
     helper "<meta>" [hamlet|<meta>|]
     helper "<meta/>" [xhamlet|<meta>|]
 
 caseInputEmpty :: Assertion
 caseInputEmpty = do
-    helper "<input>" [hamlet|<input|]
-    helper "<input/>" [xhamlet|<input|]
     helper "<input>" [hamlet|<input>|]
     helper "<input/>" [xhamlet|<input>|]
 
 caseMultiClass :: Assertion
-caseMultiClass = do
-    helper "<div class=\"foo bar\"></div>" [hamlet|<.foo.bar|]
-    helper "<div class=\"foo bar\"></div>" [hamlet|<.foo.bar>|]
+caseMultiClass = helper "<div class=\"foo bar\"></div>" [hamlet|<.foo.bar>|]
 
 caseAttribOrder :: Assertion
-caseAttribOrder = do
-    helper "<meta 1 2 3>" [hamlet|<meta 1 2 3|]
+caseAttribOrder =
     helper "<meta 1 2 3>" [hamlet|<meta 1 2 3>|]
 
 caseNothing :: Assertion
@@ -646,11 +655,11 @@
 
 caseAttribCond :: Assertion
 caseAttribCond = do
-    helper "<select></select>" [hamlet|<select :False:selected|]
-    helper "<select selected></select>" [hamlet|<select :True:selected|]
-    helper "<meta var=\"foo:bar\">" [hamlet|<meta var=foo:bar|]
+    helper "<select></select>" [hamlet|<select :False:selected>|]
+    helper "<select selected></select>" [hamlet|<select :True:selected>|]
+    helper "<meta var=\"foo:bar\">" [hamlet|<meta var=foo:bar>|]
     helper "<select selected></select>"
-        [hamlet|<select :true theArg:selected|]
+        [hamlet|<select :true theArg:selected>|]
 
     helper "<select></select>" [hamlet|<select :False:selected>|]
     helper "<select selected></select>" [hamlet|<select :True:selected>|]
@@ -685,19 +694,19 @@
 caseQuotedAttribs :: Assertion
 caseQuotedAttribs =
     helper "<input type=\"submit\" value=\"Submit response\">" [hamlet|
-<input type=submit value="Submit response"
+<input type=submit value="Submit response">
 |]
 
 caseSpacedDerefs :: Assertion
 caseSpacedDerefs = do
     helper "&lt;var&gt;" [hamlet|#{var theArg}|]
-    helper "<div class=\"&lt;var&gt;\"></div>" [hamlet|<.#{var theArg}|]
+    helper "<div class=\"&lt;var&gt;\"></div>" [hamlet|<.#{var theArg}>|]
 
 caseAttribVars :: Assertion
 caseAttribVars = do
-    helper "<div id=\"&lt;var&gt;\"></div>" [hamlet|<##{var theArg}|]
-    helper "<div class=\"&lt;var&gt;\"></div>" [hamlet|<.#{var theArg}|]
-    helper "<div f=\"&lt;var&gt;\"></div>" [hamlet|< f=#{var theArg}|]
+    helper "<div id=\"&lt;var&gt;\"></div>" [hamlet|<##{var theArg}>|]
+    helper "<div class=\"&lt;var&gt;\"></div>" [hamlet|<.#{var theArg}>|]
+    helper "<div f=\"&lt;var&gt;\"></div>" [hamlet|< f=#{var theArg}>|]
 
     helper "<div id=\"&lt;var&gt;\"></div>" [hamlet|<##{var theArg}>|]
     helper "<div class=\"&lt;var&gt;\"></div>" [hamlet|<.#{var theArg}>|]
@@ -714,10 +723,10 @@
     helper
       "<table><tbody><tr><td>1</td></tr><tr><td>2</td></tr></tbody></table>"
       [hamlet|
-<table
-  <tbody
+<table>
+  <tbody>
     $forall user <- users
-        <tr
+        <tr>
          <td>#{user}
 |]
     helper
@@ -728,8 +737,8 @@
           , "</select>"
           ])
         [hamlet|
-<select #"#{name}" name=#{name}
-    <option :isBoolBlank val:selected
+<select #"#{name}" name=#{name}>
+    <option :isBoolBlank val:selected>
     <option value=true :isBoolTrue val:selected>Yes
     <option value=false :isBoolFalse val:selected>No
 |]
@@ -785,14 +794,14 @@
 caseHamlet' = do
     helper' "foo" [shamlet|foo|]
     helper' "foo" [xshamlet|foo|]
-    helper "<br>" $ const $ [shamlet|<br|]
-    helper "<br/>" $ const $ [xshamlet|<br|]
+    helper "<br>" $ const $ [shamlet|<br>|]
+    helper "<br/>" $ const $ [xshamlet|<br>|]
 
     -- new with generalized stuff
     helper' "foo" [shamlet|foo|]
     helper' "foo" [xshamlet|foo|]
-    helper "<br>" $ const $ [shamlet|<br|]
-    helper "<br/>" $ const $ [xshamlet|<br|]
+    helper "<br>" $ const $ [shamlet|<br>|]
+    helper "<br/>" $ const $ [xshamlet|<br>|]
 
 
 instance Show Url where
diff --git a/test/hamlets/external.hamlet b/test/hamlets/external.hamlet
--- a/test/hamlets/external.hamlet
+++ b/test/hamlets/external.hamlet
@@ -1,2 +1,2 @@
 #{foo}
-<br
+<br>
