diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,9 @@
+## MMark Ext 0.2.0.0
+
+* The `skylighting` extension no longer accepts any arguments. Skylighting
+  built-in facilities for rendering to HTML are not acceptable as they
+  produce invalid HTML (with duplicate ids), so they were re-implemented.
+
 ## MMark Ext 0.1.1.0
 
 * Added the `Text.MMark.Extension.MathJax` module.
diff --git a/Text/MMark/Extension/Common.hs b/Text/MMark/Extension/Common.hs
--- a/Text/MMark/Extension/Common.hs
+++ b/Text/MMark/Extension/Common.hs
@@ -20,7 +20,6 @@
 -- >
 -- > module Main (main) where
 -- >
--- > import Skylighting (defaultFormatOpts)
 -- > import qualified Data.Text.IO                as T
 -- > import qualified Data.Text.Lazy.IO           as TL
 -- > import qualified Lucid                       as L
@@ -41,7 +40,7 @@
 -- >           . MMark.useExtensions
 -- >               [ Ext.toc "toc" toc
 -- >               , Ext.punctuationPrettifier
--- >               , Ext.skylighting defaultFormatOpts ]
+-- >               , Ext.skylighting ]
 -- >           $ r
 
 module Text.MMark.Extension.Common
diff --git a/Text/MMark/Extension/Skylighting.hs b/Text/MMark/Extension/Skylighting.hs
--- a/Text/MMark/Extension/Skylighting.hs
+++ b/Text/MMark/Extension/Skylighting.hs
@@ -9,14 +9,18 @@
 --
 -- Use the Skylighting library to highlight code snippets.
 
+{-# LANGUAGE LambdaCase        #-}
 {-# LANGUAGE OverloadedStrings #-}
 
 module Text.MMark.Extension.Skylighting
   ( skylighting )
 where
 
-import Lucid (toHtmlRaw)
-import Text.Blaze.Html.Renderer.Text
+import Control.Monad
+import Data.Semigroup ((<>))
+import Data.Text (Text)
+import Lucid
+import Skylighting (Token, TokenType (..))
 import Text.MMark.Extension (Extension, Block (..))
 import qualified Data.Text            as T
 import qualified Skylighting          as S
@@ -25,13 +29,42 @@
 -- | Use the @skylighting@ package to render code blocks with info strings
 -- that result in a successful lookup from 'S.defaultSyntaxMap'.
 --
--- The resulting markup is wrapped with spans as described in the docs for
--- 'S.formatHtmlInline'.
+-- The resulting code block will be wrapped in a @div@ with class
+-- @\"source-code\"@. The following @span@ classes can be used for styling:
+--
+--     * 'AlertTok'          = @\"al\"@
+--     * 'AnnotationTok'     = @\"an\"@
+--     * 'AttributeTok'      = @\"at\"@
+--     * 'BaseNTok'          = @\"bn\"@
+--     * 'BuiltInTok'        = @\"bu\"@
+--     * 'CharTok'           = @\"ch\"@
+--     * 'CommentTok'        = @\"co\"@
+--     * 'CommentVarTok'     = @\"cv\"@
+--     * 'ConstantTok'       = @\"cn\"@
+--     * 'ControlFlowTok'    = @\"cf\"@
+--     * 'DataTypeTok'       = @\"dt\"@
+--     * 'DecValTok'         = @\"dv\"@
+--     * 'DocumentationTok'  = @\"do\"@
+--     * 'ErrorTok'          = @\"er\"@
+--     * 'ExtensionTok'      = @\"ex\"@
+--     * 'FloatTok'          = @\"fl\"@
+--     * 'FunctionTok'       = @\"fu\"@
+--     * 'ImportTok'         = @\"im\"@
+--     * 'InformationTok'    = @\"in\"@
+--     * 'KeywordTok'        = @\"kw\"@
+--     * 'OperatorTok'       = @\"op\"@
+--     * 'OtherTok'          = @\"ot\"@
+--     * 'PreprocessorTok'   = @\"pp\"@
+--     * 'RegionMarkerTok'   = @\"re\"@
+--     * 'SpecialCharTok'    = @\"sc\"@
+--     * 'SpecialStringTok'  = @\"ss\"@
+--     * 'StringTok'         = @\"st\"@
+--     * 'VariableTok'       = @\"va\"@
+--     * 'VerbatimStringTok' = @\"vs\"@
+--     * 'WarningTok'        = @\"wa\"@
 
-skylighting
-  :: S.FormatOptions   -- ^ Skylighting formatting options
-  -> Extension
-skylighting fmtOpts = Ext.blockRender $ \old block ->
+skylighting :: Extension
+skylighting = Ext.blockRender $ \old block ->
   case block of
     cb@(CodeBlock (Just infoString') txt) ->
       let tokenizerConfig = S.TokenizerConfig
@@ -43,7 +76,59 @@
            Just syntax ->
              case S.tokenize tokenizerConfig syntax txt of
                Left _ -> old cb
-               Right sourceLines -> do
-                 toHtmlRaw . renderHtml $ S.formatHtmlBlock fmtOpts sourceLines
-                 "\n"
+               Right ls -> do
+                 div_ [class_ "source-code"]
+                   . pre_
+                   . code_ [class_ ("language-" <> infoString)]
+                   . forM_ ls $ \l -> do
+                       mapM_ tokenToHtml l
+                       newline
+                 newline
     other -> old other
+  where
+    newline :: Html ()
+    newline = "\n"
+
+-- | Render a single 'Token'.
+
+tokenToHtml :: Token -> Html ()
+tokenToHtml (tokenType, txt) =
+  span_ [class_ rawClass | not (T.null rawClass)] (toHtml txt)
+  where
+    rawClass = tokenClass tokenType
+
+-- | Return class corresponding to given 'TokenType'.
+
+tokenClass :: TokenType -> Text
+tokenClass = \case
+  KeywordTok        -> "kw"
+  DataTypeTok       -> "dt"
+  DecValTok         -> "dv"
+  BaseNTok          -> "bn"
+  FloatTok          -> "fl"
+  CharTok           -> "ch"
+  StringTok         -> "st"
+  CommentTok        -> "co"
+  OtherTok          -> "ot"
+  AlertTok          -> "al"
+  FunctionTok       -> "fu"
+  RegionMarkerTok   -> "re"
+  ErrorTok          -> "er"
+  ConstantTok       -> "cn"
+  SpecialCharTok    -> "sc"
+  VerbatimStringTok -> "vs"
+  SpecialStringTok  -> "ss"
+  ImportTok         -> "im"
+  DocumentationTok  -> "do"
+  AnnotationTok     -> "an"
+  CommentVarTok     -> "cv"
+  VariableTok       -> "va"
+  ControlFlowTok    -> "cf"
+  OperatorTok       -> "op"
+  BuiltInTok        -> "bu"
+  ExtensionTok      -> "ex"
+  PreprocessorTok   -> "pp"
+  AttributeTok      -> "at"
+  InformationTok    -> "in"
+  WarningTok        -> "wa"
+  NormalTok         -> ""
diff --git a/mmark-ext.cabal b/mmark-ext.cabal
--- a/mmark-ext.cabal
+++ b/mmark-ext.cabal
@@ -1,5 +1,5 @@
 name:                 mmark-ext
-version:              0.1.1.0
+version:              0.2.0.0
 cabal-version:        >= 1.18
 tested-with:          GHC==8.0.2, GHC==8.2.2
 license:              BSD3
@@ -32,7 +32,6 @@
 
 library
   build-depends:      base             >= 4.9   && < 5.0
-                    , blaze-html       >= 0.9   && < 0.10
                     , foldl            >= 1.2   && < 1.4
                     , lucid            >= 2.6   && < 3.0
                     , microlens        >= 0.4   && < 0.5
@@ -81,6 +80,7 @@
                     , Text.MMark.Extension.MathJaxSpec
                     , Text.MMark.Extension.ObfuscateEmailSpec
                     , Text.MMark.Extension.PunctuationPrettifierSpec
+                    , Text.MMark.Extension.SkylightingSpec
                     , Text.MMark.Extension.TableOfContentsSpec
                     , Text.MMark.Extension.TestUtils
   default-language:   Haskell2010
diff --git a/tests/Text/MMark/Extension/SkylightingSpec.hs b/tests/Text/MMark/Extension/SkylightingSpec.hs
new file mode 100644
--- /dev/null
+++ b/tests/Text/MMark/Extension/SkylightingSpec.hs
@@ -0,0 +1,20 @@
+{-# LANGUAGE OverloadedStrings #-}
+
+module Text.MMark.Extension.SkylightingSpec (spec) where
+
+import Test.Hspec
+import Text.MMark.Extension.Skylighting
+import Text.MMark.Extension.TestUtils
+
+spec :: Spec
+spec =
+  describe "skylighting" $ do
+    let to = withExt skylighting
+    context "when info string does not result in a successful lookup" $
+      it "has no effect" $
+        "```foo\nmain :: IO ()\nmain = return ()\n```\n" `to`
+          "<pre><code class=\"language-foo\">main :: IO ()\nmain = return ()\n</code></pre>\n"
+    context "with info string results in a successful lookup" $
+      it "renders it correctly" $
+        "```haskell\nmain :: IO ()\nmain = return ()\n```\n" `to`
+          "<div class=\"source-code\"><pre><code class=\"language-haskell\"><span class=\"ot\">main ::</span><span> </span><span class=\"dt\">IO</span><span> ()</span>\n<span>main </span><span class=\"fu\">=</span><span> return ()</span>\n</code></pre></div>\n"
