diff --git a/ChangeLog.md b/ChangeLog.md
new file mode 100644
--- /dev/null
+++ b/ChangeLog.md
@@ -0,0 +1,5 @@
+# Changelog for lucid-htmx
+
+## 0.1.0.0
+
+First release of `lucid-htmx`!
diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,30 @@
+Copyright Author name here (c) 2021
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+    * Redistributions of source code must retain the above copyright
+      notice, this list of conditions and the following disclaimer.
+
+    * 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.
+
+    * Neither the name of Author name here nor the names of other
+      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
+OWNER 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 @@
+# lucid-htmx
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/lucid-htmx.cabal b/lucid-htmx.cabal
new file mode 100644
--- /dev/null
+++ b/lucid-htmx.cabal
@@ -0,0 +1,52 @@
+cabal-version:      1.12
+name:               lucid-htmx
+version:            0.1.0.0
+license:            BSD3
+license-file:       LICENSE
+copyright:          (c) 2021 Wavi Labs LLC
+maintainer:         rashad@wavilabs.com
+author:             Wavi Labs LLC
+homepage:           https://github.com/WaviLabs/lucid-htmx#readme
+bug-reports:        https://github.com/WaviLabs/lucid-htmx/issues
+synopsis:           Use htmx in your lucid templates
+description:
+    Please see the README on GitHub at <https://github.com/WaviLabs/lucid-htmx#readme>
+
+category:           Web, HTML
+build-type:         Simple
+extra-source-files:
+    README.md
+    ChangeLog.md
+
+source-repository head
+    type:     git
+    location: https://github.com/WaviLabs/lucid-htmx
+
+library
+    exposed-modules:
+        Lucid.HTMX
+        Lucid.HTMX.QuasiQuoters
+        Lucid.HTMX.Servant
+
+    hs-source-dirs:   src
+    other-modules:    Paths_lucid_htmx
+    default-language: Haskell2010
+    build-depends:
+        base >=4.7 && <5,
+        lucid >=2.9.12.1 && <2.10,
+        servant >=0.18.3 && <0.19,
+        text >=1.2.4.1 && <1.3
+
+test-suite lucid-htmx-test
+    type:             exitcode-stdio-1.0
+    main-is:          Spec.hs
+    hs-source-dirs:   test
+    other-modules:    Paths_lucid_htmx
+    default-language: Haskell2010
+    ghc-options:      -threaded -rtsopts -with-rtsopts=-N
+    build-depends:
+        base >=4.7 && <5,
+        lucid >=2.9.12.1 && <2.10,
+        lucid-htmx -any,
+        servant >=0.18.3 && <0.19,
+        text >=1.2.4.1 && <1.3
diff --git a/src/Lucid/HTMX.hs b/src/Lucid/HTMX.hs
new file mode 100644
--- /dev/null
+++ b/src/Lucid/HTMX.hs
@@ -0,0 +1,114 @@
+{-# LANGUAGE OverloadedStrings #-}
+
+module Lucid.HTMX where
+
+import Lucid.Base (Attribute, makeAttribute)
+import Data.Text (Text)
+
+-- | <https://htmx.org/attributes/hx-boost/>
+hxBoost_ :: Text -> Attribute
+hxBoost_ = makeAttribute "data-hx-boost"
+
+-- | <https://htmx.org/attributes/hx-confirm/>
+hxConfirm_ :: Text -> Attribute
+hxConfirm_ = makeAttribute "data-hx-confirm"
+
+-- | <https://htmx.org/attributes/hx-delete/>
+hxDelete_ :: Text -> Attribute
+hxDelete_ = makeAttribute "data-hx-delete"
+
+-- | <https://htmx.org/attributes/hx-disable/>
+hxDisable_ :: Attribute
+hxDisable_ = makeAttribute "data-hx-disable" mempty
+
+-- | <https://htmx.org/attributes/hx-encoding/>
+hxEncoding_ :: Text -> Attribute
+hxEncoding_ = makeAttribute "data-hx-encoding"
+
+-- | <https://htmx.org/attributes/hx-ext/>
+hxExt_ :: Text -> Attribute
+hxExt_ = makeAttribute "data-hx-ext"
+
+-- | <https://htmx.org/attributes/hx-get/>
+hxGet_ :: Text -> Attribute
+hxGet_ = makeAttribute "data-hx-get"
+
+-- | <https://htmx.org/attributes/hx-headers/>
+hxHeaders_ :: Text -> Attribute
+hxHeaders_ = makeAttribute "data-hx-headers"
+
+-- | <https://htmx.org/attributes/hx-history-alt/>
+hxHistoryElt_ :: Attribute
+hxHistoryElt_ = makeAttribute "data-hx-history-elt" mempty
+
+-- | <https://htmx.org/attributes/hx-include/>
+hxInclude_ :: Text -> Attribute
+hxInclude_ = makeAttribute "data-hx-include"
+
+-- | <https://htmx.org/attributes/hx-indicator/>
+hxIndicator_ :: Text -> Attribute
+hxIndicator_ = makeAttribute "data-hx-indicator"
+
+-- | <https://htmx.org/attributes/hx-params/>
+hxParams_ :: Text -> Attribute
+hxParams_ = makeAttribute "data-hx-params"
+
+-- | <https://htmx.org/attributes/hx-patch/>
+hxPatch_ :: Text -> Attribute
+hxPatch_ = makeAttribute "data-hx-patch"
+
+-- | <https://htmx.org/attributes/hx-post/>
+hxPost_ :: Text -> Attribute
+hxPost_ = makeAttribute "data-hx-post"
+
+-- | <https://htmx.org/attributes/hx-preserve/>
+hxPreserve_ :: Text -> Attribute
+hxPreserve_ = makeAttribute "data-hx-preserve"
+
+-- | <https://htmx.org/attributes/hx-prompt/>
+hxPrompt_ :: Text -> Attribute
+hxPrompt_ = makeAttribute "data-hx-prompt"
+
+-- | <https://htmx.org/attributes/hx-push-url/>
+hxPushUrl_ :: Text -> Attribute
+hxPushUrl_ = makeAttribute "data-hx-push-url"
+
+-- | <https://htmx.org/attributes/hx-put/>
+hxPut_ :: Text -> Attribute
+hxPut_ = makeAttribute "data-hx-put"
+
+-- | <https://htmx.org/attributes/hx-request/>
+hxRequest_ :: Text -> Attribute
+hxRequest_ = makeAttribute "data-hx-request"
+
+-- | <https://htmx.org/attributes/hx-select/>
+hxSelect_ :: Text -> Attribute
+hxSelect_ = makeAttribute "data-hx-select"
+
+-- | <https://htmx.org/attributes/hx-sse/>
+hxSse_ :: Text -> Attribute
+hxSse_ = makeAttribute "data-hx-sse"
+
+-- | <https://htmx.org/attributes/hx-swap-oob/>
+hxSwapOob_ :: Text -> Attribute
+hxSwapOob_ = makeAttribute "data-hx-swap-oob"
+
+-- | <https://htmx.org/attributes/hx-swap/>
+hxSwap_ :: Text -> Attribute
+hxSwap_ = makeAttribute "data-hx-swap"
+
+-- | <https://htmx.org/attributes/hx-target/>
+hxTarget_ :: Text -> Attribute
+hxTarget_ = makeAttribute "data-hx-target"
+
+-- | <https://htmx.org/attributes/hx-trigger/>
+hxTrigger_ :: Text -> Attribute
+hxTrigger_ = makeAttribute "data-hx-trigger"
+
+-- | <https://htmx.org/attributes/hx-vals/>
+hxVals_ :: Text -> Attribute
+hxVals_ = makeAttribute "data-hx-vals"
+
+-- | <https://htmx.org/attributes/hx-ws/>
+hxWs_ :: Text -> Attribute
+hxWs_ = makeAttribute "data-hx-ws"
diff --git a/src/Lucid/HTMX/QuasiQuoters.hs b/src/Lucid/HTMX/QuasiQuoters.hs
new file mode 100644
--- /dev/null
+++ b/src/Lucid/HTMX/QuasiQuoters.hs
@@ -0,0 +1,215 @@
+{-# LANGUAGE DeriveAnyClass #-}
+{-# LANGUAGE DeriveGeneric #-}
+{-# LANGUAGE ExistentialQuantification #-}
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE InstanceSigs #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE RecordWildCards #-}
+
+module Lucid.HTMX.QuasiQuoters where
+
+{-
+{- Idea for QuasiQuoting syntax:
+
+[htmx| hx-get="bar/foo/baz?id=34" hx-swap="delay:500" hx-trigger="click" |]
+
+-}
+import Data.Text as Text
+import HTMX.Types
+import Lucid.HTMX
+
+toUrlPiece' = ("/" <>) . toUrlPiece
+
+-- -- | Makes hx_boost_ a "boolean attribute" since the only valid value for hx-boost is "true".
+-- hx_boost_ :: Attribute
+-- hx_boost_ = Base.hx_boost_ "true"
+
+-- hx_confirm_ :: Text -> Attribute
+-- hx_confirm_ = Base.hx_confirm_
+
+hxDeleteSafe_ :: Link -> Attribute
+hxDeleteSafe_ = Base.hx_delete_ . toUrlPiece'
+
+-- hx_disable_ :: Attribute
+-- hx_disable_ = Base.hx_disable_
+
+-- | Makes hx_encoding_ a "boolean attribute" since the only valid value for hx-encoding is "multipart/form-data".
+-- hx_encoding_ :: Attribute
+-- hx_encoding_ = Base.hx_encoding_ "multipart/form-data"
+
+getHTMXExtName :: HTMXExtension -> Text
+getHTMXExtName htmxExt = case htmxExt of
+    JSONEnc -> "json-enc"
+    MethodOverride -> "method-override"
+    MorphdomSwap -> "morphdom-swap"
+    ClientSideTemplates -> "client-side-templates"
+    Debug -> "debug"
+    PathDeps -> "path-deps"
+    ClassTools -> "class-tools"
+    RemoveMe -> "remove-me"
+    IncludeVals -> "included-vals"
+    AJAXHeader -> "ajax-header"
+    EventHeader -> "event-header"
+    Preload -> "preload"
+    OtherHTMXExtension extName -> extName
+
+hxExtSafe_ :: HXExt -> Attribute
+hxExtSafe_ HXExt{..} =
+   hxExt_
+   . ((if hxExtIgnore then "ignore:" else "") <>)
+   . Text.intercalate ","
+   . Prelude.map getHTMXExtName
+   . HashSet.toList
+   $ hxExtExtensions
+   -- HXExtVal htmxExtHashSet -> Base.hx_ext_ . Text.intercalate "," . Prelude.map getHTMXExtName . HashSet.toList $ htmxExtHashSet
+   -- HXExtValIgnore htmxExtHashSet -> Base.hx_ext_ . ("ignore:" <>) . Text.intercalate "," . Prelude.map getHTMXExtName . HashSet.toList $ htmxExtHashSet
+
+hxGetSafe_ :: Link -> Attribute
+hxGetSafe_ = Base.hx_get_ . toUrlPiece'
+
+-- | Value of hx_headers_ must be valid JSON
+hxHeadersSafe_ :: HXHeaders -> Attribute
+hxHeadersSafe_ HXHeaders{..} =
+   Base.hx_headers_
+   . Text.decodeUtf8
+   . LBS.toStrict
+   . Aeson.encode
+   $ hxHeadersJSON
+
+-- hx_history_elt_ :: Attribute
+-- hx_history_elt_ = Base.hx_history_elt_
+
+hxIncludeSafe_ :: HXInclude -> Attribute
+hxIncludeSafe_ HXInclude{..} = Base.hx_include_ . toCssSelector
+
+hxIndicatorSafe_ :: HXIndicator -> Attribute
+hxIndicatorSafe_ HXIndicator{..} =
+    hxIndicator_
+    . ((if hxIndicatorClosest then "closest:" else "") <>)
+    . toCssSelector
+    $ hxIndicatorSelector
+
+-- hx_indicator_ :: HXIndicatorVal -> Attribute 
+-- hx_indicator_ val = case val of
+--     HXIndicatorVal selector -> Base.hx_indicator_ . toCssSelector $ selector
+--     HXIndicatorValClosest selector -> Base.hx_indicator_ . ("closest " <>) . toCssSelector $ selector
+
+hxParamsSafe_ :: HXParams -> Attribute
+hxParamsSafe_ hxParams = case hxParams of
+    HXParams params -> hxParams_ . Text.intercalate "," $ params
+    HXParamsNot params -> hxParams_ . ("not " <>) . Text.intercalate "," $ params
+    HXParamsAll -> hxParams_ "*"
+    HXParamsNone -> hxParams_ "none"
+
+hx_patch_ :: Link -> Attribute
+hx_patch_ = Base.hx_patch_ . toUrlPiece'
+
+hx_post_ :: Link -> Attribute
+hx_post_ = Base.hx_post_ . toUrlPiece'
+
+-- For same reasons as hx_boost_
+hx_preserve_ :: Attribute
+hx_preserve_ = Base.hx_preserve_ "true"
+
+hx_prompt_ :: Text -> Attribute
+hx_prompt_ = Base.hx_prompt_
+
+hx_push_url_ :: Link -> Attribute
+hx_push_url_ = Base.hx_push_url_ . toUrlPiece'
+
+hx_put_ :: Link -> Attribute
+hx_put_ = Base.hx_put_ . toUrlPiece'
+
+hx_request_ :: HXRequestVal -> Attribute
+hx_request_ val = Base.hx_request_ $ case val of
+    (HXRequestVal (JavaScript _) _ _) -> ("javascript:" <>) . Text.decodeUtf8 . LBS.toStrict . Aeson.encode $ val
+    (HXRequestVal _ (JavaScript _) _) -> ("javascript:" <>) . Text.decodeUtf8 . LBS.toStrict . Aeson.encode $ val
+    (HXRequestVal _ _ (JavaScript _)) -> ("javascript:" <>) . Text.decodeUtf8 . LBS.toStrict . Aeson.encode $ val
+    _ -> Text.decodeUtf8 . LBS.toStrict . Aeson.encode $ val
+
+hx_select_ :: ToCssSelector a => a -> Attribute
+hx_select_ = Base.hx_select_ . toCssSelector
+
+hx_sse_ :: HXSSEVal -> Attribute
+hx_sse_ val = Base.hx_sse_ $ case val of
+    (HXSSEVal Nothing Nothing) -> ""
+    (HXSSEVal (Just link) Nothing) -> "connect:" <> (toUrlPiece' link)
+    (HXSSEVal Nothing (Just eventName)) -> "swap:" <> eventName
+    (HXSSEVal (Just link) (Just eventName)) -> "connect:" <> (toUrlPiece' link) <> " " <> "swap:" <> eventName
+
+pos :: SwapPos -> Text
+pos p = case p of
+    SwapPosInner -> "innerHTML"
+    SwapPosOuter -> "outerHTML"
+    SwapPosBeforeBegin -> "beforebegin"
+    SwapPosAfterBegin -> "afterbegin"
+    SwapPosBeforeEnd -> "beforeend"
+    SwapPosAfterEnd -> "afterend"
+    SwapPosNone -> "none"
+
+hx_swap_ :: HXSwapVal -> Attribute
+hx_swap_ HXSwapVal{..} = Base.hx_swap_ $ (pos hxSwapValPos) <> (swap hxSwapValSwap) <> (settle hxSwapValSettle) <> (view hxSwapValView)
+    where
+        swap :: Maybe SwapModDelay -> Text
+        swap s = case s of
+            Nothing -> ""
+            Just (SwapModDelay delay) -> " swap:" <> (Text.pack $ show delay) <> "s"
+
+        settle :: Maybe SwapModSettle -> Text
+        settle s = case s of
+            Nothing -> ""
+            Just (SwapModSettle delay) -> " settle:" <> (Text.pack $ show delay) <> "s"
+
+        view :: Maybe SwapModView -> Text
+        view v = case v of
+            Nothing -> ""
+            Just v' -> " " <> (viewPrefix v') <> (viewPostfix v')
+            where
+                viewPostfix :: SwapModView -> Text
+                viewPostfix v' = case v' of
+                    SwapModView _ sm ss -> case (sm, ss) of
+                        (ScrollMoveTop, ss') -> (selectorPrefix ss') <> "top"
+                        (ScrollMoveBottom, ss') -> (selectorPrefix ss') <> "bottom"
+
+                selectorPrefix :: Maybe ScrollSelector -> Text
+                selectorPrefix ss = case ss of
+                    Nothing -> ""
+                    Just ss' -> case ss' of
+                        ScrollSelector q -> (toCssSelector q) <> ":"
+                        ScrollSelectorWindow -> "window:"
+
+                viewPrefix :: SwapModView -> Text
+                viewPrefix v' = case v' of
+                      SwapModView SwapModViewTypeScroll _ _ -> "scroll:"
+                      SwapModView SwapModViewTypeShow _ _ -> "show:"
+
+hx_swap_oob_ :: HXSwapOOBVal -> Attribute
+hx_swap_oob_ val = Base.hx_swap_oob_ $ case val of
+    HXSwapOOBVal -> "true"
+    HXSwapOOBValSwap swapPos -> pos swapPos
+    HXSwapOOBValSwapSelector swapPos selector -> (pos swapPos) <> ":" <> (toCssSelector selector)
+
+hx_target_ :: HXTargetVal -> Attribute
+hx_target_ val = Base.hx_target_ $ case val of
+    HXTargetVal -> "this"
+    HXTargetValSelector selector -> toCssSelector selector
+    HXTargetValSelectorClosest selector -> "closest " <> (toCssSelector selector)
+    HXTargetValSelectorFind selector -> "find " <> (toCssSelector selector) 
+
+hx_vals_ :: ToJSON a => a -> Attribute
+hx_vals_ = Base.hx_vals_ . Text.decodeUtf8 . LBS.toStrict . Aeson.encode
+
+-- TODO: Study more. Basically all possible events plus event modifiers.
+
+-- hx_trigger_ :: HXTriggerVal -> Attribute
+-- hx_trigger_ = Base.hx_trigger_
+
+-- BELOW EXPERIMENTAL!!
+
+-- hx_ws_ :: HXWS -> Attribute
+-- hx_ws_ = hx_ws_
+
+-- TODO: Add QuasiQuoters for parsing and generating values that are checked at compile time for the various arguments to the HTMX attributes.
+-- TODO: Write tests to check that the Val types are generating the correct Text for the HTMX attributes. Tests for HTMX tag functionality maybe?
+
+-}
diff --git a/src/Lucid/HTMX/Servant.hs b/src/Lucid/HTMX/Servant.hs
new file mode 100644
--- /dev/null
+++ b/src/Lucid/HTMX/Servant.hs
@@ -0,0 +1,39 @@
+{-# LANGUAGE OverloadedStrings #-}
+
+module Lucid.HTMX.Servant where
+
+import Data.Text
+import Lucid.Base (Attribute)
+import Lucid.HTMX
+    ( hxGet_
+    , hxPost_
+    , hxPut_
+    , hxPatch_
+    , hxDelete_
+    , hxPushUrl_
+    )
+import Servant.Links (Link)
+import Servant.API (toUrlPiece, ToHttpApiData(..))
+
+toUrl :: ToHttpApiData a => a -> Text
+toUrl = ("/" <>) . toUrlPiece
+
+hxGetSafe_ :: Link -> Attribute
+hxGetSafe_ = hxGet_ . toUrl
+
+hxPostSafe_ :: Link -> Attribute
+hxPostSafe_ = hxPost_ . toUrl
+
+hxPutSafe_ :: Link -> Attribute
+hxPutSafe_ = hxPut_ . toUrl
+
+hxPatchSafe_ :: Link -> Attribute
+hxPatchSafe_ = hxPatch_ . toUrl
+
+hxDeleteSafe_ :: Link -> Attribute
+hxDeleteSafe_ = hxDelete_ . toUrl
+
+hxPushUrlSafe_ :: Either Bool Link -> Attribute
+hxPushUrlSafe_ boolOrUrl = hxPushUrl_ $ case boolOrUrl of
+    Left bool -> if bool then "true" else "false"
+    Right url -> toUrl url
diff --git a/test/Spec.hs b/test/Spec.hs
new file mode 100644
--- /dev/null
+++ b/test/Spec.hs
@@ -0,0 +1,2 @@
+main :: IO ()
+main = putStrLn "Test suite not yet implemented"
