diff --git a/ChangeLog.md b/ChangeLog.md
new file mode 100644
--- /dev/null
+++ b/ChangeLog.md
@@ -0,0 +1,5 @@
+# Revision history for logfmt
+
+## 0.1.0.0 -- YYYY-mm-dd
+
+* First version. Released on an unsuspecting world.
diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,29 @@
+BSD 3-Clause License
+
+Copyright (c) 2021, Chris McKinlay
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+1. Redistributions of source code must retain the above copyright notice, this
+   list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright notice,
+   this list of conditions and the following disclaimer in the documentation
+   and/or other materials provided with the distribution.
+
+3. Neither the name of the copyright holder nor the names of its
+   contributors may be used to endorse or promote products derived from
+   this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/README.md b/README.md
new file mode 100644
--- /dev/null
+++ b/README.md
@@ -0,0 +1,1 @@
+# logfmt
diff --git a/Setup.hs b/Setup.hs
new file mode 100644
--- /dev/null
+++ b/Setup.hs
@@ -0,0 +1,2 @@
+import Distribution.Simple
+main = defaultMain
diff --git a/logfmt.cabal b/logfmt.cabal
new file mode 100644
--- /dev/null
+++ b/logfmt.cabal
@@ -0,0 +1,45 @@
+cabal-version:       >=1.10
+
+name:           logfmt
+version:        0.0.1
+synopsis:       Formatting
+description:    Please see the README on GitHub at https://github.com/cmk/logfmt#readme
+category:       Text, System
+homepage:       https://github.com/cmk/logfmt#readme
+bug-reports:    https://github.com/cmk/logfmt/issues
+author:         Chris McKinlay
+maintainer:     chris.mckinlay@gmail.com
+copyright:      2020-2021 Chris McKinlay
+license:        BSD3
+license-file:   LICENSE
+build-type:     Simple
+extra-source-files:
+      README.md
+      ChangeLog.md
+tested-with:
+      GHC == 8.10.1
+source-repository head
+  type: git
+  location: https://github.com/cmk/logfmt
+
+library
+  exposed-modules:
+      Data.Fmt
+      Data.Fmt.Ansi
+      Data.Fmt.Attr
+      Data.Fmt.Code
+      Data.Fmt.Html
+      Data.Fmt.Time
+
+  other-modules:
+      Paths_logfmt
+  hs-source-dirs:
+      src
+  build-depends:
+      base >=4.14 && <5
+    , bytestring
+    , profunctors
+    , fast-logger
+    , ansi-terminal
+    , time
+  default-language: Haskell2010
diff --git a/src/Data/Fmt.hs b/src/Data/Fmt.hs
new file mode 100644
--- /dev/null
+++ b/src/Data/Fmt.hs
@@ -0,0 +1,815 @@
+{-# LANGUAGE DerivingVia #-}
+{-# LANGUAGE FlexibleInstances #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE RankNTypes #-}
+{-# LANGUAGE StandaloneDeriving #-}
+{-# LANGUAGE TypeFamilies #-}
+
+module Data.Fmt (
+    Term,
+
+    -- * Type
+    LogFmt,
+    Fmt (..),
+    spr,
+    printf,
+    runFmt,
+    runLogFmt,
+    
+    -- * Fmt
+    fmt,
+    logFmt,
+    (%),
+    apply,
+    bind,
+    cat,
+    refmt,
+    replace1,
+    splitWith,
+
+    -- * Fmt1
+    Fmt1,
+    Fmt2,
+    fmt1,
+    fmt2,
+    fmt1_,
+    fmt2_,
+    (.%),
+    cat1,
+    cat1With,
+    split1With,
+
+    -- * Html
+    Html,
+    toHtml,
+    comment,
+    Attr (..),
+    Element (..),
+    (!?),
+
+    -- * Formatting
+    hsep,
+    vsep,
+    hang,
+    indent,
+    prefix,
+    suffix,
+    enclose,
+    tuple,
+    quotes,
+    quotes',
+    parens,
+    braces,
+    brackets,
+    backticks,
+
+    -- * Collections
+    left1,
+    right1,
+    either1,
+    maybe1,
+    list1,
+    jsonList,
+    yamlList,
+    jsonMap,
+    yamlMap,
+
+    -- * Re-exports
+    LogStr,
+    fromLogStr,
+    ToLogStr (..),
+    IsString (..),
+) where
+
+import Control.Applicative (Const (..), getConst)
+import Control.Arrow
+import Control.Category (Category (), (<<<), (>>>))
+import Data.ByteString (ByteString)
+import Data.ByteString.Builder (Builder)
+import Data.Foldable (toList)
+import Data.Profunctor
+import Data.String
+import GHC.Exts (IsList, Item)
+import System.Log.FastLogger (LogStr, ToLogStr (..), fromLogStr)
+import qualified Control.Category as C
+import qualified Data.ByteString.Builder as BL
+import qualified Data.ByteString.Char8 as B
+import qualified Data.ByteString.Lazy.Char8 as BL
+import qualified GHC.Exts as IsList (toList)
+import qualified Numeric as N
+
+{- $setup
+ >>> import Data.Printf
+ >>> :load Data.Fmt
+-}
+
+type Term = IO ()
+
+type LogFmt = Fmt LogStr
+
+{- | A formatter, implemented as an indexed continuation
+
+ When you construct formatters the first type
+ parameter, @r@, will remain polymorphic.  The second type
+ parameter, @a@, will change to reflect the types of the data that
+ will be formatted.  For example, in
+
+ @
+ person :: Fmt2 ByteString Int
+ person = \"Person's name is \" % t % \", age is \" % d
+ @
+
+ the first type parameter remains polymorphic, and the second type
+ parameter is @ByteString -> Int -> r@, which indicates that it formats a
+ 'ByteString' and an 'Int'.
+
+ When you run the formatter, for example with 'format', you provide
+ the arguments and they will be formatted into a string.
+
+ >>> format ("This person's name is " % s % ", their age is " % d) "Anne" 22
+ "This person's name is Anne, their age is 22"
+-}
+newtype Fmt m a b = Fmt {unFmt :: (m -> a) -> b}
+
+deriving via (Costar ((->) m) a) instance Functor (Fmt m a)
+deriving via (Costar ((->) m) a) instance Applicative (Fmt m a)
+deriving via (Costar ((->) m) a) instance Monad (Fmt m a)
+deriving via (Costar ((->) m)) instance Profunctor (Fmt m)
+deriving via (Costar ((->) m)) instance Closed (Fmt m)
+deriving via (Costar ((->) m)) instance Costrong (Fmt m)
+deriving via (Costar ((->) m)) instance Cochoice (Fmt m)
+
+instance (IsString s, Show a) => Show (Fmt LogStr s a) where
+    show = show . runLogFmt
+
+instance (IsString m, a ~ b) => IsString (Fmt m a b) where
+    fromString = fmt . fromString
+
+instance Semigroup m => Semigroup (Fmt1 m s a) where
+    (<>) = (.%)
+
+instance Monoid m => Monoid (Fmt1 m s a) where
+    mempty = Fmt (\k _ -> k mempty)
+
+instance Monoid m => Category (Fmt m) where
+    id = fmt mempty
+    (.) = (%)
+
+instance Monoid m => Arrow (Fmt m) where
+    arr f = Fmt $ \k -> f (k mempty)
+    x *** y = dimap fst (,) x <*> lmap snd y
+
+instance Monoid m => Strong (Fmt m) where
+    first' x = x *** C.id
+    second' x = C.id *** x
+
+{- | Run a monadic formatting expression.
+
+   Like the method of 'Text.Printf.PrintfType', 'spr' executes the formatting
+   commands contained in the expression and returns the result as a monadic
+   variable.
+
+   For example, note that the 'Data.Fmt.Html.li' tag repeats, while the
+   'Data.Fmt.Html.ul' tag does not: 
+
+   >>> :{
+    let contact = p "You can reach me at" % ul . spr . li $ do
+          c1 <- a ! href @String "https://example.com" $ "Website"
+          c2 <- a ! href @String "mailto:cmk@example.com" $ "Email"
+          pure $ c1 <> c2
+    in runLogStr contact
+   :}
+   "<p>You can reach me at</p><ul><li><a href=\"https://foo.com\">Web</a></li><li><a href=\"mailto:cmk@foo.com\">Email</a></li></ul>"
+-}
+spr :: IsString s => Fmt LogStr s m -> Fmt m a a
+spr = fmt . runLogFmt
+
+-- | Run a formatter and print out the text to stdout.
+{-# INLINE printf #-}
+printf :: Fmt LogStr Term a -> a
+printf = flip unFmt (B.putStrLn . fromLogStr)
+
+-- | Run a 'Fmt'.
+{-# INLINE runFmt #-}
+runFmt :: Fmt m m a -> a
+runFmt = flip unFmt id
+
+-- | Run a 'LogFmt'.
+{-# INLINE runLogFmt #-}
+runLogFmt :: IsString s => Fmt LogStr s a -> a
+runLogFmt = flip unFmt (fromString . B.unpack . fromLogStr)
+{-# SPECIALIZE runLogFmt :: Fmt LogStr BL.ByteString a -> a #-}
+{-# SPECIALIZE runLogFmt :: Fmt LogStr ByteString a -> a #-}
+{-# SPECIALIZE runLogFmt :: Fmt LogStr String a -> a #-}
+{-# SPECIALIZE runLogFmt :: Fmt LogStr LogStr a -> a #-}
+{-# SPECIALIZE runLogFmt :: Fmt LogStr Builder a -> a #-}
+
+-- | Format a constant value of type @m@.
+{-# INLINE fmt #-}
+fmt :: m -> Fmt m a a
+fmt m = Fmt ($ m)
+
+-- | Format a constant value of type @m@.
+{-# INLINE logFmt #-}
+logFmt :: ToLogStr m => m -> Fmt LogStr a a
+logFmt = fmt . toLogStr
+
+-- | Concatenate two formatters.
+infixr 0 %
+
+{-# INLINE (%) #-}
+(%) :: Semigroup m => Fmt m b c -> Fmt m a b -> Fmt m a c
+f % g =
+    f
+        `bind` \a ->
+            g
+                `bind` \b -> fmt (a <> b)
+
+-- | Apply a 'Fmt1' to a 'Fmt'.
+{-# INLINE apply #-}
+apply :: Fmt1 m s m -> Fmt m s a -> Fmt m s a
+apply (Fmt f) (Fmt a) = Fmt (a . f)
+
+-- | Indexed bind.
+{-# INLINE bind #-}
+bind :: Fmt m a1 b -> (m -> Fmt m a2 a1) -> Fmt m a2 b
+bind m f = Fmt $ \k -> unFmt m (\a -> unFmt (f a) k)
+
+-- | Concatenate a collection of formatters.
+{-# INLINE cat #-}
+cat :: (Monoid m, Foldable f) => f (Fmt m a a) -> Fmt m a a
+cat = foldr (%) C.id
+
+-- | Map over the the formatting @Monoid@.
+{-# INLINE refmt #-}
+refmt :: (m1 -> m2) -> Fmt m1 a b -> Fmt m2 a b
+refmt m12 (Fmt f) = Fmt $ \a -> f (a . m12)
+
+{- | Replace one occurance of a search term.
+
+ > replace1 "bar" "foo" "foobarbaz"
+ "foofoobaz"
+-}
+{-# INLINE replace1 #-}
+replace1 :: ByteString -> Fmt LogStr a a -> Fmt LogStr a b -> Fmt LogStr a b
+replace1 x y =
+    splitWith (B.breakSubstring x) $ \l r0 ->
+        case B.stripPrefix x r0 of
+            Nothing -> logFmt l
+            Just r -> cat [logFmt l, y, logFmt r]
+
+{-# INLINE splitWith #-}
+splitWith ::
+    (ByteString -> (ByteString, ByteString)) -> -- Splitter
+    (ByteString -> ByteString -> Fmt LogStr a2 a1) -> -- Joiner
+    Fmt LogStr a1 b ->
+    Fmt LogStr a2 b
+splitWith break join = flip bind $ uncurry join . break . fromLogStr
+
+-- Fmt1
+
+-------------------------
+
+{- | A unary higher-order formatter.
+
+ @ 'Fmt1' m s a ~ (m -> s) -> a -> s @
+-}
+type Fmt1 m s a = Fmt m s (a -> s)
+
+{- | A binary higher-order formatter.
+
+ @ 'Fmt2' m s a b ~ (m -> s) -> a -> b -> s @
+-}
+type Fmt2 m s a b = Fmt m s (a -> b -> s)
+
+{- | A ternary higher-order formatter.
+
+ @ 'Fmt3' m s a b c ~ (m -> s) -> a -> b -> c -> s @
+-}
+type Fmt3 m s a b c = Fmt m s (a -> b -> c -> s)
+
+{- | Format a value of type @a@ using a function of type @a -> m@.
+
+ @ 'runFmt' . 'fmt1' :: (a -> m) -> a -> m @
+-}
+{-# INLINE fmt1 #-}
+fmt1 :: (a -> m) -> Fmt1 m s a
+fmt1 f = Fmt $ \k -> k . f
+
+{-# INLINE fmt2 #-}
+fmt2 :: (a -> b -> m) -> Fmt2 m s a b
+fmt2 f = Fmt $ \k -> fmap k . f
+
+{-# INLINE fmt1_ #-}
+fmt1_ :: Fmt m a a -> Fmt1 m a b
+fmt1_ = lmap const . closed
+
+{-# INLINE fmt2_ #-}
+fmt2_ :: Fmt m a a -> Fmt2 m a b c
+fmt2_ = lmap (const . const) . (closed . closed)
+
+-- | Concatenate two formatters, applying both to the same input.
+infixr 6 .%
+
+{-# INLINE (.%) #-}
+(.%) :: Semigroup m => Fmt1 m s a -> Fmt1 m s a -> Fmt1 m s a
+f .% g =
+    Fmt
+        ( \k a ->
+            unFmt f (\b1 -> unFmt g (\b2 -> k (b1 <> b2)) a) a
+        )
+
+{- | Format each value in a list and concatenate them all:
+
+ >>> runFmt (cat1 (s % " ")) ["one", "two", "three"]
+ "one two three "
+-}
+{-# INLINE cat1 #-}
+cat1 :: (Monoid m, Foldable f) => Fmt1 m m a -> Fmt1 m s (f a)
+cat1 f = fmt1 $ foldMap (runFmt f)
+
+{- | Use the given text-joining function to join together the individually rendered items of a list.
+
+ >>> runLogFmt (cat1With (mconcat . reverse) d) [123, 456, 789]
+ "789456123"
+
+ @
+ 'cat1With' 'L.unlines' :: 'Foldable' f => 'Fmt1' 'LogStr' 'String' a -> 'Fmt1' 'LogStr' s (f a)
+ 'cat1With' 'T.unlines' :: 'Foldable' f => 'Fmt1' 'LogStr' 'T.Text' a -> 'Fmt1' 'LogStr' s (f a)
+ 'cat1With' 'B.unlines' :: 'Foldable' f => 'Fmt1' 'LogStr' 'B.ByteString' a -> 'Fmt1' 'LogStr' s (f a)
+ 'cat1With' '$' 'L.intercalate' " " :: 'Foldable' f => 'Fmt1' 'LogStr' 'String' a -> 'Fmt1' 'LogStr' s (f a)
+ 'cat1With' '$' 'T.intercalate' " " :: 'Foldable' f => 'Fmt1' 'LogStr' 'T.Text' a -> 'Fmt1' 'LogStr' s (f a)
+ 'cat1With' '$' 'B.intercalate' " " :: 'Foldable' f => 'Fmt1' 'LogStr' 'B.ByteString' a -> 'Fmt1' 'LogStr' s (f a)
+ @
+-}
+{-# INLINEABLE cat1With #-}
+cat1With ::
+    (Foldable f, ToLogStr str, IsString str) =>
+    ([str] -> str) ->
+    Fmt1 LogStr str a ->
+    Fmt1 LogStr s (f a)
+cat1With join f = fmt1 $ toLogStr . join . fmap (runLogFmt f) . toList
+{-# SPECIALIZE cat1With :: Foldable f => ([LogStr] -> LogStr) -> Fmt1 LogStr LogStr a -> Fmt1 LogStr s (f a) #-}
+{-# SPECIALIZE cat1With :: Foldable f => ([Builder] -> Builder) -> Fmt1 LogStr Builder a -> Fmt1 LogStr s (f a) #-}
+{-# SPECIALIZE cat1With :: Foldable f => ([ByteString] -> ByteString) -> Fmt1 LogStr ByteString a -> Fmt1 LogStr s (f a) #-}
+{-# SPECIALIZE cat1With :: Foldable f => ([BL.ByteString] -> BL.ByteString) -> Fmt1 LogStr BL.ByteString a -> Fmt1 LogStr s (f a) #-}
+
+{- | Turn a text-splitting function into a formatting combinator.
+
+ @
+  'split1With' 'hsep' :: ('Traversable' f, 'ToLogStr' msg) => ('ByteString' -> f msg) -> 'Fmt' 'LogStr' s a -> 'Fmt' 'LogStr' s a
+  'split1With' 'vsep' :: ('Traversable' f, 'ToLogStr' msg) => ('ByteString' -> f msg) -> 'Fmt' 'LogStr' s a -> 'Fmt' 'LogStr' s a
+  'split1With' 'list1' :: ('Traversable' f, 'ToLogStr' msg) => ('ByteString' -> f msg) -> 'Fmt' 'LogStr' s a -> 'Fmt' 'LogStr' s a
+ @
+ >>> commas = reverse . fmap BL.reverse . BL.chunksOf 3 . BL.reverse
+ >>> dollars = prefix "$" . split1With commas (intercalate ",") . reversed
+ >>> runLogFmt (dollars d) 1234567890
+ "$1,234,567,890"
+ >>> printf (split1With (BL.splitOn ",") vsep t) "one,two,three"
+ one
+ two
+ three
+ >>> printf (split1With (BL.splitOn ",") (indentEach 4) t) "one,two,three"
+     one
+     two
+     three
+-}
+{-# INLINEABLE split1With #-}
+split1With ::
+    (Traversable f, ToLogStr str) =>
+    (Fmt1 m s_ m -> Fmt1 m m (f LogStr)) ->
+    (ByteString -> f str) ->
+    Fmt LogStr s a ->
+    Fmt m s a
+split1With lf split (Fmt g) = Fmt (g . (. runFmt (lf $ fmt1 id) . fmap toLogStr . split . fromLogStr))
+
+-- Html
+
+-------------------------
+
+{- | Format HTML
+
+  For example:
+
+  @
+  contact :: 'Html' 'LogStr'
+  contact = 'Data.Fmt.Html.p' "You can reach me at" '%' 'Data.Fmt.Html.ul' . 'spr' . 'Data.Fmt.Html.li' $ do
+        c1 <- 'Data.Fmt.Html.a' '!' 'href' @String "https://example.com" $ "Website"
+        c2 <- 'Data.Fmt.Html.a' '!' 'href' @String "mailto:cmk@example.com" $ "Email"
+        'pure' $ c1 '<>' c2
+  @
+  
+  generates the following output:
+
+  > "<p>You can reach me at</p><ul><li><a href=\"https://foo.com\">Web</a></li><li><a href=\"mailto:cmk@foo.com\">Email</a></li></ul>"
+-}
+type Html a = Fmt LogStr a a
+
+toHtml :: ToLogStr s => s -> Html a
+toHtml = logFmt
+
+comment :: ToLogStr s => s -> Html a
+comment = enclose "<!-- " " -->" . toHtml
+
+-- | Type for an attribute.
+newtype Attr = Attr (forall a. Html a -> Html a)
+
+instance Semigroup Attr where
+    Attr f <> Attr g = Attr (g . f)
+
+instance Monoid Attr where
+    mempty = Attr id
+
+{- | Apply an attribute to an HTML tag.
+
+ The interface is similar to < https://hackage.haskell.org/package/blaze-builder >.
+
+ You should not define your own instances of this class.
+-}
+class Element html where
+    {- | Apply an attribute to an element.
+
+         >>> printf $ img ! src "foo.png"
+         <img src="foo.png" />
+
+         This can be used on nested elements as well:
+
+         >>> printf $ p ! style "float: right" $ "Hello!"
+         <p style="float: right">Hello!</p>
+    -}
+    (!) :: html -> Attr -> html
+
+instance Element (Html a) where
+    h ! (Attr f) = f h
+    {-# INLINE (!) #-}
+
+instance Element (Html a -> Html b) where
+    h ! f = (! f) . h
+    {-# INLINE (!) #-}
+
+{- | Shorthand for setting an attribute depending on a conditional.
+
+ Example:
+
+ > p !? (isBig, A.class "big") $ "Hello"
+
+ Gives the same result as:
+
+ > (if isBig then p ! A.class "big" else p) "Hello"
+-}
+(!?) :: Element html => html -> (Bool, Attr) -> html
+(!?) h (c, a) = if c then h ! a else h
+
+-- Formatting
+
+-------------------------
+
+{- | Format each value in a list with spaces in between:
+
+ >>> runLogFmt (hsep d) [1, 2, 3]
+ "1 2 3"
+-}
+hsep :: Foldable f => Fmt1 LogStr ByteString a -> Fmt1 LogStr s (f a)
+hsep = cat1With $ B.intercalate " "
+{-# INLINE hsep #-}
+
+{- | Format each value in a list, placing each on its own line:
+
+ >>> printf (vsep c) ['a'..'c']
+ a
+ b
+ c
+-}
+vsep :: Foldable f => Fmt1 LogStr ByteString a -> Fmt1 LogStr s (f a)
+vsep = cat1With B.unlines
+{-# INLINE vsep #-}
+
+{- | Format a list of items, placing one per line, indent by the given number of spaces.
+
+ @ 'indentEach' n = 'Test.Contra.Type.Format.vsep' . 'indent' n @
+
+ >>> printf (split1With BL.lines (indentList 2) t) "one\ntwo\nthree"
+   one
+   two
+   three
+ >>> printf ("The lucky numbers are:\n" % indentList 2 d) [7, 13, 1, 42]
+ The lucky numbers are:
+   7
+   13
+   1
+   42
+-}
+hang :: Foldable f => Int -> Fmt1 LogStr ByteString a -> Fmt1 LogStr s (f a)
+hang n = vsep . indent n
+{-# INLINE hang #-}
+
+{- | Insert the given number of spaces at the start of the rendered text:
+
+ >>> runFmt (indent 4 d) 7
+ "    7"
+
+ Note that this only indents the first line of a multi-line string.
+ To indent all lines see 'reindent'.
+-}
+indent :: (IsString m, Semigroup m) => Int -> Fmt m a b -> Fmt m a b
+indent n = prefix $ fromString $ replicate n ' '
+{-# INLINEABLE indent #-}
+
+{- | Add the given prefix to the formatted item:
+
+ >>> runLogFmt ("The answer is: " % prefix "wait for it... " d) 42
+ "The answer is: wait for it... 42"
+
+ >>> printf (vsep (indent 4 (prefix "- " d))) [1, 2, 3]
+     - 1
+     - 2
+     - 3
+-}
+prefix :: Semigroup m => m -> Fmt m a b -> Fmt m a b
+prefix s f = fmt s % f
+{-# INLINE prefix #-}
+
+-- | Add the given suffix to the formatted item.
+suffix :: Semigroup m => m -> Fmt m a b -> Fmt m a b
+suffix s f = f % fmt s
+{-# INLINE suffix #-}
+
+{- | Enclose the output string with the given strings:
+
+ >>> runFmt (parens $ enclose v s ", ") 1 "two"
+ "(1, two)"
+ >>> runFmt (enclose (fmt "<!--") (fmt "-->") s) "an html comment"
+ "<!--an html comment-->"
+-}
+enclose :: Semigroup m => Fmt m b2 c -> Fmt m a b1 -> Fmt m b1 b2 -> Fmt m a c
+enclose pre suf f = pre % f % suf
+{-# INLINE enclose #-}
+
+-- @ 'tuple' 'd' 'd' :: 'LogFmt2' a 'Int' @
+--
+-- >>> runFmt (tuple d t) 1 "two"
+-- "(1, two)"
+tuple :: (Semigroup m, IsString m) => Fmt m b c -> Fmt m a b -> Fmt m a c
+tuple f1 f2 = parens $ enclose f1 f2 ", "
+
+{- | Add double quotes around the formatted item:
+
+ Use this to escape a string:
+
+ >>> runFmt ("He said it was based on " % quotes t' % ".") "science"
+ He said it was based on "science".
+-}
+quotes :: (Semigroup m, IsString m) => Fmt m a b -> Fmt m a b
+quotes = enclose "\"" "\""
+{-# INLINE quotes #-}
+
+{- | Add single quotes around the formatted item:
+
+ >>> let obj = Just Nothing in format ("The object is: " % quotes' shown % ".") obj
+ "The object is: 'Just Nothing'."
+-}
+quotes' :: (Semigroup m, IsString m) => Fmt m a b -> Fmt m a b
+quotes' = enclose "'" "'"
+{-# INLINE quotes' #-}
+
+{- | Add parentheses around the formatted item:
+
+ >>> runFmt ("We found " % parens d % " discrepancies.") 17
+ "We found (17) discrepancies."
+
+ >>> printf (get 5 (list1 (parens d))) [1..]
+ [(1), (2), (3), (4), (5)]
+-}
+parens :: (Semigroup m, IsString m) => Fmt m a b -> Fmt m a b
+parens = enclose "(" ")"
+{-# INLINE parens #-}
+
+{- | Add braces around the formatted item:
+
+ >>> runFmt ("\\begin" % braces t) "section"
+ "\\begin{section}"
+-}
+braces :: (Semigroup m, IsString m) => Fmt m a b -> Fmt m a b
+braces = enclose "{" "}"
+{-# INLINE braces #-}
+
+{- | Add square brackets around the formatted item:
+
+ >>> runFmt (brackets d) 7
+ "[7]"
+-}
+brackets :: (Semigroup m, IsString m) => Fmt m a b -> Fmt m a b
+brackets = enclose "[" "]"
+{-# INLINE brackets #-}
+
+{- | Add backticks around the formatted item:
+
+ >>> runLogFmt ("Be sure to run " % backticks builder % " as root.") ":(){:|:&};:"
+ "Be sure to run `:(){:|:&};:` as root."
+-}
+backticks :: (Semigroup m, IsString m) => Fmt m a b -> Fmt m a b
+backticks = enclose "`" "`"
+{-# INLINE backticks #-}
+
+-- Collections
+
+-------------------------
+
+{- | Render the value in a Left with the given formatter, rendering a Right as an empty string:
+
+ >>> runLogFmt (left1 text) (Left "bingo")
+ "bingo"
+
+ >>> runLogFmt (left1 text) (Right 16)
+ ""
+-}
+left1 :: IsString m => Fmt1 m m a -> Fmt1 m s (Either a b)
+left1 f = either1 f (fmt1 $ const "")
+{-# INLINE left1 #-}
+
+{- | Render the value in a Right with the given formatter, rendering a Left as an empty string:
+
+ >>> runLogFmt (right1 text) (Left 16)
+ ""
+
+ >>> runLogFmt (right1 text) (Right "bingo")
+ "bingo"
+-}
+right1 :: IsString m => Fmt1 m m b -> Fmt1 m s (Either a b)
+right1 = either1 (fmt1 $ const "")
+{-# INLINE right1 #-}
+
+{- | Render the value in an Either:
+
+ >>> runLogFmt (either1 text int) (Left "Error!"
+ "Error!"
+
+ >>> runLogFmt (either1 text int) (Right 69)
+ "69"
+-}
+either1 :: Fmt1 m m a -> Fmt1 m m b -> Fmt1 m s (Either a b)
+either1 l r = fmt1 $ either (runFmt l) (runFmt r)
+{-# INLINE either1 #-}
+
+{- | Render a Maybe value either as a default (if Nothing) or using the given formatter:
+
+ >>> runLogFmt (maybe1 "Goodbye" text) Nothing
+ "Goodbye"
+
+ >>> runLogFmt (maybe1 "Goodbye" text) (Just "Hello")
+ "Hello"
+-}
+maybe1 :: m -> Fmt1 m m a -> Fmt1 m s (Maybe a)
+maybe1 def f = fmt1 $ maybe def (runFmt f)
+{-# INLINE maybe1 #-}
+
+{- | Add square brackets around the Foldable (e.g. a list), and separate each formatted item with a comma and space.
+
+ >>> runLogFmt (list1 s) ["one", "two", "three"]
+ "[one, two, three]"
+ >>> printf (quotes $ list1 d) [1,2,3]
+ ["1", "2", "3"]
+ >>> printf (quotes $ list1 s) ["one", "two", "three"]
+ ["one", "two", "three"]
+-}
+list1 :: Foldable f => Fmt1 LogStr ByteString a -> Fmt1 LogStr s (f a)
+list1 = cat1With (B.intercalate ", ") . brackets
+{-# INLINE list1 #-}
+
+{- | A JSON-style formatter for lists.
+
+ >>> printf jsonList [1,2,3]
+ [
+   1
+ , 2
+ , 3
+ ]
+
+ Like 'yamlListF', it handles multiline elements well:
+
+ >>> fmt $ jsonListF ["hello\nworld", "foo\nbar\nquix"]
+ [
+   hello
+   world
+ , foo
+   bar
+   quix
+ ]
+-}
+{-# INLINE jsonList #-}
+jsonList :: (Foldable f, ToLogStr a) => Fmt1 LogStr s (f a)
+jsonList = fmt1 f
+  where
+    f xs
+        | null items = "[]\n"
+        | otherwise = "[\n" <> mconcat items <> "]\n"
+      where
+        items = zipWith buildItem (True : repeat False) (toList xs)
+        -- Item builder
+        --buildItem :: Bool -> a -> B
+
+        buildItem isFirst x =
+            case map toLogStr (B.lines (fromLogStr (toLogStr x))) of
+                []
+                    | isFirst -> "\n"
+                    | otherwise -> ",\n"
+                (h : t) ->
+                    mconcat . map (<> "\n") $
+                        if isFirst
+                            then "  " <> h : fmap ("  " <>) t
+                            else ", " <> h : fmap ("  " <>) t
+
+{- | A multiline formatter for lists.
+
+  >>> printf (yamlList d) [1,2,3]
+  - 1
+  - 2
+  - 3
+
+  Multi-line elements are indented correctly:
+
+  >>> printf (yamlList s) ["hello\nworld", "foo\nbar\nquix"]
+  - hello
+    world
+  - foo
+    bar
+    quix
+-}
+{-# INLINE yamlList #-}
+yamlList :: (Foldable f, ToLogStr a) => Fmt1 LogStr s (f a)
+yamlList = fmt1 f
+  where
+    f xs = if null items then "[]\n" else mconcat items
+      where
+        bullet = "-"
+        spaces = "  "
+        newline = "\n"
+        items = map buildItem (toList xs)
+        buildItem x = case B.lines (fromLogStr (toLogStr x)) of
+            [] -> bullet <> newline
+            (l : ls) ->
+                bullet <> " " <> toLogStr l <> newline
+                    <> mconcat [spaces <> toLogStr s <> newline | s <- ls]
+
+{- | A JSON-like map formatter; works for Map, HashMap, etc, and lists of pairs.
+
+>>> fmt $ jsonMapF [("Odds", jsonListF [1,3]), ("Evens", jsonListF [2,4])]
+{
+  Odds:
+    [
+      1
+    , 3
+    ]
+, Evens:
+    [
+      2
+    , 4
+    ]
+}
+-}
+{-# INLINE jsonMap #-}
+jsonMap :: (ToLogStr k, IsList map, Item map ~ (k, ByteString)) => Fmt1 LogStr s map
+jsonMap = fmt1 f
+  where
+    f xs
+        | null items = "{}\n"
+        | otherwise = "{\n" <> mconcat items <> "}\n"
+      where
+        items = zipWith buildItem (True : repeat False) (IsList.toList xs)
+        -- Item builder
+        --buildItem :: Bool -> (k, v) -> B
+        buildItem isFirst (k, v) = do
+            let kb = (if isFirst then "  " else ", ") <> toLogStr k
+            case map toLogStr (B.lines v) of
+                [] -> kb <> ":\n"
+                [l] -> kb <> ": " <> l <> "\n"
+                ls ->
+                    kb <> ":\n"
+                        <> mconcat ["    " <> s <> "\n" | s <- ls]
+
+--  | A YAML-like map formatter:
+--
+-- >>> BL.putStrLn $ BL.toLazyByteString $ yamlMapF id id [("Odds", yamlListF (BL.fromString . show) "-" [1,3]), ("Evens", yamlListF (BL.fromString . show) "-" [2,4])]
+-- Odds:
+--   - 1
+--   - 3
+-- Evens:
+--   - 2
+--   - 4
+{-# INLINE yamlMap #-}
+yamlMap :: (ToLogStr k, ToLogStr v, IsList map, Item map ~ (k, v)) => Fmt1 LogStr s map
+yamlMap = fmt1 f
+  where
+    f xs
+        | null items = "{}\n"
+        | otherwise = mconcat items
+      where
+        items = map (\(k, v) -> nameF (toLogStr k) (toLogStr v)) (IsList.toList xs)
+
+nameF :: LogStr -> LogStr -> LogStr
+nameF k v = case B.lines (fromLogStr v) of
+    [] -> k <> ":\n"
+    [l] -> k <> ": " <> toLogStr l <> "\n"
+    ls ->
+        k <> ":\n"
+            <> mconcat ["  " <> toLogStr s <> "\n" | s <- ls]
diff --git a/src/Data/Fmt/Ansi.hs b/src/Data/Fmt/Ansi.hs
new file mode 100644
--- /dev/null
+++ b/src/Data/Fmt/Ansi.hs
@@ -0,0 +1,102 @@
+module Data.Fmt.Ansi (
+    -- * Ansi terminal codes
+    code,
+    codes,
+    erase,
+    reset,
+    shift,
+    scroll,
+
+    -- ** Emphasis
+    blink,
+    bold,
+    faint,
+    italic,
+    underline,
+
+    -- ** Color
+    dull,
+    vivid,
+    layer,
+    palette,
+    Palette,
+    XColor,
+    Color (..),
+    ConsoleLayer (..),
+) where
+
+import Data.Fmt
+import Data.Word
+import System.Console.ANSI.Codes
+import System.Console.ANSI.Types
+
+-- Ansi terminal formatters
+
+-------------------------
+
+code :: (Semigroup m, IsString m) => SGR -> Fmt m s a -> Fmt m s a
+code = codes . pure
+
+-- | Wrap content with escape sequence to set and reset color of normal intensity.
+codes :: (Semigroup m, IsString m) => [SGR] -> Fmt m s a -> Fmt m s a
+codes sgr x = enclose before after x
+  where
+    before = fromString $ setSGRCode sgr
+    after = fromString $ setSGRCode [Reset]
+
+erase :: (Semigroup m, IsString m) => Ordering -> Fmt m s a -> Fmt m s a
+erase LT = suffix $ fromString clearFromCursorToLineBeginningCode
+erase EQ = suffix $ fromString clearLineCode
+erase GT = suffix $ fromString clearFromCursorToLineEndCode
+
+reset :: (Semigroup m, IsString m) => Fmt m s a -> Fmt m s a
+reset = prefix $ fromString $ setSGRCode [Reset]
+
+shift :: (Semigroup m, IsString m) => Either Int Int -> Fmt m s a -> Fmt m s a
+shift = prefix . fromString . either cursorBackwardCode cursorForwardCode
+
+scroll :: (Semigroup m, IsString m) => Either Int Int -> Fmt m s a -> Fmt m s a
+scroll = prefix . fromString . either scrollPageUpCode scrollPageDownCode
+
+-- Emphasis
+
+-------------------------
+
+blink :: (Semigroup m, IsString m) => Fmt m s a -> Fmt m s a
+blink = code $ SetBlinkSpeed SlowBlink
+
+bold :: (Semigroup m, IsString m) => Fmt m s a -> Fmt m s a
+bold = code $ SetConsoleIntensity BoldIntensity
+
+italic :: (Semigroup m, IsString m) => Fmt m s a -> Fmt m s a
+italic = code $ SetItalicized True
+
+underline :: (Semigroup m, IsString m) => Fmt m s a -> Fmt m s a
+underline = code $ SetUnderlining SingleUnderline
+
+faint :: (Semigroup m, IsString m) => Fmt m s a -> Fmt m s a
+faint = code $ SetConsoleIntensity FaintIntensity
+
+-- Color
+
+-------------------------
+
+-- | The xterm < https://en.wikipedia.org/wiki/8-bit_color 8 bit > color encoding.
+type XColor = Word8
+
+-- | A simple palette consisting of a foreground and background color.
+type Palette = (XColor, XColor)
+
+dull :: (Semigroup m, IsString m) => Color -> ConsoleLayer -> Fmt m s a -> Fmt m s a
+dull = layer . xtermSystem Dull
+
+vivid :: (Semigroup m, IsString m) => Color -> ConsoleLayer -> Fmt m s a -> Fmt m s a
+vivid = layer . xtermSystem Vivid
+
+--vivid col lay = code $ SetColor lay Vivid col
+
+layer :: (Semigroup m, IsString m) => XColor -> ConsoleLayer -> Fmt m s a -> Fmt m s a
+layer pal lay = code $ SetPaletteColor lay pal
+
+palette :: (Semigroup m, IsString m) => Palette -> Fmt m s a -> Fmt m s a
+palette (fg, bg) = codes [SetPaletteColor Foreground fg, SetPaletteColor Background bg]
diff --git a/src/Data/Fmt/Attr.hs b/src/Data/Fmt/Attr.hs
new file mode 100644
--- /dev/null
+++ b/src/Data/Fmt/Attr.hs
@@ -0,0 +1,3414 @@
+{-# LANGUAGE OverloadedStrings #-}
+
+{- | Html5 formatting.
+
+ The API is similar to < https://hackage.haskell.org/package/blaze-html >.
+-}
+module Data.Fmt.Attr (
+    -- * Html
+    Html,
+    Attr,
+    toHtml,
+    comment,
+    Element (..),
+    (!?),
+
+    -- * Attributes
+    accept,
+    acceptCharset,
+    accesskey,
+    action,
+    alt,
+    async,
+    autocomplete,
+    autofocus,
+    autoplay,
+    challenge,
+    charset,
+    checked,
+    cite,
+    class_,
+    cols,
+    colspan,
+    content,
+    contenteditable,
+    contextmenu,
+    controls,
+    coords,
+    data_,
+    datetime,
+    defer,
+    dir,
+    disabled,
+    draggable,
+    enctype,
+    for,
+    form,
+    formaction,
+    formenctype,
+    formmethod,
+    formnovalidate,
+    formtarget,
+    headers,
+    height,
+    hidden,
+    high,
+    href,
+    hreflang,
+    httpEquiv,
+    icon,
+    id,
+    ismap,
+    item,
+    itemprop,
+    itemscope,
+    itemtype,
+    keytype,
+    label,
+    lang,
+    list,
+    loop,
+    low,
+    manifest,
+    max,
+    maxlength,
+    media,
+    method,
+    min,
+    multiple,
+    name,
+    novalidate,
+    onbeforeonload,
+    onbeforeprint,
+    onblur,
+    oncanplay,
+    oncanplaythrough,
+    onchange,
+    onclick,
+    oncontextmenu,
+    ondblclick,
+    ondrag,
+    ondragend,
+    ondragenter,
+    ondragleave,
+    ondragover,
+    ondragstart,
+    ondrop,
+    ondurationchange,
+    onemptied,
+    onended,
+    onerror,
+    onfocus,
+    onformchange,
+    onforminput,
+    onhaschange,
+    oninput,
+    oninvalid,
+    onkeydown,
+    onkeyup,
+    onload,
+    onloadeddata,
+    onloadedmetadata,
+    onloadstart,
+    onmessage,
+    onmousedown,
+    onmousemove,
+    onmouseout,
+    onmouseover,
+    onmouseup,
+    onmousewheel,
+    ononline,
+    onpagehide,
+    onpageshow,
+    onpause,
+    onplay,
+    onplaying,
+    onprogress,
+    onpropstate,
+    onratechange,
+    onreadystatechange,
+    onredo,
+    onresize,
+    onscroll,
+    onseeked,
+    onseeking,
+    onselect,
+    onstalled,
+    onstorage,
+    onsubmit,
+    onsuspend,
+    ontimeupdate,
+    onundo,
+    onunload,
+    onvolumechange,
+    onwaiting,
+    open,
+    optimum,
+    pattern,
+    ping,
+    placeholder,
+    preload,
+    pubdate,
+    radiogroup,
+    readonly,
+    rel,
+    required,
+    reversed,
+    role,
+    rows,
+    rowspan,
+    sandbox,
+    scope,
+    scoped,
+    seamless,
+    selected,
+    shape,
+    size,
+    sizes,
+    span,
+    spellcheck,
+    src,
+    srcdoc,
+    start,
+    step,
+    style,
+    subject,
+    summary,
+    tabindex,
+    target,
+    title,
+    type_,
+    usemap,
+    value,
+    width,
+    wrap,
+    xmlns,
+) where
+
+import qualified Data.ByteString.Char8 as B
+import Data.Fmt
+import Prelude (Eq (..), Maybe (..), Monoid (..), ($), (||))
+
+attr :: ToLogStr s => LogStr -> s -> Attr
+attr k v = Attr $ splitWith f g
+  where
+    f = B.break $ \c -> c == '/' || c == '>'
+    g l r0 = case B.uncons r0 of
+        Just ('/', r) -> toHtml l % fmt k % "=" % quotes (toHtml v) % fmt " /" % toHtml r
+        Just ('>', r) -> toHtml l % " " % fmt k % "=" % quotes (toHtml v) % fmt ">" % toHtml r
+        _ -> fmt mempty
+
+-- Attributes
+
+-------------------------
+
+{- | Combinator for the @accept@ attribute.
+
+ Example:
+
+ > div ! accept "bar" $ "Hello."
+
+ Result:
+
+ > <div accept="bar">Hello.</div>
+-}
+accept ::
+    ToLogStr s =>
+    -- | Attribute value.
+    s ->
+    -- | Resulting attribute.
+    Attr
+accept = attr "accept"
+{-# INLINE accept #-}
+
+{- | Combinator for the @accept-charset@ attribute.
+
+ Example:
+
+ > div ! acceptCharset "bar" $ "Hello."
+
+ Result:
+
+ > <div accept-charset="bar">Hello.</div>
+-}
+acceptCharset ::
+    ToLogStr s =>
+    -- | Attribute value.
+    s ->
+    -- | Resulting attribute.
+    Attr
+acceptCharset = attr "accept-charset"
+{-# INLINE acceptCharset #-}
+
+{- | Combinator for the @accesskey@ attribute.
+
+ Example:
+
+ > div ! accesskey "bar" $ "Hello."
+
+ Result:
+
+ > <div accesskey="bar">Hello.</div>
+-}
+accesskey ::
+    ToLogStr s =>
+    -- | Attribute value.
+    s ->
+    -- | Resulting attribute.
+    Attr
+accesskey = attr "accesskey"
+{-# INLINE accesskey #-}
+
+{- | Combinator for the @action@ attribute.
+
+ Example:
+
+ > div ! action "bar" $ "Hello."
+
+ Result:
+
+ > <div action="bar">Hello.</div>
+-}
+action ::
+    ToLogStr s =>
+    -- | Attribute value.
+    s ->
+    -- | Resulting attribute.
+    Attr
+action = attr "action"
+{-# INLINE action #-}
+
+{- | Combinator for the @alt@ attribute.
+
+ Example:
+
+ > div ! alt "bar" $ "Hello."
+
+ Result:
+
+ > <div alt="bar">Hello.</div>
+-}
+alt ::
+    ToLogStr s =>
+    -- | Attribute value.
+    s ->
+    -- | Resulting attribute.
+    Attr
+alt = attr "alt"
+{-# INLINE alt #-}
+
+{- | Combinator for the @async@ attribute.
+
+ Example:
+
+ > div ! async "bar" $ "Hello."
+
+ Result:
+
+ > <div async="bar">Hello.</div>
+-}
+async ::
+    ToLogStr s =>
+    -- | Attribute value.
+    s ->
+    -- | Resulting attribute.
+    Attr
+async = attr "async"
+{-# INLINE async #-}
+
+{- | Combinator for the @autocomplete@ attribute.
+
+ Example:
+
+ > div ! autocomplete "bar" $ "Hello."
+
+ Result:
+
+ > <div autocomplete="bar">Hello.</div>
+-}
+autocomplete ::
+    ToLogStr s =>
+    -- | Attribute value.
+    s ->
+    -- | Resulting attribute.
+    Attr
+autocomplete = attr "autocomplete"
+{-# INLINE autocomplete #-}
+
+{- | Combinator for the @autofocus@ attribute.
+
+ Example:
+
+ > div ! autofocus "bar" $ "Hello."
+
+ Result:
+
+ > <div autofocus="bar">Hello.</div>
+-}
+autofocus ::
+    ToLogStr s =>
+    -- | Attribute value.
+    s ->
+    -- | Resulting attribute.
+    Attr
+autofocus = attr "autofocus"
+{-# INLINE autofocus #-}
+
+{- | Combinator for the @autoplay@ attribute.
+
+ Example:
+
+ > div ! autoplay "bar" $ "Hello."
+
+ Result:
+
+ > <div autoplay="bar">Hello.</div>
+-}
+autoplay ::
+    ToLogStr s =>
+    -- | Attribute value.
+    s ->
+    -- | Resulting attribute.
+    Attr
+autoplay = attr "autoplay"
+{-# INLINE autoplay #-}
+
+{- | Combinator for the @challenge@ attribute.
+
+ Example:
+
+ > div ! challenge "bar" $ "Hello."
+
+ Result:
+
+ > <div challenge="bar">Hello.</div>
+-}
+challenge ::
+    ToLogStr s =>
+    -- | Attribute value.
+    s ->
+    -- | Resulting attribute.
+    Attr
+challenge = attr "challenge"
+{-# INLINE challenge #-}
+
+{- | Combinator for the @charset@ attribute.
+
+ Example:
+
+ > div ! charset "bar" $ "Hello."
+
+ Result:
+
+ > <div charset="bar">Hello.</div>
+-}
+charset ::
+    ToLogStr s =>
+    -- | Attribute value.
+    s ->
+    -- | Resulting attribute.
+    Attr
+charset = attr "charset"
+{-# INLINE charset #-}
+
+{- | Combinator for the @checked@ attribute.
+
+ Example:
+
+ > div ! checked "bar" $ "Hello."
+
+ Result:
+
+ > <div checked="bar">Hello.</div>
+-}
+checked ::
+    ToLogStr s =>
+    -- | Attribute value.
+    s ->
+    -- | Resulting attribute.
+    Attr
+checked = attr "checked"
+{-# INLINE checked #-}
+
+{- | Combinator for the @cite@ attribute.
+
+ Example:
+
+ > div ! cite "bar" $ "Hello."
+
+ Result:
+
+ > <div cite="bar">Hello.</div>
+-}
+cite ::
+    ToLogStr s =>
+    -- | Attribute value.
+    s ->
+    -- | Resulting attribute.
+    Attr
+cite = attr "cite"
+{-# INLINE cite #-}
+
+{- | Combinator for the @class@ attribute.
+
+ Example:
+
+ > div ! class_ "bar" $ "Hello."
+
+ Result:
+
+ > <div class="bar">Hello.</div>
+-}
+class_ ::
+    ToLogStr s =>
+    -- | Attribute value.
+    s ->
+    -- | Resulting attribute.
+    Attr
+class_ = attr "class"
+{-# INLINE class_ #-}
+
+{- | Combinator for the @cols@ attribute.
+
+ Example:
+
+ > div ! cols "bar" $ "Hello."
+
+ Result:
+
+ > <div cols="bar">Hello.</div>
+-}
+cols ::
+    ToLogStr s =>
+    -- | Attribute value.
+    s ->
+    -- | Resulting attribute.
+    Attr
+cols = attr "cols"
+{-# INLINE cols #-}
+
+{- | Combinator for the @colspan@ attribute.
+
+ Example:
+
+ > div ! colspan "bar" $ "Hello."
+
+ Result:
+
+ > <div colspan="bar">Hello.</div>
+-}
+colspan ::
+    ToLogStr s =>
+    -- | Attribute value.
+    s ->
+    -- | Resulting attribute.
+    Attr
+colspan = attr "colspan"
+{-# INLINE colspan #-}
+
+{- | Combinator for the @content@ attribute.
+
+ Example:
+
+ > div ! content "bar" $ "Hello."
+
+ Result:
+
+ > <div content="bar">Hello.</div>
+-}
+content ::
+    ToLogStr s =>
+    -- | Attribute value.
+    s ->
+    -- | Resulting attribute.
+    Attr
+content = attr "content"
+{-# INLINE content #-}
+
+{- | Combinator for the @contenteditable@ attribute.
+
+ Example:
+
+ > div ! contenteditable "bar" $ "Hello."
+
+ Result:
+
+ > <div contenteditable="bar">Hello.</div>
+-}
+contenteditable ::
+    ToLogStr s =>
+    -- | Attribute value.
+    s ->
+    -- | Resulting attribute.
+    Attr
+contenteditable = attr "contenteditable"
+{-# INLINE contenteditable #-}
+
+{- | Combinator for the @contextmenu@ attribute.
+
+ Example:
+
+ > div ! contextmenu "bar" $ "Hello."
+
+ Result:
+
+ > <div contextmenu="bar">Hello.</div>
+-}
+contextmenu ::
+    ToLogStr s =>
+    -- | Attribute value.
+    s ->
+    -- | Resulting attribute.
+    Attr
+contextmenu = attr "contextmenu"
+{-# INLINE contextmenu #-}
+
+{- | Combinator for the @controls@ attribute.
+
+ Example:
+
+ > div ! controls "bar" $ "Hello."
+
+ Result:
+
+ > <div controls="bar">Hello.</div>
+-}
+controls ::
+    ToLogStr s =>
+    -- | Attribute value.
+    s ->
+    -- | Resulting attribute.
+    Attr
+controls = attr "controls"
+{-# INLINE controls #-}
+
+{- | Combinator for the @coords@ attribute.
+
+ Example:
+
+ > div ! coords "bar" $ "Hello."
+
+ Result:
+
+ > <div coords="bar">Hello.</div>
+-}
+coords ::
+    ToLogStr s =>
+    -- | Attribute value.
+    s ->
+    -- | Resulting attribute.
+    Attr
+coords = attr "coords"
+{-# INLINE coords #-}
+
+{- | Combinator for the @data@ attribute.
+
+ Example:
+
+ > div ! data_ "bar" $ "Hello."
+
+ Result:
+
+ > <div data="bar">Hello.</div>
+-}
+data_ ::
+    ToLogStr s =>
+    -- | Attribute value.
+    s ->
+    -- | Resulting attribute.
+    Attr
+data_ = attr "data"
+{-# INLINE data_ #-}
+
+{- | Combinator for the @datetime@ attribute.
+
+ Example:
+
+ > div ! datetime "bar" $ "Hello."
+
+ Result:
+
+ > <div datetime="bar">Hello.</div>
+-}
+datetime ::
+    ToLogStr s =>
+    -- | Attribute value.
+    s ->
+    -- | Resulting attribute.
+    Attr
+datetime = attr "datetime"
+{-# INLINE datetime #-}
+
+{- | Combinator for the @defer@ attribute.
+
+ Example:
+
+ > div ! defer "bar" $ "Hello."
+
+ Result:
+
+ > <div defer="bar">Hello.</div>
+-}
+defer ::
+    ToLogStr s =>
+    -- | Attribute value.
+    s ->
+    -- | Resulting attribute.
+    Attr
+defer = attr "defer"
+{-# INLINE defer #-}
+
+{- | Combinator for the @dir@ attribute.
+
+ Example:
+
+ > div ! dir "bar" $ "Hello."
+
+ Result:
+
+ > <div dir="bar">Hello.</div>
+-}
+dir ::
+    ToLogStr s =>
+    -- | Attribute value.
+    s ->
+    -- | Resulting attribute.
+    Attr
+dir = attr "dir"
+{-# INLINE dir #-}
+
+{- | Combinator for the @disabled@ attribute.
+
+ Example:
+
+ > div ! disabled "bar" $ "Hello."
+
+ Result:
+
+ > <div disabled="bar">Hello.</div>
+-}
+disabled ::
+    ToLogStr s =>
+    -- | Attribute value.
+    s ->
+    -- | Resulting attribute.
+    Attr
+disabled = attr "disabled"
+{-# INLINE disabled #-}
+
+{- | Combinator for the @draggable@ attribute.
+
+ Example:
+
+ > div ! draggable "bar" $ "Hello."
+
+ Result:
+
+ > <div draggable="bar">Hello.</div>
+-}
+draggable ::
+    ToLogStr s =>
+    -- | Attribute value.
+    s ->
+    -- | Resulting attribute.
+    Attr
+draggable = attr "draggable"
+{-# INLINE draggable #-}
+
+{- | Combinator for the @enctype@ attribute.
+
+ Example:
+
+ > div ! enctype "bar" $ "Hello."
+
+ Result:
+
+ > <div enctype="bar">Hello.</div>
+-}
+enctype ::
+    ToLogStr s =>
+    -- | Attribute value.
+    s ->
+    -- | Resulting attribute.
+    Attr
+enctype = attr "enctype"
+{-# INLINE enctype #-}
+
+{- | Combinator for the @for@ attribute.
+
+ Example:
+
+ > div ! for "bar" $ "Hello."
+
+ Result:
+
+ > <div for="bar">Hello.</div>
+-}
+for ::
+    ToLogStr s =>
+    -- | Attribute value.
+    s ->
+    -- | Resulting attribute.
+    Attr
+for = attr "for"
+{-# INLINE for #-}
+
+{- | Combinator for the @form@ attribute.
+
+ Example:
+
+ > div ! form "bar" $ "Hello."
+
+ Result:
+
+ > <div form="bar">Hello.</div>
+-}
+form ::
+    ToLogStr s =>
+    -- | Attribute value.
+    s ->
+    -- | Resulting attribute.
+    Attr
+form = attr "form"
+{-# INLINE form #-}
+
+{- | Combinator for the @formaction@ attribute.
+
+ Example:
+
+ > div ! formaction "bar" $ "Hello."
+
+ Result:
+
+ > <div formaction="bar">Hello.</div>
+-}
+formaction ::
+    ToLogStr s =>
+    -- | Attribute value.
+    s ->
+    -- | Resulting attribute.
+    Attr
+formaction = attr "formaction"
+{-# INLINE formaction #-}
+
+{- | Combinator for the @formenctype@ attribute.
+
+ Example:
+
+ > div ! formenctype "bar" $ "Hello."
+
+ Result:
+
+ > <div formenctype="bar">Hello.</div>
+-}
+formenctype ::
+    ToLogStr s =>
+    -- | Attribute value.
+    s ->
+    -- | Resulting attribute.
+    Attr
+formenctype = attr "formenctype"
+{-# INLINE formenctype #-}
+
+{- | Combinator for the @formmethod@ attribute.
+
+ Example:
+
+ > div ! formmethod "bar" $ "Hello."
+
+ Result:
+
+ > <div formmethod="bar">Hello.</div>
+-}
+formmethod ::
+    ToLogStr s =>
+    -- | Attribute value.
+    s ->
+    -- | Resulting attribute.
+    Attr
+formmethod = attr "formmethod"
+{-# INLINE formmethod #-}
+
+{- | Combinator for the @formnovalidate@ attribute.
+
+ Example:
+
+ > div ! formnovalidate "bar" $ "Hello."
+
+ Result:
+
+ > <div formnovalidate="bar">Hello.</div>
+-}
+formnovalidate ::
+    ToLogStr s =>
+    -- | Attribute value.
+    s ->
+    -- | Resulting attribute.
+    Attr
+formnovalidate = attr "formnovalidate"
+{-# INLINE formnovalidate #-}
+
+{- | Combinator for the @formtarget@ attribute.
+
+ Example:
+
+ > div ! formtarget "bar" $ "Hello."
+
+ Result:
+
+ > <div formtarget="bar">Hello.</div>
+-}
+formtarget ::
+    ToLogStr s =>
+    -- | Attribute value.
+    s ->
+    -- | Resulting attribute.
+    Attr
+formtarget = attr "formtarget"
+{-# INLINE formtarget #-}
+
+{- | Combinator for the @headers@ attribute.
+
+ Example:
+
+ > div ! headers "bar" $ "Hello."
+
+ Result:
+
+ > <div headers="bar">Hello.</div>
+-}
+headers ::
+    ToLogStr s =>
+    -- | Attribute value.
+    s ->
+    -- | Resulting attribute.
+    Attr
+headers = attr "headers"
+{-# INLINE headers #-}
+
+{- | Combinator for the @height@ attribute.
+
+ Example:
+
+ > div ! height "bar" $ "Hello."
+
+ Result:
+
+ > <div height="bar">Hello.</div>
+-}
+height ::
+    ToLogStr s =>
+    -- | Attribute value.
+    s ->
+    -- | Resulting attribute.
+    Attr
+height = attr "height"
+{-# INLINE height #-}
+
+{- | Combinator for the @hidden@ attribute.
+
+ Example:
+
+ > div ! hidden "bar" $ "Hello."
+
+ Result:
+
+ > <div hidden="bar">Hello.</div>
+-}
+hidden ::
+    ToLogStr s =>
+    -- | Attribute value.
+    s ->
+    -- | Resulting attribute.
+    Attr
+hidden = attr "hidden"
+{-# INLINE hidden #-}
+
+{- | Combinator for the @high@ attribute.
+
+ Example:
+
+ > div ! high "bar" $ "Hello."
+
+ Result:
+
+ > <div high="bar">Hello.</div>
+-}
+high ::
+    ToLogStr s =>
+    -- | Attribute value.
+    s ->
+    -- | Resulting attribute.
+    Attr
+high = attr "high"
+{-# INLINE high #-}
+
+{- | Combinator for the @href@ attribute.
+
+ Example:
+
+ > div ! href "bar" $ "Hello."
+
+ Result:
+
+ > <div href="bar">Hello.</div>
+-}
+href ::
+    ToLogStr s =>
+    -- | Attribute value.
+    s ->
+    -- | Resulting attribute.
+    Attr
+href = attr "href"
+{-# INLINE href #-}
+
+{- | Combinator for the @hreflang@ attribute.
+
+ Example:
+
+ > div ! hreflang "bar" $ "Hello."
+
+ Result:
+
+ > <div hreflang="bar">Hello.</div>
+-}
+hreflang ::
+    ToLogStr s =>
+    -- | Attribute value.
+    s ->
+    -- | Resulting attribute.
+    Attr
+hreflang = attr "hreflang"
+{-# INLINE hreflang #-}
+
+{- | Combinator for the @http-equiv@ attribute.
+
+ Example:
+
+ > div ! httpEquiv "bar" $ "Hello."
+
+ Result:
+
+ > <div http-equiv="bar">Hello.</div>
+-}
+httpEquiv ::
+    ToLogStr s =>
+    -- | Attribute value.
+    s ->
+    -- | Resulting attribute.
+    Attr
+httpEquiv = attr "http-equiv"
+{-# INLINE httpEquiv #-}
+
+{- | Combinator for the @icon@ attribute.
+
+ Example:
+
+ > div ! icon "bar" $ "Hello."
+
+ Result:
+
+ > <div icon="bar">Hello.</div>
+-}
+icon ::
+    ToLogStr s =>
+    -- | Attribute value.
+    s ->
+    -- | Resulting attribute.
+    Attr
+icon = attr "icon"
+{-# INLINE icon #-}
+
+{- | Combinator for the @id@ attribute.
+
+ Example:
+
+ > div ! id "bar" $ "Hello."
+
+ Result:
+
+ > <div id="bar">Hello.</div>
+-}
+id ::
+    ToLogStr s =>
+    -- | Attribute value.
+    s ->
+    -- | Resulting attribute.
+    Attr
+id = attr "id"
+{-# INLINE id #-}
+
+{- | Combinator for the @ismap@ attribute.
+
+ Example:
+
+ > div ! ismap "bar" $ "Hello."
+
+ Result:
+
+ > <div ismap="bar">Hello.</div>
+-}
+ismap ::
+    ToLogStr s =>
+    -- | Attribute value.
+    s ->
+    -- | Resulting attribute.
+    Attr
+ismap = attr "ismap"
+{-# INLINE ismap #-}
+
+{- | Combinator for the @item@ attribute.
+
+ Example:
+
+ > div ! item "bar" $ "Hello."
+
+ Result:
+
+ > <div item="bar">Hello.</div>
+-}
+item ::
+    ToLogStr s =>
+    -- | Attribute value.
+    s ->
+    -- | Resulting attribute.
+    Attr
+item = attr "item"
+{-# INLINE item #-}
+
+{- | Combinator for the @itemprop@ attribute.
+
+ Example:
+
+ > div ! itemprop "bar" $ "Hello."
+
+ Result:
+
+ > <div itemprop="bar">Hello.</div>
+-}
+itemprop ::
+    ToLogStr s =>
+    -- | Attribute value.
+    s ->
+    -- | Resulting attribute.
+    Attr
+itemprop = attr "itemprop"
+{-# INLINE itemprop #-}
+
+{- | Combinator for the @itemscope@ attribute.
+
+ Example:
+
+ > div ! itemscope "bar" $ "Hello."
+
+ Result:
+
+ > <div itemscope="bar">Hello.</div>
+-}
+itemscope ::
+    ToLogStr s =>
+    -- | Attribute value.
+    s ->
+    -- | Resulting attribute.
+    Attr
+itemscope = attr "itemscope"
+{-# INLINE itemscope #-}
+
+{- | Combinator for the @itemtype@ attribute.
+
+ Example:
+
+ > div ! itemtype "bar" $ "Hello."
+
+ Result:
+
+ > <div itemtype="bar">Hello.</div>
+-}
+itemtype ::
+    ToLogStr s =>
+    -- | Attribute value.
+    s ->
+    -- | Resulting attribute.
+    Attr
+itemtype = attr "itemtype"
+{-# INLINE itemtype #-}
+
+{- | Combinator for the @keytype@ attribute.
+
+ Example:
+
+ > div ! keytype "bar" $ "Hello."
+
+ Result:
+
+ > <div keytype="bar">Hello.</div>
+-}
+keytype ::
+    ToLogStr s =>
+    -- | Attribute value.
+    s ->
+    -- | Resulting attribute.
+    Attr
+keytype = attr "keytype"
+{-# INLINE keytype #-}
+
+{- | Combinator for the @label@ attribute.
+
+ Example:
+
+ > div ! label "bar" $ "Hello."
+
+ Result:
+
+ > <div label="bar">Hello.</div>
+-}
+label ::
+    ToLogStr s =>
+    -- | Attribute value.
+    s ->
+    -- | Resulting attribute.
+    Attr
+label = attr "label"
+{-# INLINE label #-}
+
+{- | Combinator for the @lang@ attribute.
+
+ Example:
+
+ > div ! lang "bar" $ "Hello."
+
+ Result:
+
+ > <div lang="bar">Hello.</div>
+-}
+lang ::
+    ToLogStr s =>
+    -- | Attribute value.
+    s ->
+    -- | Resulting attribute.
+    Attr
+lang = attr "lang"
+{-# INLINE lang #-}
+
+{- | Combinator for the @list@ attribute.
+
+ Example:
+
+ > div ! list "bar" $ "Hello."
+
+ Result:
+
+ > <div list="bar">Hello.</div>
+-}
+list ::
+    ToLogStr s =>
+    -- | Attribute value.
+    s ->
+    -- | Resulting attribute.
+    Attr
+list = attr "list"
+{-# INLINE list #-}
+
+{- | Combinator for the @loop@ attribute.
+
+ Example:
+
+ > div ! loop "bar" $ "Hello."
+
+ Result:
+
+ > <div loop="bar">Hello.</div>
+-}
+loop ::
+    ToLogStr s =>
+    -- | Attribute value.
+    s ->
+    -- | Resulting attribute.
+    Attr
+loop = attr "loop"
+{-# INLINE loop #-}
+
+{- | Combinator for the @low@ attribute.
+
+ Example:
+
+ > div ! low "bar" $ "Hello."
+
+ Result:
+
+ > <div low="bar">Hello.</div>
+-}
+low ::
+    ToLogStr s =>
+    -- | Attribute value.
+    s ->
+    -- | Resulting attribute.
+    Attr
+low = attr "low"
+{-# INLINE low #-}
+
+{- | Combinator for the @manifest@ attribute.
+
+ Example:
+
+ > div ! manifest "bar" $ "Hello."
+
+ Result:
+
+ > <div manifest="bar">Hello.</div>
+-}
+manifest ::
+    ToLogStr s =>
+    -- | Attribute value.
+    s ->
+    -- | Resulting attribute.
+    Attr
+manifest = attr "manifest"
+{-# INLINE manifest #-}
+
+{- | Combinator for the @max@ attribute.
+
+ Example:
+
+ > div ! max "bar" $ "Hello."
+
+ Result:
+
+ > <div max="bar">Hello.</div>
+-}
+max ::
+    ToLogStr s =>
+    -- | Attribute value.
+    s ->
+    -- | Resulting attribute.
+    Attr
+max = attr "max"
+{-# INLINE max #-}
+
+{- | Combinator for the @maxlength@ attribute.
+
+ Example:
+
+ > div ! maxlength "bar" $ "Hello."
+
+ Result:
+
+ > <div maxlength="bar">Hello.</div>
+-}
+maxlength ::
+    ToLogStr s =>
+    -- | Attribute value.
+    s ->
+    -- | Resulting attribute.
+    Attr
+maxlength = attr "maxlength"
+{-# INLINE maxlength #-}
+
+{- | Combinator for the @media@ attribute.
+
+ Example:
+
+ > div ! media "bar" $ "Hello."
+
+ Result:
+
+ > <div media="bar">Hello.</div>
+-}
+media ::
+    ToLogStr s =>
+    -- | Attribute value.
+    s ->
+    -- | Resulting attribute.
+    Attr
+media = attr "media"
+{-# INLINE media #-}
+
+{- | Combinator for the @method@ attribute.
+
+ Example:
+
+ > div ! method "bar" $ "Hello."
+
+ Result:
+
+ > <div method="bar">Hello.</div>
+-}
+method ::
+    ToLogStr s =>
+    -- | Attribute value.
+    s ->
+    -- | Resulting attribute.
+    Attr
+method = attr "method"
+{-# INLINE method #-}
+
+{- | Combinator for the @min@ attribute.
+
+ Example:
+
+ > div ! min "bar" $ "Hello."
+
+ Result:
+
+ > <div min="bar">Hello.</div>
+-}
+min ::
+    ToLogStr s =>
+    -- | Attribute value.
+    s ->
+    -- | Resulting attribute.
+    Attr
+min = attr "min"
+{-# INLINE min #-}
+
+{- | Combinator for the @multiple@ attribute.
+
+ Example:
+
+ > div ! multiple "bar" $ "Hello."
+
+ Result:
+
+ > <div multiple="bar">Hello.</div>
+-}
+multiple ::
+    ToLogStr s =>
+    -- | Attribute value.
+    s ->
+    -- | Resulting attribute.
+    Attr
+multiple = attr "multiple"
+{-# INLINE multiple #-}
+
+{- | Combinator for the @name@ attribute.
+
+ Example:
+
+ > div ! name "bar" $ "Hello."
+
+ Result:
+
+ > <div name="bar">Hello.</div>
+-}
+name ::
+    ToLogStr s =>
+    -- | Attribute value.
+    s ->
+    -- | Resulting attribute.
+    Attr
+name = attr "name"
+{-# INLINE name #-}
+
+{- | Combinator for the @novalidate@ attribute.
+
+ Example:
+
+ > div ! novalidate "bar" $ "Hello."
+
+ Result:
+
+ > <div novalidate="bar">Hello.</div>
+-}
+novalidate ::
+    ToLogStr s =>
+    -- | Attribute value.
+    s ->
+    -- | Resulting attribute.
+    Attr
+novalidate = attr "novalidate"
+{-# INLINE novalidate #-}
+
+{- | Combinator for the @onbeforeonload@ attribute.
+
+ Example:
+
+ > div ! onbeforeonload "bar" $ "Hello."
+
+ Result:
+
+ > <div onbeforeonload="bar">Hello.</div>
+-}
+onbeforeonload ::
+    ToLogStr s =>
+    -- | Attribute value.
+    s ->
+    -- | Resulting attribute.
+    Attr
+onbeforeonload = attr "onbeforeonload"
+{-# INLINE onbeforeonload #-}
+
+{- | Combinator for the @onbeforeprint@ attribute.
+
+ Example:
+
+ > div ! onbeforeprint "bar" $ "Hello."
+
+ Result:
+
+ > <div onbeforeprint="bar">Hello.</div>
+-}
+onbeforeprint ::
+    ToLogStr s =>
+    -- | Attribute value.
+    s ->
+    -- | Resulting attribute.
+    Attr
+onbeforeprint = attr "onbeforeprint"
+{-# INLINE onbeforeprint #-}
+
+{- | Combinator for the @onblur@ attribute.
+
+ Example:
+
+ > div ! onblur "bar" $ "Hello."
+
+ Result:
+
+ > <div onblur="bar">Hello.</div>
+-}
+onblur ::
+    ToLogStr s =>
+    -- | Attribute value.
+    s ->
+    -- | Resulting attribute.
+    Attr
+onblur = attr "onblur"
+{-# INLINE onblur #-}
+
+{- | Combinator for the @oncanplay@ attribute.
+
+ Example:
+
+ > div ! oncanplay "bar" $ "Hello."
+
+ Result:
+
+ > <div oncanplay="bar">Hello.</div>
+-}
+oncanplay ::
+    ToLogStr s =>
+    -- | Attribute value.
+    s ->
+    -- | Resulting attribute.
+    Attr
+oncanplay = attr "oncanplay"
+{-# INLINE oncanplay #-}
+
+{- | Combinator for the @oncanplaythrough@ attribute.
+
+ Example:
+
+ > div ! oncanplaythrough "bar" $ "Hello."
+
+ Result:
+
+ > <div oncanplaythrough="bar">Hello.</div>
+-}
+oncanplaythrough ::
+    ToLogStr s =>
+    -- | Attribute value.
+    s ->
+    -- | Resulting attribute.
+    Attr
+oncanplaythrough = attr "oncanplaythrough"
+{-# INLINE oncanplaythrough #-}
+
+{- | Combinator for the @onchange@ attribute.
+
+ Example:
+
+ > div ! onchange "bar" $ "Hello."
+
+ Result:
+
+ > <div onchange="bar">Hello.</div>
+-}
+onchange ::
+    ToLogStr s =>
+    -- | Attribute value.
+    s ->
+    -- | Resulting attribute.
+    Attr
+onchange = attr "onchange"
+{-# INLINE onchange #-}
+
+{- | Combinator for the @onclick@ attribute.
+
+ Example:
+
+ > div ! onclick "bar" $ "Hello."
+
+ Result:
+
+ > <div onclick="bar">Hello.</div>
+-}
+onclick ::
+    ToLogStr s =>
+    -- | Attribute value.
+    s ->
+    -- | Resulting attribute.
+    Attr
+onclick = attr "onclick"
+{-# INLINE onclick #-}
+
+{- | Combinator for the @oncontextmenu@ attribute.
+
+ Example:
+
+ > div ! oncontextmenu "bar" $ "Hello."
+
+ Result:
+
+ > <div oncontextmenu="bar">Hello.</div>
+-}
+oncontextmenu ::
+    ToLogStr s =>
+    -- | Attribute value.
+    s ->
+    -- | Resulting attribute.
+    Attr
+oncontextmenu = attr "oncontextmenu"
+{-# INLINE oncontextmenu #-}
+
+{- | Combinator for the @ondblclick@ attribute.
+
+ Example:
+
+ > div ! ondblclick "bar" $ "Hello."
+
+ Result:
+
+ > <div ondblclick="bar">Hello.</div>
+-}
+ondblclick ::
+    ToLogStr s =>
+    -- | Attribute value.
+    s ->
+    -- | Resulting attribute.
+    Attr
+ondblclick = attr "ondblclick"
+{-# INLINE ondblclick #-}
+
+{- | Combinator for the @ondrag@ attribute.
+
+ Example:
+
+ > div ! ondrag "bar" $ "Hello."
+
+ Result:
+
+ > <div ondrag="bar">Hello.</div>
+-}
+ondrag ::
+    ToLogStr s =>
+    -- | Attribute value.
+    s ->
+    -- | Resulting attribute.
+    Attr
+ondrag = attr "ondrag"
+{-# INLINE ondrag #-}
+
+{- | Combinator for the @ondragend@ attribute.
+
+ Example:
+
+ > div ! ondragend "bar" $ "Hello."
+
+ Result:
+
+ > <div ondragend="bar">Hello.</div>
+-}
+ondragend ::
+    ToLogStr s =>
+    -- | Attribute value.
+    s ->
+    -- | Resulting attribute.
+    Attr
+ondragend = attr "ondragend"
+{-# INLINE ondragend #-}
+
+{- | Combinator for the @ondragenter@ attribute.
+
+ Example:
+
+ > div ! ondragenter "bar" $ "Hello."
+
+ Result:
+
+ > <div ondragenter="bar">Hello.</div>
+-}
+ondragenter ::
+    ToLogStr s =>
+    -- | Attribute value.
+    s ->
+    -- | Resulting attribute.
+    Attr
+ondragenter = attr "ondragenter"
+{-# INLINE ondragenter #-}
+
+{- | Combinator for the @ondragleave@ attribute.
+
+ Example:
+
+ > div ! ondragleave "bar" $ "Hello."
+
+ Result:
+
+ > <div ondragleave="bar">Hello.</div>
+-}
+ondragleave ::
+    ToLogStr s =>
+    -- | Attribute value.
+    s ->
+    -- | Resulting attribute.
+    Attr
+ondragleave = attr "ondragleave"
+{-# INLINE ondragleave #-}
+
+{- | Combinator for the @ondragover@ attribute.
+
+ Example:
+
+ > div ! ondragover "bar" $ "Hello."
+
+ Result:
+
+ > <div ondragover="bar">Hello.</div>
+-}
+ondragover ::
+    ToLogStr s =>
+    -- | Attribute value.
+    s ->
+    -- | Resulting attribute.
+    Attr
+ondragover = attr "ondragover"
+{-# INLINE ondragover #-}
+
+{- | Combinator for the @ondragstart@ attribute.
+
+ Example:
+
+ > div ! ondragstart "bar" $ "Hello."
+
+ Result:
+
+ > <div ondragstart="bar">Hello.</div>
+-}
+ondragstart ::
+    ToLogStr s =>
+    -- | Attribute value.
+    s ->
+    -- | Resulting attribute.
+    Attr
+ondragstart = attr "ondragstart"
+{-# INLINE ondragstart #-}
+
+{- | Combinator for the @ondrop@ attribute.
+
+ Example:
+
+ > div ! ondrop "bar" $ "Hello."
+
+ Result:
+
+ > <div ondrop="bar">Hello.</div>
+-}
+ondrop ::
+    ToLogStr s =>
+    -- | Attribute value.
+    s ->
+    -- | Resulting attribute.
+    Attr
+ondrop = attr "ondrop"
+{-# INLINE ondrop #-}
+
+{- | Combinator for the @ondurationchange@ attribute.
+
+ Example:
+
+ > div ! ondurationchange "bar" $ "Hello."
+
+ Result:
+
+ > <div ondurationchange="bar">Hello.</div>
+-}
+ondurationchange ::
+    ToLogStr s =>
+    -- | Attribute value.
+    s ->
+    -- | Resulting attribute.
+    Attr
+ondurationchange = attr "ondurationchange"
+{-# INLINE ondurationchange #-}
+
+{- | Combinator for the @onemptied@ attribute.
+
+ Example:
+
+ > div ! onemptied "bar" $ "Hello."
+
+ Result:
+
+ > <div onemptied="bar">Hello.</div>
+-}
+onemptied ::
+    ToLogStr s =>
+    -- | Attribute value.
+    s ->
+    -- | Resulting attribute.
+    Attr
+onemptied = attr "onemptied"
+{-# INLINE onemptied #-}
+
+{- | Combinator for the @onended@ attribute.
+
+ Example:
+
+ > div ! onended "bar" $ "Hello."
+
+ Result:
+
+ > <div onended="bar">Hello.</div>
+-}
+onended ::
+    ToLogStr s =>
+    -- | Attribute value.
+    s ->
+    -- | Resulting attribute.
+    Attr
+onended = attr "onended"
+{-# INLINE onended #-}
+
+{- | Combinator for the @onerror@ attribute.
+
+ Example:
+
+ > div ! onerror "bar" $ "Hello."
+
+ Result:
+
+ > <div onerror="bar">Hello.</div>
+-}
+onerror ::
+    ToLogStr s =>
+    -- | Attribute value.
+    s ->
+    -- | Resulting attribute.
+    Attr
+onerror = attr "onerror"
+{-# INLINE onerror #-}
+
+{- | Combinator for the @onfocus@ attribute.
+
+ Example:
+
+ > div ! onfocus "bar" $ "Hello."
+
+ Result:
+
+ > <div onfocus="bar">Hello.</div>
+-}
+onfocus ::
+    ToLogStr s =>
+    -- | Attribute value.
+    s ->
+    -- | Resulting attribute.
+    Attr
+onfocus = attr "onfocus"
+{-# INLINE onfocus #-}
+
+{- | Combinator for the @onformchange@ attribute.
+
+ Example:
+
+ > div ! onformchange "bar" $ "Hello."
+
+ Result:
+
+ > <div onformchange="bar">Hello.</div>
+-}
+onformchange ::
+    ToLogStr s =>
+    -- | Attribute value.
+    s ->
+    -- | Resulting attribute.
+    Attr
+onformchange = attr "onformchange"
+{-# INLINE onformchange #-}
+
+{- | Combinator for the @onforminput@ attribute.
+
+ Example:
+
+ > div ! onforminput "bar" $ "Hello."
+
+ Result:
+
+ > <div onforminput="bar">Hello.</div>
+-}
+onforminput ::
+    ToLogStr s =>
+    -- | Attribute value.
+    s ->
+    -- | Resulting attribute.
+    Attr
+onforminput = attr "onforminput"
+{-# INLINE onforminput #-}
+
+{- | Combinator for the @onhaschange@ attribute.
+
+ Example:
+
+ > div ! onhaschange "bar" $ "Hello."
+
+ Result:
+
+ > <div onhaschange="bar">Hello.</div>
+-}
+onhaschange ::
+    ToLogStr s =>
+    -- | Attribute value.
+    s ->
+    -- | Resulting attribute.
+    Attr
+onhaschange = attr "onhaschange"
+{-# INLINE onhaschange #-}
+
+{- | Combinator for the @oninput@ attribute.
+
+ Example:
+
+ > div ! oninput "bar" $ "Hello."
+
+ Result:
+
+ > <div oninput="bar">Hello.</div>
+-}
+oninput ::
+    ToLogStr s =>
+    -- | Attribute value.
+    s ->
+    -- | Resulting attribute.
+    Attr
+oninput = attr "oninput"
+{-# INLINE oninput #-}
+
+{- | Combinator for the @oninvalid@ attribute.
+
+ Example:
+
+ > div ! oninvalid "bar" $ "Hello."
+
+ Result:
+
+ > <div oninvalid="bar">Hello.</div>
+-}
+oninvalid ::
+    ToLogStr s =>
+    -- | Attribute value.
+    s ->
+    -- | Resulting attribute.
+    Attr
+oninvalid = attr "oninvalid"
+{-# INLINE oninvalid #-}
+
+{- | Combinator for the @onkeydown@ attribute.
+
+ Example:
+
+ > div ! onkeydown "bar" $ "Hello."
+
+ Result:
+
+ > <div onkeydown="bar">Hello.</div>
+-}
+onkeydown ::
+    ToLogStr s =>
+    -- | Attribute value.
+    s ->
+    -- | Resulting attribute.
+    Attr
+onkeydown = attr "onkeydown"
+{-# INLINE onkeydown #-}
+
+{- | Combinator for the @onkeyup@ attribute.
+
+ Example:
+
+ > div ! onkeyup "bar" $ "Hello."
+
+ Result:
+
+ > <div onkeyup="bar">Hello.</div>
+-}
+onkeyup ::
+    ToLogStr s =>
+    -- | Attribute value.
+    s ->
+    -- | Resulting attribute.
+    Attr
+onkeyup = attr "onkeyup"
+{-# INLINE onkeyup #-}
+
+{- | Combinator for the @onload@ attribute.
+
+ Example:
+
+ > div ! onload "bar" $ "Hello."
+
+ Result:
+
+ > <div onload="bar">Hello.</div>
+-}
+onload ::
+    ToLogStr s =>
+    -- | Attribute value.
+    s ->
+    -- | Resulting attribute.
+    Attr
+onload = attr "onload"
+{-# INLINE onload #-}
+
+{- | Combinator for the @onloadeddata@ attribute.
+
+ Example:
+
+ > div ! onloadeddata "bar" $ "Hello."
+
+ Result:
+
+ > <div onloadeddata="bar">Hello.</div>
+-}
+onloadeddata ::
+    ToLogStr s =>
+    -- | Attribute value.
+    s ->
+    -- | Resulting attribute.
+    Attr
+onloadeddata = attr "onloadeddata"
+{-# INLINE onloadeddata #-}
+
+{- | Combinator for the @onloadedmetadata@ attribute.
+
+ Example:
+
+ > div ! onloadedmetadata "bar" $ "Hello."
+
+ Result:
+
+ > <div onloadedmetadata="bar">Hello.</div>
+-}
+onloadedmetadata ::
+    ToLogStr s =>
+    -- | Attribute value.
+    s ->
+    -- | Resulting attribute.
+    Attr
+onloadedmetadata = attr "onloadedmetadata"
+{-# INLINE onloadedmetadata #-}
+
+{- | Combinator for the @onloadstart@ attribute.
+
+ Example:
+
+ > div ! onloadstart "bar" $ "Hello."
+
+ Result:
+
+ > <div onloadstart="bar">Hello.</div>
+-}
+onloadstart ::
+    ToLogStr s =>
+    -- | Attribute value.
+    s ->
+    -- | Resulting attribute.
+    Attr
+onloadstart = attr "onloadstart"
+{-# INLINE onloadstart #-}
+
+{- | Combinator for the @onmessage@ attribute.
+
+ Example:
+
+ > div ! onmessage "bar" $ "Hello."
+
+ Result:
+
+ > <div onmessage="bar">Hello.</div>
+-}
+onmessage ::
+    ToLogStr s =>
+    -- | Attribute value.
+    s ->
+    -- | Resulting attribute.
+    Attr
+onmessage = attr "onmessage"
+{-# INLINE onmessage #-}
+
+{- | Combinator for the @onmousedown@ attribute.
+
+ Example:
+
+ > div ! onmousedown "bar" $ "Hello."
+
+ Result:
+
+ > <div onmousedown="bar">Hello.</div>
+-}
+onmousedown ::
+    ToLogStr s =>
+    -- | Attribute value.
+    s ->
+    -- | Resulting attribute.
+    Attr
+onmousedown = attr "onmousedown"
+{-# INLINE onmousedown #-}
+
+{- | Combinator for the @onmousemove@ attribute.
+
+ Example:
+
+ > div ! onmousemove "bar" $ "Hello."
+
+ Result:
+
+ > <div onmousemove="bar">Hello.</div>
+-}
+onmousemove ::
+    ToLogStr s =>
+    -- | Attribute value.
+    s ->
+    -- | Resulting attribute.
+    Attr
+onmousemove = attr "onmousemove"
+{-# INLINE onmousemove #-}
+
+{- | Combinator for the @onmouseout@ attribute.
+
+ Example:
+
+ > div ! onmouseout "bar" $ "Hello."
+
+ Result:
+
+ > <div onmouseout="bar">Hello.</div>
+-}
+onmouseout ::
+    ToLogStr s =>
+    -- | Attribute value.
+    s ->
+    -- | Resulting attribute.
+    Attr
+onmouseout = attr "onmouseout"
+{-# INLINE onmouseout #-}
+
+{- | Combinator for the @onmouseover@ attribute.
+
+ Example:
+
+ > div ! onmouseover "bar" $ "Hello."
+
+ Result:
+
+ > <div onmouseover="bar">Hello.</div>
+-}
+onmouseover ::
+    ToLogStr s =>
+    -- | Attribute value.
+    s ->
+    -- | Resulting attribute.
+    Attr
+onmouseover = attr "onmouseover"
+{-# INLINE onmouseover #-}
+
+{- | Combinator for the @onmouseup@ attribute.
+
+ Example:
+
+ > div ! onmouseup "bar" $ "Hello."
+
+ Result:
+
+ > <div onmouseup="bar">Hello.</div>
+-}
+onmouseup ::
+    ToLogStr s =>
+    -- | Attribute value.
+    s ->
+    -- | Resulting attribute.
+    Attr
+onmouseup = attr "onmouseup"
+{-# INLINE onmouseup #-}
+
+{- | Combinator for the @onmousewheel@ attribute.
+
+ Example:
+
+ > div ! onmousewheel "bar" $ "Hello."
+
+ Result:
+
+ > <div onmousewheel="bar">Hello.</div>
+-}
+onmousewheel ::
+    ToLogStr s =>
+    -- | Attribute value.
+    s ->
+    -- | Resulting attribute.
+    Attr
+onmousewheel = attr "onmousewheel"
+{-# INLINE onmousewheel #-}
+
+{- | Combinator for the @ononline@ attribute.
+
+ Example:
+
+ > div ! ononline "bar" $ "Hello."
+
+ Result:
+
+ > <div ononline="bar">Hello.</div>
+-}
+ononline ::
+    ToLogStr s =>
+    -- | Attribute value.
+    s ->
+    -- | Resulting attribute.
+    Attr
+ononline = attr "ononline"
+{-# INLINE ononline #-}
+
+{- | Combinator for the @onpagehide@ attribute.
+
+ Example:
+
+ > div ! onpagehide "bar" $ "Hello."
+
+ Result:
+
+ > <div onpagehide="bar">Hello.</div>
+-}
+onpagehide ::
+    ToLogStr s =>
+    -- | Attribute value.
+    s ->
+    -- | Resulting attribute.
+    Attr
+onpagehide = attr "onpagehide"
+{-# INLINE onpagehide #-}
+
+{- | Combinator for the @onpageshow@ attribute.
+
+ Example:
+
+ > div ! onpageshow "bar" $ "Hello."
+
+ Result:
+
+ > <div onpageshow="bar">Hello.</div>
+-}
+onpageshow ::
+    ToLogStr s =>
+    -- | Attribute value.
+    s ->
+    -- | Resulting attribute.
+    Attr
+onpageshow = attr "onpageshow"
+{-# INLINE onpageshow #-}
+
+{- | Combinator for the @onpause@ attribute.
+
+ Example:
+
+ > div ! onpause "bar" $ "Hello."
+
+ Result:
+
+ > <div onpause="bar">Hello.</div>
+-}
+onpause ::
+    ToLogStr s =>
+    -- | Attribute value.
+    s ->
+    -- | Resulting attribute.
+    Attr
+onpause = attr "onpause"
+{-# INLINE onpause #-}
+
+{- | Combinator for the @onplay@ attribute.
+
+ Example:
+
+ > div ! onplay "bar" $ "Hello."
+
+ Result:
+
+ > <div onplay="bar">Hello.</div>
+-}
+onplay ::
+    ToLogStr s =>
+    -- | Attribute value.
+    s ->
+    -- | Resulting attribute.
+    Attr
+onplay = attr "onplay"
+{-# INLINE onplay #-}
+
+{- | Combinator for the @onplaying@ attribute.
+
+ Example:
+
+ > div ! onplaying "bar" $ "Hello."
+
+ Result:
+
+ > <div onplaying="bar">Hello.</div>
+-}
+onplaying ::
+    ToLogStr s =>
+    -- | Attribute value.
+    s ->
+    -- | Resulting attribute.
+    Attr
+onplaying = attr "onplaying"
+{-# INLINE onplaying #-}
+
+{- | Combinator for the @onprogress@ attribute.
+
+ Example:
+
+ > div ! onprogress "bar" $ "Hello."
+
+ Result:
+
+ > <div onprogress="bar">Hello.</div>
+-}
+onprogress ::
+    ToLogStr s =>
+    -- | Attribute value.
+    s ->
+    -- | Resulting attribute.
+    Attr
+onprogress = attr "onprogress"
+{-# INLINE onprogress #-}
+
+{- | Combinator for the @onpropstate@ attribute.
+
+ Example:
+
+ > div ! onpropstate "bar" $ "Hello."
+
+ Result:
+
+ > <div onpropstate="bar">Hello.</div>
+-}
+onpropstate ::
+    ToLogStr s =>
+    -- | Attribute value.
+    s ->
+    -- | Resulting attribute.
+    Attr
+onpropstate = attr "onpropstate"
+{-# INLINE onpropstate #-}
+
+{- | Combinator for the @onratechange@ attribute.
+
+ Example:
+
+ > div ! onratechange "bar" $ "Hello."
+
+ Result:
+
+ > <div onratechange="bar">Hello.</div>
+-}
+onratechange ::
+    ToLogStr s =>
+    -- | Attribute value.
+    s ->
+    -- | Resulting attribute.
+    Attr
+onratechange = attr "onratechange"
+{-# INLINE onratechange #-}
+
+{- | Combinator for the @onreadystatechange@ attribute.
+
+ Example:
+
+ > div ! onreadystatechange "bar" $ "Hello."
+
+ Result:
+
+ > <div onreadystatechange="bar">Hello.</div>
+-}
+onreadystatechange ::
+    ToLogStr s =>
+    -- | Attribute value.
+    s ->
+    -- | Resulting attribute.
+    Attr
+onreadystatechange = attr "onreadystatechange"
+{-# INLINE onreadystatechange #-}
+
+{- | Combinator for the @onredo@ attribute.
+
+ Example:
+
+ > div ! onredo "bar" $ "Hello."
+
+ Result:
+
+ > <div onredo="bar">Hello.</div>
+-}
+onredo ::
+    ToLogStr s =>
+    -- | Attribute value.
+    s ->
+    -- | Resulting attribute.
+    Attr
+onredo = attr "onredo"
+{-# INLINE onredo #-}
+
+{- | Combinator for the @onresize@ attribute.
+
+ Example:
+
+ > div ! onresize "bar" $ "Hello."
+
+ Result:
+
+ > <div onresize="bar">Hello.</div>
+-}
+onresize ::
+    ToLogStr s =>
+    -- | Attribute value.
+    s ->
+    -- | Resulting attribute.
+    Attr
+onresize = attr "onresize"
+{-# INLINE onresize #-}
+
+{- | Combinator for the @onscroll@ attribute.
+
+ Example:
+
+ > div ! onscroll "bar" $ "Hello."
+
+ Result:
+
+ > <div onscroll="bar">Hello.</div>
+-}
+onscroll ::
+    ToLogStr s =>
+    -- | Attribute value.
+    s ->
+    -- | Resulting attribute.
+    Attr
+onscroll = attr "onscroll"
+{-# INLINE onscroll #-}
+
+{- | Combinator for the @onseeked@ attribute.
+
+ Example:
+
+ > div ! onseeked "bar" $ "Hello."
+
+ Result:
+
+ > <div onseeked="bar">Hello.</div>
+-}
+onseeked ::
+    ToLogStr s =>
+    -- | Attribute value.
+    s ->
+    -- | Resulting attribute.
+    Attr
+onseeked = attr "onseeked"
+{-# INLINE onseeked #-}
+
+{- | Combinator for the @onseeking@ attribute.
+
+ Example:
+
+ > div ! onseeking "bar" $ "Hello."
+
+ Result:
+
+ > <div onseeking="bar">Hello.</div>
+-}
+onseeking ::
+    ToLogStr s =>
+    -- | Attribute value.
+    s ->
+    -- | Resulting attribute.
+    Attr
+onseeking = attr "onseeking"
+{-# INLINE onseeking #-}
+
+{- | Combinator for the @onselect@ attribute.
+
+ Example:
+
+ > div ! onselect "bar" $ "Hello."
+
+ Result:
+
+ > <div onselect="bar">Hello.</div>
+-}
+onselect ::
+    ToLogStr s =>
+    -- | Attribute value.
+    s ->
+    -- | Resulting attribute.
+    Attr
+onselect = attr "onselect"
+{-# INLINE onselect #-}
+
+{- | Combinator for the @onstalled@ attribute.
+
+ Example:
+
+ > div ! onstalled "bar" $ "Hello."
+
+ Result:
+
+ > <div onstalled="bar">Hello.</div>
+-}
+onstalled ::
+    ToLogStr s =>
+    -- | Attribute value.
+    s ->
+    -- | Resulting attribute.
+    Attr
+onstalled = attr "onstalled"
+{-# INLINE onstalled #-}
+
+{- | Combinator for the @onstorage@ attribute.
+
+ Example:
+
+ > div ! onstorage "bar" $ "Hello."
+
+ Result:
+
+ > <div onstorage="bar">Hello.</div>
+-}
+onstorage ::
+    ToLogStr s =>
+    -- | Attribute value.
+    s ->
+    -- | Resulting attribute.
+    Attr
+onstorage = attr "onstorage"
+{-# INLINE onstorage #-}
+
+{- | Combinator for the @onsubmit@ attribute.
+
+ Example:
+
+ > div ! onsubmit "bar" $ "Hello."
+
+ Result:
+
+ > <div onsubmit="bar">Hello.</div>
+-}
+onsubmit ::
+    ToLogStr s =>
+    -- | Attribute value.
+    s ->
+    -- | Resulting attribute.
+    Attr
+onsubmit = attr "onsubmit"
+{-# INLINE onsubmit #-}
+
+{- | Combinator for the @onsuspend@ attribute.
+
+ Example:
+
+ > div ! onsuspend "bar" $ "Hello."
+
+ Result:
+
+ > <div onsuspend="bar">Hello.</div>
+-}
+onsuspend ::
+    ToLogStr s =>
+    -- | Attribute value.
+    s ->
+    -- | Resulting attribute.
+    Attr
+onsuspend = attr "onsuspend"
+{-# INLINE onsuspend #-}
+
+{- | Combinator for the @ontimeupdate@ attribute.
+
+ Example:
+
+ > div ! ontimeupdate "bar" $ "Hello."
+
+ Result:
+
+ > <div ontimeupdate="bar">Hello.</div>
+-}
+ontimeupdate ::
+    ToLogStr s =>
+    -- | Attribute value.
+    s ->
+    -- | Resulting attribute.
+    Attr
+ontimeupdate = attr "ontimeupdate"
+{-# INLINE ontimeupdate #-}
+
+{- | Combinator for the @onundo@ attribute.
+
+ Example:
+
+ > div ! onundo "bar" $ "Hello."
+
+ Result:
+
+ > <div onundo="bar">Hello.</div>
+-}
+onundo ::
+    ToLogStr s =>
+    -- | Attribute value.
+    s ->
+    -- | Resulting attribute.
+    Attr
+onundo = attr "onundo"
+{-# INLINE onundo #-}
+
+{- | Combinator for the @onunload@ attribute.
+
+ Example:
+
+ > div ! onunload "bar" $ "Hello."
+
+ Result:
+
+ > <div onunload="bar">Hello.</div>
+-}
+onunload ::
+    ToLogStr s =>
+    -- | Attribute value.
+    s ->
+    -- | Resulting attribute.
+    Attr
+onunload = attr "onunload"
+{-# INLINE onunload #-}
+
+{- | Combinator for the @onvolumechange@ attribute.
+
+ Example:
+
+ > div ! onvolumechange "bar" $ "Hello."
+
+ Result:
+
+ > <div onvolumechange="bar">Hello.</div>
+-}
+onvolumechange ::
+    ToLogStr s =>
+    -- | Attribute value.
+    s ->
+    -- | Resulting attribute.
+    Attr
+onvolumechange = attr "onvolumechange"
+{-# INLINE onvolumechange #-}
+
+{- | Combinator for the @onwaiting@ attribute.
+
+ Example:
+
+ > div ! onwaiting "bar" $ "Hello."
+
+ Result:
+
+ > <div onwaiting="bar">Hello.</div>
+-}
+onwaiting ::
+    ToLogStr s =>
+    -- | Attribute value.
+    s ->
+    -- | Resulting attribute.
+    Attr
+onwaiting = attr "onwaiting"
+{-# INLINE onwaiting #-}
+
+{- | Combinator for the @open@ attribute.
+
+ Example:
+
+ > div ! open "bar" $ "Hello."
+
+ Result:
+
+ > <div open="bar">Hello.</div>
+-}
+open ::
+    ToLogStr s =>
+    -- | Attribute value.
+    s ->
+    -- | Resulting attribute.
+    Attr
+open = attr "open"
+{-# INLINE open #-}
+
+{- | Combinator for the @optimum@ attribute.
+
+ Example:
+
+ > div ! optimum "bar" $ "Hello."
+
+ Result:
+
+ > <div optimum="bar">Hello.</div>
+-}
+optimum ::
+    ToLogStr s =>
+    -- | Attribute value.
+    s ->
+    -- | Resulting attribute.
+    Attr
+optimum = attr "optimum"
+{-# INLINE optimum #-}
+
+{- | Combinator for the @pattern@ attribute.
+
+ Example:
+
+ > div ! pattern "bar" $ "Hello."
+
+ Result:
+
+ > <div pattern="bar">Hello.</div>
+-}
+pattern ::
+    ToLogStr s =>
+    -- | Attribute value.
+    s ->
+    -- | Resulting attribute.
+    Attr
+pattern = attr "pattern"
+{-# INLINE pattern #-}
+
+{- | Combinator for the @ping@ attribute.
+
+ Example:
+
+ > div ! ping "bar" $ "Hello."
+
+ Result:
+
+ > <div ping="bar">Hello.</div>
+-}
+ping ::
+    ToLogStr s =>
+    -- | Attribute value.
+    s ->
+    -- | Resulting attribute.
+    Attr
+ping = attr "ping"
+{-# INLINE ping #-}
+
+{- | Combinator for the @placeholder@ attribute.
+
+ Example:
+
+ > div ! placeholder "bar" $ "Hello."
+
+ Result:
+
+ > <div placeholder="bar">Hello.</div>
+-}
+placeholder ::
+    ToLogStr s =>
+    -- | Attribute value.
+    s ->
+    -- | Resulting attribute.
+    Attr
+placeholder = attr "placeholder"
+{-# INLINE placeholder #-}
+
+{- | Combinator for the @preload@ attribute.
+
+ Example:
+
+ > div ! preload "bar" $ "Hello."
+
+ Result:
+
+ > <div preload="bar">Hello.</div>
+-}
+preload ::
+    ToLogStr s =>
+    -- | Attribute value.
+    s ->
+    -- | Resulting attribute.
+    Attr
+preload = attr "preload"
+{-# INLINE preload #-}
+
+{- | Combinator for the @pubdate@ attribute.
+
+ Example:
+
+ > div ! pubdate "bar" $ "Hello."
+
+ Result:
+
+ > <div pubdate="bar">Hello.</div>
+-}
+pubdate ::
+    ToLogStr s =>
+    -- | Attribute value.
+    s ->
+    -- | Resulting attribute.
+    Attr
+pubdate = attr "pubdate"
+{-# INLINE pubdate #-}
+
+{- | Combinator for the @radiogroup@ attribute.
+
+ Example:
+
+ > div ! radiogroup "bar" $ "Hello."
+
+ Result:
+
+ > <div radiogroup="bar">Hello.</div>
+-}
+radiogroup ::
+    ToLogStr s =>
+    -- | Attribute value.
+    s ->
+    -- | Resulting attribute.
+    Attr
+radiogroup = attr "radiogroup"
+{-# INLINE radiogroup #-}
+
+{- | Combinator for the @readonly@ attribute.
+
+ Example:
+
+ > div ! readonly "bar" $ "Hello."
+
+ Result:
+
+ > <div readonly="bar">Hello.</div>
+-}
+readonly ::
+    ToLogStr s =>
+    -- | Attribute value.
+    s ->
+    -- | Resulting attribute.
+    Attr
+readonly = attr "readonly"
+{-# INLINE readonly #-}
+
+{- | Combinator for the @rel@ attribute.
+
+ Example:
+
+ > div ! rel "bar" $ "Hello."
+
+ Result:
+
+ > <div rel="bar">Hello.</div>
+-}
+rel ::
+    ToLogStr s =>
+    -- | Attribute value.
+    s ->
+    -- | Resulting attribute.
+    Attr
+rel = attr "rel"
+{-# INLINE rel #-}
+
+{- | Combinator for the @required@ attribute.
+
+ Example:
+
+ > div ! required "bar" $ "Hello."
+
+ Result:
+
+ > <div required="bar">Hello.</div>
+-}
+required ::
+    ToLogStr s =>
+    -- | Attribute value.
+    s ->
+    -- | Resulting attribute.
+    Attr
+required = attr "required"
+{-# INLINE required #-}
+
+{- | Combinator for the @reversed@ attribute.
+
+ Example:
+
+ > div ! reversed "bar" $ "Hello."
+
+ Result:
+
+ > <div reversed="bar">Hello.</div>
+-}
+reversed ::
+    ToLogStr s =>
+    -- | Attribute value.
+    s ->
+    -- | Resulting attribute.
+    Attr
+reversed = attr "reversed"
+{-# INLINE reversed #-}
+
+{- | Combinator for the @role@ attribute.
+
+ Example:
+
+ > div ! role "bar" $ "Hello."
+
+ Result:
+
+ > <div role="bar">Hello.</div>
+-}
+role ::
+    ToLogStr s =>
+    -- | Attribute value.
+    s ->
+    -- | Resulting attribute.
+    Attr
+role = attr "role"
+{-# INLINE role #-}
+
+{- | Combinator for the @rows@ attribute.
+
+ Example:
+
+ > div ! rows "bar" $ "Hello."
+
+ Result:
+
+ > <div rows="bar">Hello.</div>
+-}
+rows ::
+    ToLogStr s =>
+    -- | Attribute value.
+    s ->
+    -- | Resulting attribute.
+    Attr
+rows = attr "rows"
+{-# INLINE rows #-}
+
+{- | Combinator for the @rowspan@ attribute.
+
+ Example:
+
+ > div ! rowspan "bar" $ "Hello."
+
+ Result:
+
+ > <div rowspan="bar">Hello.</div>
+-}
+rowspan ::
+    ToLogStr s =>
+    -- | Attribute value.
+    s ->
+    -- | Resulting attribute.
+    Attr
+rowspan = attr "rowspan"
+{-# INLINE rowspan #-}
+
+{- | Combinator for the @sandbox@ attribute.
+
+ Example:
+
+ > div ! sandbox "bar" $ "Hello."
+
+ Result:
+
+ > <div sandbox="bar">Hello.</div>
+-}
+sandbox ::
+    ToLogStr s =>
+    -- | Attribute value.
+    s ->
+    -- | Resulting attribute.
+    Attr
+sandbox = attr "sandbox"
+{-# INLINE sandbox #-}
+
+{- | Combinator for the @scope@ attribute.
+
+ Example:
+
+ > div ! scope "bar" $ "Hello."
+
+ Result:
+
+ > <div scope="bar">Hello.</div>
+-}
+scope ::
+    ToLogStr s =>
+    -- | Attribute value.
+    s ->
+    -- | Resulting attribute.
+    Attr
+scope = attr "scope"
+{-# INLINE scope #-}
+
+{- | Combinator for the @scoped@ attribute.
+
+ Example:
+
+ > div ! scoped "bar" $ "Hello."
+
+ Result:
+
+ > <div scoped="bar">Hello.</div>
+-}
+scoped ::
+    ToLogStr s =>
+    -- | Attribute value.
+    s ->
+    -- | Resulting attribute.
+    Attr
+scoped = attr "scoped"
+{-# INLINE scoped #-}
+
+{- | Combinator for the @seamless@ attribute.
+
+ Example:
+
+ > div ! seamless "bar" $ "Hello."
+
+ Result:
+
+ > <div seamless="bar">Hello.</div>
+-}
+seamless ::
+    ToLogStr s =>
+    -- | Attribute value.
+    s ->
+    -- | Resulting attribute.
+    Attr
+seamless = attr "seamless"
+{-# INLINE seamless #-}
+
+{- | Combinator for the @selected@ attribute.
+
+ Example:
+
+ > div ! selected "bar" $ "Hello."
+
+ Result:
+
+ > <div selected="bar">Hello.</div>
+-}
+selected ::
+    ToLogStr s =>
+    -- | Attribute value.
+    s ->
+    -- | Resulting attribute.
+    Attr
+selected = attr "selected"
+{-# INLINE selected #-}
+
+{- | Combinator for the @shape@ attribute.
+
+ Example:
+
+ > div ! shape "bar" $ "Hello."
+
+ Result:
+
+ > <div shape="bar">Hello.</div>
+-}
+shape ::
+    ToLogStr s =>
+    -- | Attribute value.
+    s ->
+    -- | Resulting attribute.
+    Attr
+shape = attr "shape"
+{-# INLINE shape #-}
+
+{- | Combinator for the @size@ attribute.
+
+ Example:
+
+ > div ! size "bar" $ "Hello."
+
+ Result:
+
+ > <div size="bar">Hello.</div>
+-}
+size ::
+    ToLogStr s =>
+    -- | Attribute value.
+    s ->
+    -- | Resulting attribute.
+    Attr
+size = attr "size"
+{-# INLINE size #-}
+
+{- | Combinator for the @sizes@ attribute.
+
+ Example:
+
+ > div ! sizes "bar" $ "Hello."
+
+ Result:
+
+ > <div sizes="bar">Hello.</div>
+-}
+sizes ::
+    ToLogStr s =>
+    -- | Attribute value.
+    s ->
+    -- | Resulting attribute.
+    Attr
+sizes = attr "sizes"
+{-# INLINE sizes #-}
+
+{- | Combinator for the @span@ attribute.
+
+ Example:
+
+ > div ! span "bar" $ "Hello."
+
+ Result:
+
+ > <div span="bar">Hello.</div>
+-}
+span ::
+    ToLogStr s =>
+    -- | Attribute value.
+    s ->
+    -- | Resulting attribute.
+    Attr
+span = attr "span"
+{-# INLINE span #-}
+
+{- | Combinator for the @spellcheck@ attribute.
+
+ Example:
+
+ > div ! spellcheck "bar" $ "Hello."
+
+ Result:
+
+ > <div spellcheck="bar">Hello.</div>
+-}
+spellcheck ::
+    ToLogStr s =>
+    -- | Attribute value.
+    s ->
+    -- | Resulting attribute.
+    Attr
+spellcheck = attr "spellcheck"
+{-# INLINE spellcheck #-}
+
+{- | Combinator for the @src@ attribute.
+
+ Example:
+
+ > div ! src "bar" $ "Hello."
+
+ Result:
+
+ > <div src="bar">Hello.</div>
+-}
+src ::
+    ToLogStr s =>
+    -- | Attribute value.
+    s ->
+    -- | Resulting attribute.
+    Attr
+src = attr "src"
+{-# INLINE src #-}
+
+{- | Combinator for the @srcdoc@ attribute.
+
+ Example:
+
+ > div ! srcdoc "bar" $ "Hello."
+
+ Result:
+
+ > <div srcdoc="bar">Hello.</div>
+-}
+srcdoc ::
+    ToLogStr s =>
+    -- | Attribute value.
+    s ->
+    -- | Resulting attribute.
+    Attr
+srcdoc = attr "srcdoc"
+{-# INLINE srcdoc #-}
+
+{- | Combinator for the @start@ attribute.
+
+ Example:
+
+ > div ! start "bar" $ "Hello."
+
+ Result:
+
+ > <div start="bar">Hello.</div>
+-}
+start ::
+    ToLogStr s =>
+    -- | Attribute value.
+    s ->
+    -- | Resulting attribute.
+    Attr
+start = attr "start"
+{-# INLINE start #-}
+
+{- | Combinator for the @step@ attribute.
+
+ Example:
+
+ > div ! step "bar" $ "Hello."
+
+ Result:
+
+ > <div step="bar">Hello.</div>
+-}
+step ::
+    ToLogStr s =>
+    -- | Attribute value.
+    s ->
+    -- | Resulting attribute.
+    Attr
+step = attr "step"
+{-# INLINE step #-}
+
+{- | Combinator for the @style@ attribute.
+
+ Example:
+
+ > div ! style "bar" $ "Hello."
+
+ Result:
+
+ > <div style="bar">Hello.</div>
+-}
+style ::
+    ToLogStr s =>
+    -- | Attribute value.
+    s ->
+    -- | Resulting attribute.
+    Attr
+style = attr "style"
+{-# INLINE style #-}
+
+{- | Combinator for the @subject@ attribute.
+
+ Example:
+
+ > div ! subject "bar" $ "Hello."
+
+ Result:
+
+ > <div subject="bar">Hello.</div>
+-}
+subject ::
+    ToLogStr s =>
+    -- | Attribute value.
+    s ->
+    -- | Resulting attribute.
+    Attr
+subject = attr "subject"
+{-# INLINE subject #-}
+
+{- | Combinator for the @summary@ attribute.
+
+ Example:
+
+ > div ! summary "bar" $ "Hello."
+
+ Result:
+
+ > <div summary="bar">Hello.</div>
+-}
+summary ::
+    ToLogStr s =>
+    -- | Attribute value.
+    s ->
+    -- | Resulting attribute.
+    Attr
+summary = attr "summary"
+{-# INLINE summary #-}
+
+{- | Combinator for the @tabindex@ attribute.
+
+ Example:
+
+ > div ! tabindex "bar" $ "Hello."
+
+ Result:
+
+ > <div tabindex="bar">Hello.</div>
+-}
+tabindex ::
+    ToLogStr s =>
+    -- | Attribute value.
+    s ->
+    -- | Resulting attribute.
+    Attr
+tabindex = attr "tabindex"
+{-# INLINE tabindex #-}
+
+{- | Combinator for the @target@ attribute.
+
+ Example:
+
+ > div ! target "bar" $ "Hello."
+
+ Result:
+
+ > <div target="bar">Hello.</div>
+-}
+target ::
+    ToLogStr s =>
+    -- | Attribute value.
+    s ->
+    -- | Resulting attribute.
+    Attr
+target = attr "target"
+{-# INLINE target #-}
+
+{- | Combinator for the @title@ attribute.
+
+ Example:
+
+ > div ! title "bar" $ "Hello."
+
+ Result:
+
+ > <div title="bar">Hello.</div>
+-}
+title ::
+    ToLogStr s =>
+    -- | Attribute value.
+    s ->
+    -- | Resulting attribute.
+    Attr
+title = attr "title"
+{-# INLINE title #-}
+
+{- | Combinator for the @type@ attribute.
+
+ Example:
+
+ > div ! type_ "bar" $ "Hello."
+
+ Result:
+
+ > <div type="bar">Hello.</div>
+-}
+type_ ::
+    ToLogStr s =>
+    -- | Attribute value.
+    s ->
+    -- | Resulting attribute.
+    Attr
+type_ = attr "type"
+{-# INLINE type_ #-}
+
+{- | Combinator for the @usemap@ attribute.
+
+ Example:
+
+ > div ! usemap "bar" $ "Hello."
+
+ Result:
+
+ > <div usemap="bar">Hello.</div>
+-}
+usemap ::
+    ToLogStr s =>
+    -- | Attribute value.
+    s ->
+    -- | Resulting attribute.
+    Attr
+usemap = attr "usemap"
+{-# INLINE usemap #-}
+
+{- | Combinator for the @value@ attribute.
+
+ Example:
+
+ > div ! value "bar" $ "Hello."
+
+ Result:
+
+ > <div value="bar">Hello.</div>
+-}
+value ::
+    ToLogStr s =>
+    -- | Attribute value.
+    s ->
+    -- | Resulting attribute.
+    Attr
+value = attr "value"
+{-# INLINE value #-}
+
+{- | Combinator for the @width@ attribute.
+
+ Example:
+
+ > div ! width "bar" $ "Hello."
+
+ Result:
+
+ > <div width="bar">Hello.</div>
+-}
+width ::
+    ToLogStr s =>
+    -- | Attribute value.
+    s ->
+    -- | Resulting attribute.
+    Attr
+width = attr "width"
+{-# INLINE width #-}
+
+{- | Combinator for the @wrap@ attribute.
+
+ Example:
+
+ > div ! wrap "bar" $ "Hello."
+
+ Result:
+
+ > <div wrap="bar">Hello.</div>
+-}
+wrap ::
+    ToLogStr s =>
+    -- | Attribute value.
+    s ->
+    -- | Resulting attribute.
+    Attr
+wrap = attr "wrap"
+{-# INLINE wrap #-}
+
+{- | Combinator for the @xmlns@ attribute.
+
+ Example:
+
+ > div ! xmlns "bar" $ "Hello."
+
+ Result:
+
+ > <div xmlns="bar">Hello.</div>
+-}
+xmlns ::
+    ToLogStr s =>
+    -- | Attribute value.
+    s ->
+    -- | Resulting attribute.
+    Attr
+xmlns = attr "xmlns"
+{-# INLINE xmlns #-}
diff --git a/src/Data/Fmt/Code.hs b/src/Data/Fmt/Code.hs
new file mode 100644
--- /dev/null
+++ b/src/Data/Fmt/Code.hs
@@ -0,0 +1,324 @@
+module Data.Fmt.Code (
+    v,
+
+    -- * Character encodings
+    c,
+    c7,
+    c8,
+    s,
+    s7,
+    s8,
+
+    -- * Ascii float encodings
+    e,
+    f,
+    g,
+
+    -- * Decimal encodings
+    d,
+    hhd,
+    hd,
+    ld,
+    lld,
+    u,
+    hhu,
+    hu,
+    lu,
+    llu,
+
+    -- * Hexadecimal encodings
+    x,
+    hhx,
+    hx,
+    hx',
+    lx,
+    lx',
+    llx,
+    llx',
+
+    -- * Binary encodings
+    b,
+    b',
+    hhb,
+    hb,
+    hb',
+    lb,
+    lb',
+    llb,
+    llb',
+) where
+
+import Data.ByteString (ByteString)
+import qualified Data.ByteString.Builder as BL
+import qualified Data.ByteString.Lazy.Char8 as BL
+import Data.Fmt
+import Data.Int
+import Data.Word
+import qualified Numeric as N
+
+{- $setup
+ >>> import Text.Printf
+ >>> :load Data.Fmt
+-}
+
+{- | Encode a loggable value.
+
+ Semantics are similar to 'ByteString.Printf.printf':
+
+ >>> Text.Printf.printf "%v" 42 :: String
+ "42"
+ >>> runLogFmt v 42
+ "42"
+-}
+{-# INLINE v #-}
+v :: ToLogStr a => Fmt1 LogStr s a
+v = fmt1 toLogStr
+
+-- Character encodings
+
+-------------------------
+
+-- | Format a character.
+{-# INLINE c #-}
+c :: IsString m => Fmt1 m s Char
+c = fmt1 (fromString . pure)
+
+-- | ASCII encode a 'Char'.
+{-# INLINE c7 #-}
+c7 :: Fmt1 LogStr s Char
+c7 = fmt1 $ toLogStr . BL.char7
+
+-- | Latin-1 (ISO/IEC 8859-1) encode a 'Char'.
+{-# INLINE c8 #-}
+c8 :: Fmt1 LogStr s Char
+c8 = fmt1 $ toLogStr . BL.char8
+
+-- | Format a showable value.
+{-# INLINE s #-}
+s :: (IsString m, Show a) => Fmt1 m s a
+s = fmt1 (fromString . show)
+
+-- | ASCII encode a 'String'.
+{-# INLINE s7 #-}
+s7 :: Fmt1 LogStr s String
+s7 = fmt1 $ toLogStr . BL.string7
+
+-- | Latin-1 (ISO/IEC 8859-1) encode a 'String'.
+{-# INLINE s8 #-}
+s8 :: Fmt1 LogStr s String
+s8 = fmt1 $ toLogStr . BL.string8
+
+-- Floating point
+
+-------------------------
+
+{- | Format a floating point number to a given number of digits of precision.
+
+ Semantics are similar to 'ByteString.Printf.printf':
+
+ >>> Text.Printf.printf "%.5e" pi :: String
+ "3.14159e0"
+ >>> runLogFmt (e 5) pi
+ "3.14159e0"
+-}
+e :: (IsString m, RealFloat a) => Int -> Fmt1 m s a
+e prec = fmt1 $ fromString . flip (N.showEFloat $ Just prec) []
+
+{- | Format a floating point number to a given number of digits of precision.
+
+ Semantics are similar to 'ByteString.Printf.printf':
+
+ >>> Text.Printf.printf "%.5f" maximal32 :: String
+ "340282330000000000000000000000000000000.00000"
+ >>> runLogFmt (f 5) maximal32
+ "340282330000000000000000000000000000000.00000"
+-}
+f :: (IsString m, RealFloat a) => Int -> Fmt1 m s a
+f prec = fmt1 $ fromString . flip (N.showFFloat $ Just prec) []
+
+{- | Format a floating point number to a given number of digits of precision.
+
+ Semantics are similar to 'ByteString.Printf.printf':
+
+ >>> Text.Printf.printf "%.5g" maximal32 :: String
+ "3.40282e38"
+ >>> runLogFmt (g 5) maximal32
+ "3.40282e38"
+-}
+g :: (IsString m, RealFloat a) => Int -> Fmt1 m s a
+g prec = fmt1 $ fromString . flip (N.showGFloat $ Just prec) []
+
+-- Decimal encodings
+
+-------------------------
+
+-- | Decimal encoding of an 'Int' using the ASCII digits.
+{-# INLINE d #-}
+d :: Fmt1 LogStr s Int
+d = fmt1 $ toLogStr . BL.intDec
+
+{- | Decimal encoding of an 'Int8' using the ASCII digits.
+
+ e.g.
+
+ > toLazyByteString (int8Dec 42)   = "42"
+ > toLazyByteString (int8Dec (-1)) = "-1"
+-}
+{-# INLINE hhd #-}
+hhd :: Fmt1 LogStr s Int8
+hhd = fmt1 $ toLogStr . BL.int8Dec
+
+-- | Decimal encoding of an 'Int16' using the ASCII digits.
+{-# INLINE hd #-}
+hd :: Fmt1 LogStr s Int16
+hd = fmt1 $ toLogStr . BL.int16Dec
+
+-- | Decimal encoding of an 'Int32' using the ASCII digits.
+{-# INLINE ld #-}
+ld :: Fmt1 LogStr s Int32
+ld = fmt1 $ toLogStr . BL.int32Dec
+
+-- | Decimal encoding of an 'Int64' using the ASCII digits.
+{-# INLINE lld #-}
+lld :: Fmt1 LogStr s Int64
+lld = fmt1 $ toLogStr . BL.int64Dec
+
+-- | Decimal encoding of a 'Word' using the ASCII digits.
+{-# INLINE u #-}
+u :: Fmt1 LogStr s Word
+u = fmt1 $ toLogStr . BL.wordDec
+
+-- | Decimal encoding of a 'Word8' using the ASCII digits.
+{-# INLINE hhu #-}
+hhu :: Fmt1 LogStr s Word8
+hhu = fmt1 $ toLogStr . BL.word8Dec
+
+-- | Decimal encoding of a 'Word16' using the ASCII digits.
+{-# INLINE hu #-}
+hu :: Fmt1 LogStr s Word16
+hu = fmt1 $ toLogStr . BL.word16Dec
+
+-- | Decimal encoding of a 'Word32' using the ASCII digits.
+{-# INLINE lu #-}
+lu :: Fmt1 LogStr s Word32
+lu = fmt1 $ toLogStr . BL.word32Dec
+
+-- | Decimal encoding of a 'Word64' using the ASCII digits.
+{-# INLINE llu #-}
+llu :: Fmt1 LogStr s Word64
+llu = fmt1 $ toLogStr . BL.word64Dec
+
+-- Hexadecimal encodings
+--------------------
+
+-- | Shortest hexadecimal encoding of a 'Word' using lower-case characters.
+{-# INLINE x #-}
+x :: Fmt1 LogStr s Word
+x = fmt1 $ toLogStr . BL.wordHex
+
+-- | Shortest hexadecimal encoding of a 'Word8' using lower-case characters.
+{-# INLINE hhx #-}
+hhx :: Fmt1 LogStr s Word8
+hhx = fmt1 $ toLogStr . BL.word8Hex
+
+-- | Encode a 'Word8' using 2 nibbles (hexadecimal digits).
+{-# INLINE hhx' #-}
+hhx' :: Fmt1 LogStr s Word8
+hhx' = fmt1 $ toLogStr . BL.word8HexFixed
+
+-- | Shortest hexadecimal encoding of a 'Word16' using lower-case characters.
+{-# INLINE hx #-}
+hx :: Fmt1 LogStr s Word16
+hx = fmt1 $ toLogStr . BL.word16Hex
+
+-- | Encode a 'Word16' using 4 nibbles.
+{-# INLINE hx' #-}
+hx' :: Fmt1 LogStr s Word16
+hx' = fmt1 $ toLogStr . BL.word16HexFixed
+
+-- | Shortest hexadecimal encoding of a 'Word32' using lower-case characters.
+{-# INLINE lx #-}
+lx :: Fmt1 LogStr s Word32
+lx = fmt1 $ toLogStr . BL.word32Hex
+
+-- | Encode a 'Word32' using 8 nibbles.
+{-# INLINE lx' #-}
+lx' :: Fmt1 LogStr s Word32
+lx' = fmt1 $ toLogStr . BL.word32HexFixed
+
+{- | Shortest hexadecimal encoding of a 'Word64' using lower-case characters.
+
+ Semantics are similar to 'Text.Printf.printf':
+
+ >>> Text.printf "%s: %llx" "Val" (-7) :: String
+ "Val: fffffffffffffff9"
+ >>> printf (s % ": " % llx) "Val" (-7)
+ "Val: fffffffffffffff9"
+-}
+{-# INLINE llx #-}
+llx :: Fmt1 LogStr s Word64
+llx = fmt1 $ toLogStr . BL.word64Hex
+
+-- | Encode a 'Word64' using 16 nibbles.
+{-# INLINE llx' #-}
+llx' :: Fmt1 LogStr s Word64
+llx' = fmt1 $ toLogStr . BL.word64HexFixed
+
+-- Binary encodings
+--------------------
+
+-- | Format a lazy byte string.
+b :: Fmt1 LogStr s BL.ByteString
+b = fmt1 toLogStr
+{-# INLINE b #-}
+
+{- | Format a strict byte string.
+
+ @ 'fmap' (. 'Data.ByteString.pack') 't'' :: 'Fmt1' 'Data.ByteString.Builder.Builder' s 'String' @
+-}
+b' :: Fmt1 LogStr s ByteString
+b' = fmt1 toLogStr
+{-# INLINE b' #-}
+
+-- | Encode a 'Word8' as-is
+{-# INLINE hhb #-}
+hhb :: Fmt1 LogStr s Word8
+hhb = fmt1 $ toLogStr . BL.word8
+
+-- | Encode a 'Word16' using little-endian format.
+{-# INLINE hb #-}
+hb :: Fmt1 LogStr s Word16
+hb = fmt1 $ toLogStr . BL.word16LE
+
+-- | Encode a 'Word16' using big-endian format.
+{-# INLINE hb' #-}
+hb' :: Fmt1 LogStr s Word16
+hb' = fmt1 $ toLogStr . BL.word16BE
+
+-- | Encode a 'Word32' using little-endian format.
+{-# INLINE lb #-}
+lb :: Fmt1 LogStr s Word32
+lb = fmt1 $ toLogStr . BL.word32LE
+
+-- | Encode a 'Word32' using big-endian format.
+{-# INLINE lb' #-}
+lb' :: Fmt1 LogStr s Word32
+lb' = fmt1 $ toLogStr . BL.word32BE
+
+{- | Encode a 'Word64' using little-endian format.
+
+ Semantics are similar to 'Tebt.Printf.printf':
+
+ >>> Text.printf "%s: %llb" "Val" (-7) :: String
+ "Val: fffffffffffffff9"
+ >>> printf (s % ": " % llb) "Val" (-7)
+ "Val: fffffffffffffff9"
+-}
+{-# INLINE llb #-}
+llb :: Fmt1 LogStr s Word64
+llb = fmt1 $ toLogStr . BL.word64LE
+
+-- | Encode a 'Word64' using big-endian format.
+{-# INLINE llb' #-}
+llb' :: Fmt1 LogStr s Word64
+llb' = fmt1 $ toLogStr . BL.word64BE
diff --git a/src/Data/Fmt/Html.hs b/src/Data/Fmt/Html.hs
new file mode 100644
--- /dev/null
+++ b/src/Data/Fmt/Html.hs
@@ -0,0 +1,2120 @@
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE TypeApplications #-}
+
+{- | Html5 formatting.
+
+ The API is similar to < https://hackage.haskell.org/package/blaze-html >.
+-}
+module Data.Fmt.Html (
+    -- * Html
+    Html,
+    Attr,
+    toHtml,
+    comment,
+    Element (..),
+    (!?),
+   
+    numbers,
+    -- * Elements
+    docType,
+    docTypeHtml,
+    a,
+    abbr,
+    address,
+    area,
+    article,
+    aside,
+    audio,
+    b,
+    base,
+    bdo,
+    blockquote,
+    body,
+    br,
+    button,
+    canvas,
+    caption,
+    cite,
+    code,
+    col,
+    colgroup,
+    command,
+    datalist,
+    dd,
+    del,
+    details,
+    dfn,
+    div,
+    dl,
+    dt,
+    em,
+    embed,
+    fieldset,
+    figcaption,
+    figure,
+    footer,
+    form,
+    h1,
+    h2,
+    h3,
+    h4,
+    h5,
+    h6,
+    head,
+    header,
+    hgroup,
+    hr,
+    html,
+    i,
+    iframe,
+    img,
+    input,
+    ins,
+    kbd,
+    keygen,
+    label,
+    legend,
+    li,
+    link,
+    main,
+    map,
+    mark,
+    menu,
+    menuitem,
+    meta,
+    meter,
+    nav,
+    noscript,
+    object,
+    ol,
+    optgroup,
+    option,
+    output,
+    p,
+    param,
+    pre,
+    progress,
+    q,
+    rp,
+    rt,
+    ruby,
+    samp,
+    script,
+    section,
+    select,
+    small,
+    source,
+    span,
+    strong,
+    style,
+    sub,
+    summary,
+    sup,
+    table,
+    tbody,
+    td,
+    textarea,
+    tfoot,
+    th,
+    thead,
+    time,
+    title,
+    tr,
+    track,
+    u,
+    ul,
+    var,
+    video,
+    wbr,
+    contact
+) where
+
+import Data.Fmt
+import Prelude hiding (div, head, map, span)
+
+import Data.Fmt.Attr (href)
+
+
+contact :: Html LogStr
+contact = p "You can reach me at" % ul . spr . li $ do
+    c1 <- a ! href @String "https://example.com" $ "Website"
+    c2 <- a ! href @String "mailto:cmk@example.com" $ "Email"
+    pure $ c1 <> c2
+
+-- "<p>You can reach me at</p><ul><li><a href=\"https://example.com\">Website</a></li><li><a href=\"mailto:cmk@example.com\">Email</a></li></ul>"
+
+-- | Run a monadic expression.
+--
+-- This executes the formatting commands contained in the expression and 
+-- returns the result as a variable.
+run :: Fmt m m n -> Fmt n a a
+run = fmt . runFmt
+
+-- > runLogFmt $ numbers 2
+-- "<html><p>A list of numbers:</p><html><ul><li>1</li><li>2</li></ul></html><p>The end.</p></html>"
+numbers :: Int -> Html LogStr
+numbers n = html $ do
+    l <- ul . cat $ li . toHtml <$> [1 .. n]
+    cat
+        [ p "A list of numbers:"
+        , fmt l
+        , p "The end."
+        ]
+
+-- | Create a < https://en.wikipedia.org/wiki/HTML_element#Syntax tag > for an element.
+element :: String -> Html a -> Html a
+element = enclose <$> (enclose "<" ">" . toHtml) <*> (enclose "</" ">" . toHtml)
+
+element_ :: String -> Html a
+element_ = enclose "<" " />" . toHtml
+
+-- Elements
+
+-------------------------
+
+{- | Combinator for the document type. This should be placed at the top
+ of every HTML page.
+
+ Example:
+
+ > docType
+
+ Result:
+
+ > <!DOCTYPE HTML>
+-}
+docType ::
+    -- | The document type HTML.
+    Html a
+docType = "<!DOCTYPE HTML>\n"
+{-# INLINE docType #-}
+
+{- | Combinator for the @\<html>@ element. This combinator will also
+ insert the correct doctype.
+
+ Example:
+
+ > docTypeHtml $ span $ fmt "foo"
+
+ Result:
+
+ > <!DOCTYPE HTML>
+ > <html><span>foo</span></html>
+-}
+docTypeHtml ::
+    -- | Inner HTML.
+    Html a ->
+    -- | Resulting HTML.
+    Html a
+docTypeHtml inner = docType % html inner
+{-# INLINE docTypeHtml #-}
+
+{- | Combinator for the @\<a>@ element.
+
+ Example:
+
+ > a $ span $ fmt "foo"
+
+ Result:
+
+ > <a><span>foo</span></a>
+-}
+a ::
+    -- | Inner HTML.
+    Html a ->
+    -- | Resulting HTML.
+    Html a
+a = element "a"
+{-# INLINE a #-}
+
+{- | Combinator for the @\<abbr>@ element.
+
+ Example:
+
+ > abbr $ span $ fmt "foo"
+
+ Result:
+
+ > <abbr><span>foo</span></abbr>
+-}
+abbr ::
+    -- | Inner HTML.
+    Html a ->
+    -- | Resulting HTML.
+    Html a
+abbr = element "abbr"
+{-# INLINE abbr #-}
+
+{- | Combinator for the @\<address>@ element.
+
+ Example:
+
+ > address $ span $ fmt "foo"
+
+ Result:
+
+ > <address><span>foo</span></address>
+-}
+address ::
+    -- | Inner HTML.
+    Html a ->
+    -- | Resulting HTML.
+    Html a
+address = element "address"
+{-# INLINE address #-}
+
+{- | Combinator for the @\<area />@ element.
+
+ Example:
+
+ > area
+
+ Result:
+
+ > <area />
+-}
+area ::
+    -- | Resulting HTML.
+    Html a
+area = element_ "area"
+{-# INLINE area #-}
+
+{- | Combinator for the @\<article>@ element.
+
+ Example:
+
+ > article $ span $ fmt "foo"
+
+ Result:
+
+ > <article><span>foo</span></article>
+-}
+article ::
+    -- | Inner HTML.
+    Html a ->
+    -- | Resulting HTML.
+    Html a
+article = element "article"
+{-# INLINE article #-}
+
+{- | Combinator for the @\<aside>@ element.
+
+ Example:
+
+ > aside $ span $ fmt "foo"
+
+ Result:
+
+ > <aside><span>foo</span></aside>
+-}
+aside ::
+    -- | Inner HTML.
+    Html a ->
+    -- | Resulting HTML.
+    Html a
+aside = element "aside"
+{-# INLINE aside #-}
+
+{- | Combinator for the @\<audio>@ element.
+
+ Example:
+
+ > audio $ span $ fmt "foo"
+
+ Result:
+
+ > <audio><span>foo</span></audio>
+-}
+audio ::
+    -- | Inner HTML.
+    Html a ->
+    -- | Resulting HTML.
+    Html a
+audio = element "audio"
+{-# INLINE audio #-}
+
+{- | Combinator for the @\<b>@ element.
+
+ Example:
+
+ > b $ span $ fmt "foo"
+
+ Result:
+
+ > <b><span>foo</span></b>
+-}
+b ::
+    -- | Inner HTML.
+    Html a ->
+    -- | Resulting HTML.
+    Html a
+b = element "b"
+{-# INLINE b #-}
+
+{- | Combinator for the @\<base />@ element.
+
+ Example:
+
+ > base
+
+ Result:
+
+ > <base />
+-}
+base ::
+    -- | Resulting HTML.
+    Html a
+base = element_ "base"
+{-# INLINE base #-}
+
+{- | Combinator for the @\<bdo>@ element.
+
+ Example:
+
+ > bdo $ span $ fmt "foo"
+
+ Result:
+
+ > <bdo><span>foo</span></bdo>
+-}
+bdo ::
+    -- | Inner HTML.
+    Html a ->
+    -- | Resulting HTML.
+    Html a
+bdo = element "bdo"
+{-# INLINE bdo #-}
+
+{- | Combinator for the @\<blockquote>@ element.
+
+ Example:
+
+ > blockquote $ span $ fmt "foo"
+
+ Result:
+
+ > <blockquote><span>foo</span></blockquote>
+-}
+blockquote ::
+    -- | Inner HTML.
+    Html a ->
+    -- | Resulting HTML.
+    Html a
+blockquote = element "blockquote"
+{-# INLINE blockquote #-}
+
+{- | Combinator for the @\<body>@ element.
+
+ Example:
+
+ > body $ span $ fmt "foo"
+
+ Result:
+
+ > <body><span>foo</span></body>
+-}
+body ::
+    -- | Inner HTML.
+    Html a ->
+    -- | Resulting HTML.
+    Html a
+body = element "body"
+{-# INLINE body #-}
+
+{- | Combinator for the @\<br />@ element.
+
+ Example:
+
+ > br
+
+ Result:
+
+ > <br />
+-}
+br ::
+    -- | Resulting HTML.
+    Html a
+br = element_ "br"
+{-# INLINE br #-}
+
+{- | Combinator for the @\<button>@ element.
+
+ Example:
+
+ > button $ span $ fmt "foo"
+
+ Result:
+
+ > <button><span>foo</span></button>
+-}
+button ::
+    -- | Inner HTML.
+    Html a ->
+    -- | Resulting HTML.
+    Html a
+button = element "button"
+{-# INLINE button #-}
+
+{- | Combinator for the @\<canvas>@ element.
+
+ Example:
+
+ > canvas $ span $ fmt "foo"
+
+ Result:
+
+ > <canvas><span>foo</span></canvas>
+-}
+canvas ::
+    -- | Inner HTML.
+    Html a ->
+    -- | Resulting HTML.
+    Html a
+canvas = element "canvas"
+{-# INLINE canvas #-}
+
+{- | Combinator for the @\<caption>@ element.
+
+ Example:
+
+ > caption $ span $ fmt "foo"
+
+ Result:
+
+ > <caption><span>foo</span></caption>
+-}
+caption ::
+    -- | Inner HTML.
+    Html a ->
+    -- | Resulting HTML.
+    Html a
+caption = element "caption"
+{-# INLINE caption #-}
+
+{- | Combinator for the @\<cite>@ element.
+
+ Example:
+
+ > cite $ span $ fmt "foo"
+
+ Result:
+
+ > <cite><span>foo</span></cite>
+-}
+cite ::
+    -- | Inner HTML.
+    Html a ->
+    -- | Resulting HTML.
+    Html a
+cite = element "cite"
+{-# INLINE cite #-}
+
+{- | Combinator for the @\<code>@ element.
+
+ Example:
+
+ > code $ span $ fmt "foo"
+
+ Result:
+
+ > <code><span>foo</span></code>
+-}
+code ::
+    -- | Inner HTML.
+    Html a ->
+    -- | Resulting HTML.
+    Html a
+code = element "code"
+{-# INLINE code #-}
+
+{- | Combinator for the @\<col />@ element.
+
+ Example:
+
+ > col
+
+ Result:
+
+ > <col />
+-}
+col ::
+    -- | Resulting HTML.
+    Html a
+col = element_ "col"
+{-# INLINE col #-}
+
+{- | Combinator for the @\<colgroup>@ element.
+
+ Example:
+
+ > colgroup $ span $ fmt "foo"
+
+ Result:
+
+ > <colgroup><span>foo</span></colgroup>
+-}
+colgroup ::
+    -- | Inner HTML.
+    Html a ->
+    -- | Resulting HTML.
+    Html a
+colgroup = element "colgroup"
+{-# INLINE colgroup #-}
+
+{- | Combinator for the @\<command>@ element.
+
+ Example:
+
+ > command $ span $ fmt "foo"
+
+ Result:
+
+ > <command><span>foo</span></command>
+-}
+command ::
+    -- | Inner HTML.
+    Html a ->
+    -- | Resulting HTML.
+    Html a
+command = element "command"
+{-# INLINE command #-}
+
+{- | Combinator for the @\<datalist>@ element.
+
+ Example:
+
+ > datalist $ span $ fmt "foo"
+
+ Result:
+
+ > <datalist><span>foo</span></datalist>
+-}
+datalist ::
+    -- | Inner HTML.
+    Html a ->
+    -- | Resulting HTML.
+    Html a
+datalist = element "datalist"
+{-# INLINE datalist #-}
+
+{- | Combinator for the @\<dd>@ element.
+
+ Example:
+
+ > dd $ span $ fmt "foo"
+
+ Result:
+
+ > <dd><span>foo</span></dd>
+-}
+dd ::
+    -- | Inner HTML.
+    Html a ->
+    -- | Resulting HTML.
+    Html a
+dd = element "dd"
+{-# INLINE dd #-}
+
+{- | Combinator for the @\<del>@ element.
+
+ Example:
+
+ > del $ span $ fmt "foo"
+
+ Result:
+
+ > <del><span>foo</span></del>
+-}
+del ::
+    -- | Inner HTML.
+    Html a ->
+    -- | Resulting HTML.
+    Html a
+del = element "del"
+{-# INLINE del #-}
+
+{- | Combinator for the @\<details>@ element.
+
+ Example:
+
+ > details $ span $ fmt "foo"
+
+ Result:
+
+ > <details><span>foo</span></details>
+-}
+details ::
+    -- | Inner HTML.
+    Html a ->
+    -- | Resulting HTML.
+    Html a
+details = element "details"
+{-# INLINE details #-}
+
+{- | Combinator for the @\<dfn>@ element.
+
+ Example:
+
+ > dfn $ span $ fmt "foo"
+
+ Result:
+
+ > <dfn><span>foo</span></dfn>
+-}
+dfn ::
+    -- | Inner HTML.
+    Html a ->
+    -- | Resulting HTML.
+    Html a
+dfn = element "dfn"
+{-# INLINE dfn #-}
+
+{- | Combinator for the @\<div>@ element.
+
+ Example:
+
+ > div $ span $ fmt "foo"
+
+ Result:
+
+ > <div><span>foo</span></div>
+-}
+div ::
+    -- | Inner HTML.
+    Html a ->
+    -- | Resulting HTML.
+    Html a
+div = element "div"
+{-# INLINE div #-}
+
+{- | Combinator for the @\<dl>@ element.
+
+ Example:
+
+ > dl $ span $ fmt "foo"
+
+ Result:
+
+ > <dl><span>foo</span></dl>
+-}
+dl ::
+    -- | Inner HTML.
+    Html a ->
+    -- | Resulting HTML.
+    Html a
+dl = element "dl"
+{-# INLINE dl #-}
+
+{- | Combinator for the @\<dt>@ element.
+
+ Example:
+
+ > dt $ span $ fmt "foo"
+
+ Result:
+
+ > <dt><span>foo</span></dt>
+-}
+dt ::
+    -- | Inner HTML.
+    Html a ->
+    -- | Resulting HTML.
+    Html a
+dt = element "dt"
+{-# INLINE dt #-}
+
+{- | Combinator for the @\<em>@ element.
+
+ Example:
+
+ > em $ span $ fmt "foo"
+
+ Result:
+
+ > <em><span>foo</span></em>
+-}
+em ::
+    -- | Inner HTML.
+    Html a ->
+    -- | Resulting HTML.
+    Html a
+em = element "em"
+{-# INLINE em #-}
+
+{- | Combinator for the @\<embed />@ element.
+
+ Example:
+
+ > embed
+
+ Result:
+
+ > <embed />
+-}
+embed ::
+    -- | Resulting HTML.
+    Html a
+embed = element_ "embed"
+{-# INLINE embed #-}
+
+{- | Combinator for the @\<fieldset>@ element.
+
+ Example:
+
+ > fieldset $ span $ fmt "foo"
+
+ Result:
+
+ > <fieldset><span>foo</span></fieldset>
+-}
+fieldset ::
+    -- | Inner HTML.
+    Html a ->
+    -- | Resulting HTML.
+    Html a
+fieldset = element "fieldset"
+{-# INLINE fieldset #-}
+
+{- | Combinator for the @\<figcaption>@ element.
+
+ Example:
+
+ > figcaption $ span $ fmt "foo"
+
+ Result:
+
+ > <figcaption><span>foo</span></figcaption>
+-}
+figcaption ::
+    -- | Inner HTML.
+    Html a ->
+    -- | Resulting HTML.
+    Html a
+figcaption = element "figcaption"
+{-# INLINE figcaption #-}
+
+{- | Combinator for the @\<figure>@ element.
+
+ Example:
+
+ > figure $ span $ fmt "foo"
+
+ Result:
+
+ > <figure><span>foo</span></figure>
+-}
+figure ::
+    -- | Inner HTML.
+    Html a ->
+    -- | Resulting HTML.
+    Html a
+figure = element "figure"
+{-# INLINE figure #-}
+
+{- | Combinator for the @\<footer>@ element.
+
+ Example:
+
+ > footer $ span $ fmt "foo"
+
+ Result:
+
+ > <footer><span>foo</span></footer>
+-}
+footer ::
+    -- | Inner HTML.
+    Html a ->
+    -- | Resulting HTML.
+    Html a
+footer = element "footer"
+{-# INLINE footer #-}
+
+{- | Combinator for the @\<form>@ element.
+
+ Example:
+
+ > form $ span $ fmt "foo"
+
+ Result:
+
+ > <form><span>foo</span></form>
+-}
+form ::
+    -- | Inner HTML.
+    Html a ->
+    -- | Resulting HTML.
+    Html a
+form = element "form"
+{-# INLINE form #-}
+
+{- | Combinator for the @\<h1>@ element.
+
+ Example:
+
+ > h1 $ span $ fmt "foo"
+
+ Result:
+
+ > <h1><span>foo</span></h1>
+-}
+h1 ::
+    -- | Inner HTML.
+    Html a ->
+    -- | Resulting HTML.
+    Html a
+h1 = element "h1"
+{-# INLINE h1 #-}
+
+{- | Combinator for the @\<h2>@ element.
+
+ Example:
+
+ > h2 $ span $ fmt "foo"
+
+ Result:
+
+ > <h2><span>foo</span></h2>
+-}
+h2 ::
+    -- | Inner HTML.
+    Html a ->
+    -- | Resulting HTML.
+    Html a
+h2 = element "h2"
+{-# INLINE h2 #-}
+
+{- | Combinator for the @\<h3>@ element.
+
+ Example:
+
+ > h3 $ span $ fmt "foo"
+
+ Result:
+
+ > <h3><span>foo</span></h3>
+-}
+h3 ::
+    -- | Inner HTML.
+    Html a ->
+    -- | Resulting HTML.
+    Html a
+h3 = element "h3"
+{-# INLINE h3 #-}
+
+{- | Combinator for the @\<h4>@ element.
+
+ Example:
+
+ > h4 $ span $ fmt "foo"
+
+ Result:
+
+ > <h4><span>foo</span></h4>
+-}
+h4 ::
+    -- | Inner HTML.
+    Html a ->
+    -- | Resulting HTML.
+    Html a
+h4 = element "h4"
+{-# INLINE h4 #-}
+
+{- | Combinator for the @\<h5>@ element.
+
+ Example:
+
+ > h5 $ span $ fmt "foo"
+
+ Result:
+
+ > <h5><span>foo</span></h5>
+-}
+h5 ::
+    -- | Inner HTML.
+    Html a ->
+    -- | Resulting HTML.
+    Html a
+h5 = element "h5"
+{-# INLINE h5 #-}
+
+{- | Combinator for the @\<h6>@ element.
+
+ Example:
+
+ > h6 $ span $ fmt "foo"
+
+ Result:
+
+ > <h6><span>foo</span></h6>
+-}
+h6 ::
+    -- | Inner HTML.
+    Html a ->
+    -- | Resulting HTML.
+    Html a
+h6 = element "h6"
+{-# INLINE h6 #-}
+
+{- | Combinator for the @\<head>@ element.
+
+ Example:
+
+ > head $ span $ fmt "foo"
+
+ Result:
+
+ > <head><span>foo</span></head>
+-}
+head ::
+    -- | Inner HTML.
+    Html a ->
+    -- | Resulting HTML.
+    Html a
+head = element "head"
+{-# INLINE head #-}
+
+{- | Combinator for the @\<header>@ element.
+
+ Example:
+
+ > header $ span $ fmt "foo"
+
+ Result:
+
+ > <header><span>foo</span></header>
+-}
+header ::
+    -- | Inner HTML.
+    Html a ->
+    -- | Resulting HTML.
+    Html a
+header = element "header"
+{-# INLINE header #-}
+
+{- | Combinator for the @\<hgroup>@ element.
+
+ Example:
+
+ > hgroup $ span $ fmt "foo"
+
+ Result:
+
+ > <hgroup><span>foo</span></hgroup>
+-}
+hgroup ::
+    -- | Inner HTML.
+    Html a ->
+    -- | Resulting HTML.
+    Html a
+hgroup = element "hgroup"
+{-# INLINE hgroup #-}
+
+{- | Combinator for the @\<hr />@ element.
+
+ Example:
+
+ > hr
+
+ Result:
+
+ > <hr />
+-}
+hr ::
+    -- | Resulting HTML.
+    Html a
+hr = element_ "hr"
+{-# INLINE hr #-}
+
+{- | Combinator for the @\<html>@ element.
+
+ Example:
+
+ > html $ span $ fmt "foo"
+
+ Result:
+
+ > <html><span>foo</span></html>
+-}
+html ::
+    -- | Inner HTML.
+    Html a ->
+    -- | Resulting HTML.
+    Html a
+html = element "html"
+{-# INLINE html #-}
+
+{- | Combinator for the @\<i>@ element.
+
+ Example:
+
+ > i $ span $ fmt "foo"
+
+ Result:
+
+ > <i><span>foo</span></i>
+-}
+i ::
+    -- | Inner HTML.
+    Html a ->
+    -- | Resulting HTML.
+    Html a
+i = element "i"
+{-# INLINE i #-}
+
+{- | Combinator for the @\<iframe>@ element.
+
+ Example:
+
+ > iframe $ span $ fmt "foo"
+
+ Result:
+
+ > <iframe><span>foo</span></iframe>
+-}
+iframe ::
+    -- | Inner HTML.
+    Html a ->
+    -- | Resulting HTML.
+    Html a
+iframe = element "iframe"
+{-# INLINE iframe #-}
+
+{- | Combinator for the @\<img />@ element.
+
+ Example:
+
+ > img
+
+ Result:
+
+ > <img />
+-}
+img ::
+    -- | Resulting HTML.
+    Html a
+img = element_ "img"
+{-# INLINE img #-}
+
+{- | Combinator for the @\<input />@ element.
+
+ Example:
+
+ > input
+
+ Result:
+
+ > <input />
+-}
+input ::
+    -- | Resulting HTML.
+    Html a
+input = element_ "input"
+{-# INLINE input #-}
+
+{- | Combinator for the @\<ins>@ element.
+
+ Example:
+
+ > ins $ span $ fmt "foo"
+
+ Result:
+
+ > <ins><span>foo</span></ins>
+-}
+ins ::
+    -- | Inner HTML.
+    Html a ->
+    -- | Resulting HTML.
+    Html a
+ins = element "ins"
+{-# INLINE ins #-}
+
+{- | Combinator for the @\<kbd>@ element.
+
+ Example:
+
+ > kbd $ span $ fmt "foo"
+
+ Result:
+
+ > <kbd><span>foo</span></kbd>
+-}
+kbd ::
+    -- | Inner HTML.
+    Html a ->
+    -- | Resulting HTML.
+    Html a
+kbd = element "kbd"
+{-# INLINE kbd #-}
+
+{- | Combinator for the @\<keygen />@ element.
+
+ Example:
+
+ > keygen
+
+ Result:
+
+ > <keygen />
+-}
+keygen ::
+    -- | Resulting HTML.
+    Html a
+keygen = element_ "keygen"
+{-# INLINE keygen #-}
+
+{- | Combinator for the @\<label>@ element.
+
+ Example:
+
+ > label $ span $ fmt "foo"
+
+ Result:
+
+ > <label><span>foo</span></label>
+-}
+label ::
+    -- | Inner HTML.
+    Html a ->
+    -- | Resulting HTML.
+    Html a
+label = element "label"
+{-# INLINE label #-}
+
+{- | Combinator for the @\<legend>@ element.
+
+ Example:
+
+ > legend $ span $ fmt "foo"
+
+ Result:
+
+ > <legend><span>foo</span></legend>
+-}
+legend ::
+    -- | Inner HTML.
+    Html a ->
+    -- | Resulting HTML.
+    Html a
+legend = element "legend"
+{-# INLINE legend #-}
+
+{- | Combinator for the @\<li>@ element.
+
+ Example:
+
+ > li $ span $ fmt "foo"
+
+ Result:
+
+ > <li><span>foo</span></li>
+-}
+li ::
+    -- | Inner HTML.
+    Html a ->
+    -- | Resulting HTML.
+    Html a
+li = element "li"
+{-# INLINE li #-}
+
+{- | Combinator for the @\<link />@ element.
+
+ Example:
+
+ > link
+
+ Result:
+
+ > <link />
+-}
+link ::
+    -- | Resulting HTML.
+    Html a
+link = element_ "link"
+{-# INLINE link #-}
+
+{- | Combinator for the @\<main>@ element.
+
+ Example:
+
+ > main $ span $ fmt "foo"
+
+ Result:
+
+ > <main><span>foo</span></main>
+-}
+main ::
+    -- | Inner HTML.
+    Html a ->
+    -- | Resulting HTML.
+    Html a
+main = element "main"
+{-# INLINE main #-}
+
+{- | Combinator for the @\<map>@ element.
+
+ Example:
+
+ > map $ span $ fmt "foo"
+
+ Result:
+
+ > <map><span>foo</span></map>
+-}
+map ::
+    -- | Inner HTML.
+    Html a ->
+    -- | Resulting HTML.
+    Html a
+map = element "map"
+{-# INLINE map #-}
+
+{- | Combinator for the @\<mark>@ element.
+
+ Example:
+
+ > mark $ span $ fmt "foo"
+
+ Result:
+
+ > <mark><span>foo</span></mark>
+-}
+mark ::
+    -- | Inner HTML.
+    Html a ->
+    -- | Resulting HTML.
+    Html a
+mark = element "mark"
+{-# INLINE mark #-}
+
+{- | Combinator for the @\<menu>@ element.
+
+ Example:
+
+ > menu $ span $ fmt "foo"
+
+ Result:
+
+ > <menu><span>foo</span></menu>
+-}
+menu ::
+    -- | Inner HTML.
+    Html a ->
+    -- | Resulting HTML.
+    Html a
+menu = element "menu"
+{-# INLINE menu #-}
+
+{- | Combinator for the @\<menuitem />@ element.
+
+ Example:
+
+ > menuitem
+
+ Result:
+
+ > <menuitem />
+-}
+menuitem ::
+    -- | Resulting HTML.
+    Html a
+menuitem = element_ "menuitem"
+{-# INLINE menuitem #-}
+
+{- | Combinator for the @\<meta />@ element.
+
+ Example:
+
+ > meta
+
+ Result:
+
+ > <meta />
+-}
+meta ::
+    -- | Resulting HTML.
+    Html a
+meta = element_ "meta"
+{-# INLINE meta #-}
+
+{- | Combinator for the @\<meter>@ element.
+
+ Example:
+
+ > meter $ span $ fmt "foo"
+
+ Result:
+
+ > <meter><span>foo</span></meter>
+-}
+meter ::
+    -- | Inner HTML.
+    Html a ->
+    -- | Resulting HTML.
+    Html a
+meter = element "meter"
+{-# INLINE meter #-}
+
+{- | Combinator for the @\<nav>@ element.
+
+ Example:
+
+ > nav $ span $ fmt "foo"
+
+ Result:
+
+ > <nav><span>foo</span></nav>
+-}
+nav ::
+    -- | Inner HTML.
+    Html a ->
+    -- | Resulting HTML.
+    Html a
+nav = element "nav"
+{-# INLINE nav #-}
+
+{- | Combinator for the @\<noscript>@ element.
+
+ Example:
+
+ > noscript $ span $ fmt "foo"
+
+ Result:
+
+ > <noscript><span>foo</span></noscript>
+-}
+noscript ::
+    -- | Inner HTML.
+    Html a ->
+    -- | Resulting HTML.
+    Html a
+noscript = element "noscript"
+{-# INLINE noscript #-}
+
+{- | Combinator for the @\<object>@ element.
+
+ Example:
+
+ > object $ span $ fmt "foo"
+
+ Result:
+
+ > <object><span>foo</span></object>
+-}
+object ::
+    -- | Inner HTML.
+    Html a ->
+    -- | Resulting HTML.
+    Html a
+object = element "object"
+{-# INLINE object #-}
+
+{- | Combinator for the @\<ol>@ element.
+
+ Example:
+
+ > ol $ span $ fmt "foo"
+
+ Result:
+
+ > <ol><span>foo</span></ol>
+-}
+ol ::
+    -- | Inner HTML.
+    Html a ->
+    -- | Resulting HTML.
+    Html a
+ol = element "ol"
+{-# INLINE ol #-}
+
+{- | Combinator for the @\<optgroup>@ element.
+
+ Example:
+
+ > optgroup $ span $ fmt "foo"
+
+ Result:
+
+ > <optgroup><span>foo</span></optgroup>
+-}
+optgroup ::
+    -- | Inner HTML.
+    Html a ->
+    -- | Resulting HTML.
+    Html a
+optgroup = element "optgroup"
+{-# INLINE optgroup #-}
+
+{- | Combinator for the @\<option>@ element.
+
+ Example:
+
+ > option $ span $ fmt "foo"
+
+ Result:
+
+ > <option><span>foo</span></option>
+-}
+option ::
+    -- | Inner HTML.
+    Html a ->
+    -- | Resulting HTML.
+    Html a
+option = element "option"
+{-# INLINE option #-}
+
+{- | Combinator for the @\<output>@ element.
+
+ Example:
+
+ > output $ span $ fmt "foo"
+
+ Result:
+
+ > <output><span>foo</span></output>
+-}
+output ::
+    -- | Inner HTML.
+    Html a ->
+    -- | Resulting HTML.
+    Html a
+output = element "output"
+{-# INLINE output #-}
+
+{- | Combinator for the @\<p>@ element.
+
+ Example:
+
+ > p $ span $ fmt "foo"
+
+ Result:
+
+ > <p><span>foo</span></p>
+-}
+p ::
+    -- | Inner HTML.
+    Html a ->
+    -- | Resulting HTML.
+    Html a
+p = element "p"
+{-# INLINE p #-}
+
+{- | Combinator for the @\<param />@ element.
+
+ Example:
+
+ > param
+
+ Result:
+
+ > <param />
+-}
+param ::
+    -- | Resulting HTML.
+    Html a
+param = element_ "param"
+{-# INLINE param #-}
+
+{- | Combinator for the @\<pre>@ element.
+
+ Example:
+
+ > pre $ span $ fmt "foo"
+
+ Result:
+
+ > <pre><span>foo</span></pre>
+-}
+pre ::
+    -- | Inner HTML.
+    Html a ->
+    -- | Resulting HTML.
+    Html a
+pre = element "pre"
+{-# INLINE pre #-}
+
+{- | Combinator for the @\<progress>@ element.
+
+ Example:
+
+ > progress $ span $ fmt "foo"
+
+ Result:
+
+ > <progress><span>foo</span></progress>
+-}
+progress ::
+    -- | Inner HTML.
+    Html a ->
+    -- | Resulting HTML.
+    Html a
+progress = element "progress"
+{-# INLINE progress #-}
+
+{- | Combinator for the @\<q>@ element.
+
+ Example:
+
+ > q $ span $ fmt "foo"
+
+ Result:
+
+ > <q><span>foo</span></q>
+-}
+q ::
+    -- | Inner HTML.
+    Html a ->
+    -- | Resulting HTML.
+    Html a
+q = element "q"
+{-# INLINE q #-}
+
+{- | Combinator for the @\<rp>@ element.
+
+ Example:
+
+ > rp $ span $ fmt "foo"
+
+ Result:
+
+ > <rp><span>foo</span></rp>
+-}
+rp ::
+    -- | Inner HTML.
+    Html a ->
+    -- | Resulting HTML.
+    Html a
+rp = element "rp"
+{-# INLINE rp #-}
+
+{- | Combinator for the @\<rt>@ element.
+
+ Example:
+
+ > rt $ span $ fmt "foo"
+
+ Result:
+
+ > <rt><span>foo</span></rt>
+-}
+rt ::
+    -- | Inner HTML.
+    Html a ->
+    -- | Resulting HTML.
+    Html a
+rt = element "rt"
+{-# INLINE rt #-}
+
+{- | Combinator for the @\<ruby>@ element.
+
+ Example:
+
+ > ruby $ span $ fmt "foo"
+
+ Result:
+
+ > <ruby><span>foo</span></ruby>
+-}
+ruby ::
+    -- | Inner HTML.
+    Html a ->
+    -- | Resulting HTML.
+    Html a
+ruby = element "ruby"
+{-# INLINE ruby #-}
+
+{- | Combinator for the @\<samp>@ element.
+
+ Example:
+
+ > samp $ span $ fmt "foo"
+
+ Result:
+
+ > <samp><span>foo</span></samp>
+-}
+samp ::
+    -- | Inner HTML.
+    Html a ->
+    -- | Resulting HTML.
+    Html a
+samp = element "samp"
+{-# INLINE samp #-}
+
+{- | Combinator for the @\<script>@ element.
+
+ Example:
+
+ > script $ span $ fmt "foo"
+
+ Result:
+
+ > <script><span>foo</span></script>
+-}
+script ::
+    -- | Inner HTML.
+    Html a ->
+    -- | Resulting HTML.
+    Html a
+script = element "script"
+{-# INLINE script #-}
+
+{- | Combinator for the @\<section>@ element.
+
+ Example:
+
+ > section $ span $ fmt "foo"
+
+ Result:
+
+ > <section><span>foo</span></section>
+-}
+section ::
+    -- | Inner HTML.
+    Html a ->
+    -- | Resulting HTML.
+    Html a
+section = element "section"
+{-# INLINE section #-}
+
+{- | Combinator for the @\<select>@ element.
+
+ Example:
+
+ > select $ span $ fmt "foo"
+
+ Result:
+
+ > <select><span>foo</span></select>
+-}
+select ::
+    -- | Inner HTML.
+    Html a ->
+    -- | Resulting HTML.
+    Html a
+select = element "select"
+{-# INLINE select #-}
+
+{- | Combinator for the @\<small>@ element.
+
+ Example:
+
+ > small $ span $ fmt "foo"
+
+ Result:
+
+ > <small><span>foo</span></small>
+-}
+small ::
+    -- | Inner HTML.
+    Html a ->
+    -- | Resulting HTML.
+    Html a
+small = element "small"
+{-# INLINE small #-}
+
+{- | Combinator for the @\<source />@ element.
+
+ Example:
+
+ > source
+
+ Result:
+
+ > <source />
+-}
+source ::
+    -- | Resulting HTML.
+    Html a
+source = element_ "source"
+{-# INLINE source #-}
+
+{- | Combinator for the @\<span>@ element.
+
+ Example:
+
+ > span $ span $ fmt "foo"
+
+ Result:
+
+ > <span><span>foo</span></span>
+-}
+span ::
+    -- | Inner HTML.
+    Html a ->
+    -- | Resulting HTML.
+    Html a
+span = element "span"
+{-# INLINE span #-}
+
+{- | Combinator for the @\<strong>@ element.
+
+ Example:
+
+ > strong $ span $ fmt "foo"
+
+ Result:
+
+ > <strong><span>foo</span></strong>
+-}
+strong ::
+    -- | Inner HTML.
+    Html a ->
+    -- | Resulting HTML.
+    Html a
+strong = element "strong"
+{-# INLINE strong #-}
+
+{- | Combinator for the @\<style>@ element.
+
+ Example:
+
+ > style $ span $ fmt "foo"
+
+ Result:
+
+ > <style><span>foo</span></style>
+-}
+style ::
+    -- | Inner HTML.
+    Html a ->
+    -- | Resulting HTML.
+    Html a
+style = element "style"
+{-# INLINE style #-}
+
+{- | Combinator for the @\<sub>@ element.
+
+ Example:
+
+ > sub $ span $ fmt "foo"
+
+ Result:
+
+ > <sub><span>foo</span></sub>
+-}
+sub ::
+    -- | Inner HTML.
+    Html a ->
+    -- | Resulting HTML.
+    Html a
+sub = element "sub"
+{-# INLINE sub #-}
+
+{- | Combinator for the @\<summary>@ element.
+
+ Example:
+
+ > summary $ span $ fmt "foo"
+
+ Result:
+
+ > <summary><span>foo</span></summary>
+-}
+summary ::
+    -- | Inner HTML.
+    Html a ->
+    -- | Resulting HTML.
+    Html a
+summary = element "summary"
+{-# INLINE summary #-}
+
+{- | Combinator for the @\<sup>@ element.
+
+ Example:
+
+ > sup $ span $ fmt "foo"
+
+ Result:
+
+ > <sup><span>foo</span></sup>
+-}
+sup ::
+    -- | Inner HTML.
+    Html a ->
+    -- | Resulting HTML.
+    Html a
+sup = element "sup"
+{-# INLINE sup #-}
+
+{- | Combinator for the @\<table>@ element.
+
+ Example:
+
+ > table $ span $ fmt "foo"
+
+ Result:
+
+ > <table><span>foo</span></table>
+-}
+table ::
+    -- | Inner HTML.
+    Html a ->
+    -- | Resulting HTML.
+    Html a
+table = element "table"
+{-# INLINE table #-}
+
+{- | Combinator for the @\<tbody>@ element.
+
+ Example:
+
+ > tbody $ span $ fmt "foo"
+
+ Result:
+
+ > <tbody><span>foo</span></tbody>
+-}
+tbody ::
+    -- | Inner HTML.
+    Html a ->
+    -- | Resulting HTML.
+    Html a
+tbody = element "tbody"
+{-# INLINE tbody #-}
+
+{- | Combinator for the @\<td>@ element.
+
+ Example:
+
+ > td $ span $ fmt "foo"
+
+ Result:
+
+ > <td><span>foo</span></td>
+-}
+td ::
+    -- | Inner HTML.
+    Html a ->
+    -- | Resulting HTML.
+    Html a
+td = element "td"
+{-# INLINE td #-}
+
+{- | Combinator for the @\<textarea>@ element.
+
+ Example:
+
+ > textarea $ span $ fmt "foo"
+
+ Result:
+
+ > <textarea><span>foo</span></textarea>
+-}
+textarea ::
+    -- | Inner HTML.
+    Html a ->
+    -- | Resulting HTML.
+    Html a
+textarea = element "textarea"
+{-# INLINE textarea #-}
+
+{- | Combinator for the @\<tfoot>@ element.
+
+ Example:
+
+ > tfoot $ span $ fmt "foo"
+
+ Result:
+
+ > <tfoot><span>foo</span></tfoot>
+-}
+tfoot ::
+    -- | Inner HTML.
+    Html a ->
+    -- | Resulting HTML.
+    Html a
+tfoot = element "tfoot"
+{-# INLINE tfoot #-}
+
+{- | Combinator for the @\<th>@ element.
+
+ Example:
+
+ > th $ span $ fmt "foo"
+
+ Result:
+
+ > <th><span>foo</span></th>
+-}
+th ::
+    -- | Inner HTML.
+    Html a ->
+    -- | Resulting HTML.
+    Html a
+th = element "th"
+{-# INLINE th #-}
+
+{- | Combinator for the @\<thead>@ element.
+
+ Example:
+
+ > thead $ span $ fmt "foo"
+
+ Result:
+
+ > <thead><span>foo</span></thead>
+-}
+thead ::
+    -- | Inner HTML.
+    Html a ->
+    -- | Resulting HTML.
+    Html a
+thead = element "thead"
+{-# INLINE thead #-}
+
+{- | Combinator for the @\<time>@ element.
+
+ Example:
+
+ > time $ span $ fmt "foo"
+
+ Result:
+
+ > <time><span>foo</span></time>
+-}
+time ::
+    -- | Inner HTML.
+    Html a ->
+    -- | Resulting HTML.
+    Html a
+time = element "time"
+{-# INLINE time #-}
+
+{- | Combinator for the @\<title>@ element.
+
+ Example:
+
+ > title $ span $ fmt "foo"
+
+ Result:
+
+ > <title><span>foo</span></title>
+-}
+title ::
+    -- | Inner HTML.
+    Html a ->
+    -- | Resulting HTML.
+    Html a
+title = element "title"
+{-# INLINE title #-}
+
+{- | Combinator for the @\<tr>@ element.
+
+ Example:
+
+ > tr $ span $ fmt "foo"
+
+ Result:
+
+ > <tr><span>foo</span></tr>
+-}
+tr ::
+    -- | Inner HTML.
+    Html a ->
+    -- | Resulting HTML.
+    Html a
+tr = element "tr"
+{-# INLINE tr #-}
+
+{- | Combinator for the @\<track />@ element.
+
+ Example:
+
+ > track
+
+ Result:
+
+ > <track />
+-}
+track ::
+    -- | Resulting HTML.
+    Html a
+track = element_ "track"
+{-# INLINE track #-}
+
+{- | Combinator for the @\<u>@ element.
+
+ Example:
+
+ > u $ span $ fmt "foo"
+
+ Result:
+
+ > <u><span>foo</span></u>
+-}
+u ::
+    -- | Inner HTML.
+    Html a ->
+    -- | Resulting HTML.
+    Html a
+u = element "u"
+{-# INLINE u #-}
+
+{- | Combinator for the @\<ul>@ element.
+
+ Example:
+
+ > ul $ span $ fmt "foo"
+
+ Result:
+
+ > <ul><span>foo</span></ul>
+-}
+ul ::
+    -- | Inner HTML.
+    Html a ->
+    -- | Resulting HTML.
+    Html a
+ul = element "ul"
+{-# INLINE ul #-}
+
+{- | Combinator for the @\<var>@ element.
+
+ Example:
+
+ > var $ span $ fmt "foo"
+
+ Result:
+
+ > <var><span>foo</span></var>
+-}
+var ::
+    -- | Inner HTML.
+    Html a ->
+    -- | Resulting HTML.
+    Html a
+var = element "var"
+{-# INLINE var #-}
+
+{- | Combinator for the @\<video>@ element.
+
+ Example:
+
+ > video $ span $ fmt "foo"
+
+ Result:
+
+ > <video><span>foo</span></video>
+-}
+video ::
+    -- | Inner HTML.
+    Html a ->
+    -- | Resulting HTML.
+    Html a
+video = element "video"
+{-# INLINE video #-}
+
+{- | Combinator for the @\<wbr />@ element.
+
+ Example:
+
+ > wbr
+
+ Result:
+
+ > <wbr />
+-}
+wbr :: Html a
+wbr = element_ "wbr"
+{-# INLINE wbr #-}
diff --git a/src/Data/Fmt/Time.hs b/src/Data/Fmt/Time.hs
new file mode 100644
--- /dev/null
+++ b/src/Data/Fmt/Time.hs
@@ -0,0 +1,106 @@
+{-# LANGUAGE OverloadedStrings #-}
+
+module Data.Fmt.Time (
+    time,
+
+    -- * Time
+    t,
+    hm,
+    hms,
+    day,
+    day',
+    month,
+    month',
+    year,
+    unix,
+    zone,
+
+    -- * Duration
+    secs,
+    mins,
+    hours,
+    days,
+    years,
+) where
+
+import Data.Fmt
+import Data.String
+import Data.Time (FormatTime, defaultTimeLocale, formatTime)
+import Data.Time.Format.ISO8601 (ISO8601, iso8601Show)
+import Prelude hiding (min)
+import qualified Data.Fmt.Code as Fmt
+
+{- | A custom time formatter.
+
+ For example, the E.U. formatting convention is  /time "%Y-%m-%d %T %z"/
+-}
+time :: (IsString m, FormatTime a) => String -> Fmt1 m s a
+time s = fmt1 $ fromString . formatTime defaultTimeLocale s
+
+-- Time
+
+-------------------------
+
+-- | < https://en.wikipedia.org/wiki/ISO_8601 ISO-8601> time: /2021-06-05T01:46:46.173677-07:00/
+t :: (IsString m, ISO8601 a) => Fmt1 m s a
+t = fmt1 $ fromString . iso8601Show
+
+-- | Hour-minute of day: @%H:%M@.
+hm :: (IsString m, FormatTime a) => Fmt1 m s a
+hm = time "%R"
+
+-- | Hour-minute-second of day: @%H:%M:%S@.
+hms :: (IsString m, FormatTime a) => Fmt1 m s a
+hms = time "%T"
+
+-- | Day of month: @01@ - @31@.
+day :: (IsString m, FormatTime a) => Fmt1 m s a
+day = time "%d"
+
+-- | Day of week: @Sun@ - @Sat@.
+day' :: (IsString m, FormatTime a) => Fmt1 m s a
+day' = time "%a"
+
+-- | Month of year: @01@ - @12@.
+month :: (IsString m, FormatTime a) => Fmt1 m s a
+month = time "%m"
+
+-- | Month of year: @Jan@ - @Dec@.
+month' :: (IsString m, FormatTime a) => Fmt1 m s a
+month' = time "%b"
+
+-- | Year.
+year :: (IsString m, FormatTime a) => Fmt1 m s a
+year = time "%Y"
+
+-- | Seconds from Unix epoch.
+unix :: (IsString m, FormatTime a) => Fmt1 m s a
+unix = time "%s"
+
+-- | Timezone offset: @-HHMM@.
+zone :: (IsString m, FormatTime a) => Fmt1 m s a
+zone = time "%z"
+
+-- Duration
+
+-------------------------
+
+-- | Display to a given precision the absolute value time span in seconds.
+secs :: IsString m => Int -> Fmt1 m s Double
+secs n = fmt1 (runFmt (Fmt.f n) . abs . count) where count n' = n'
+
+-- | Display to a given precision the absolute value time span in minutes.
+mins :: IsString m => Int -> Fmt1 m s Double
+mins n = fmt1 (runFmt (Fmt.f n) . abs . count) where count n' = n' / 60
+
+-- | Display to a given precision the absolute value time span in hours.
+hours :: IsString m => Int -> Fmt1 m s Double
+hours n = fmt1 (runFmt (Fmt.f n) . abs . count) where count n' = n' / 60 / 60
+
+-- | Display to a given precision the absolute value time span in days.
+days :: (IsString m) => Int -> Fmt1 m s Double
+days n = fmt1 (runFmt (Fmt.f n) . abs . count) where count n' = n' / 24 / 60 / 60
+
+-- | Display to a given precision the absolute value time span in years.
+years :: IsString m => Int -> Fmt1 m s Double
+years n = fmt1 (runFmt (Fmt.f n) . abs . count) where count n' = n' / 365 / 24 / 60 / 60
