diff --git a/ChangeLog.md b/ChangeLog.md
new file mode 100644
--- /dev/null
+++ b/ChangeLog.md
@@ -0,0 +1,3 @@
+# Changelog for lucid-alpine
+
+## Unreleased changes
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,2 @@
+# lucid-alpine
+Lucid EDSL + Alpine.js = lucid-alpine
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/app/Main.hs b/app/Main.hs
new file mode 100644
--- /dev/null
+++ b/app/Main.hs
@@ -0,0 +1,4 @@
+module Main where
+
+main :: IO ()
+main = print "Alpine rocks!"
diff --git a/lucid-alpine.cabal b/lucid-alpine.cabal
new file mode 100644
--- /dev/null
+++ b/lucid-alpine.cabal
@@ -0,0 +1,58 @@
+cabal-version:      1.12
+name:               lucid-alpine
+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-alpine#readme
+bug-reports:        https://github.com/WaviLabs/lucid-alpine/issues
+synopsis:           Use Alpine.js in your lucid templates
+description:
+    Please see the README on GitHub at <https://github.com/WaviLabs/lucid-alpine#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-alpine
+
+library
+    exposed-modules:  Lucid.Alpine
+    hs-source-dirs:   src
+    other-modules:    Paths_lucid_alpine
+    default-language: Haskell2010
+    build-depends:
+        base >=4.7 && <5,
+        lucid >=2.9.12.1 && <2.10,
+        text >=1.2.4.1 && <1.3
+
+executable lucid-alpine-exe
+    main-is:          Main.hs
+    hs-source-dirs:   app
+    other-modules:    Paths_lucid_alpine
+    default-language: Haskell2010
+    ghc-options:      -threaded -rtsopts -with-rtsopts=-N
+    build-depends:
+        base >=4.7 && <5,
+        lucid >=2.9.12.1 && <2.10,
+        lucid-alpine -any,
+        text >=1.2.4.1 && <1.3
+
+test-suite lucid-alpine-test
+    type:             exitcode-stdio-1.0
+    main-is:          Spec.hs
+    hs-source-dirs:   test
+    other-modules:    Paths_lucid_alpine
+    default-language: Haskell2010
+    ghc-options:      -threaded -rtsopts -with-rtsopts=-N
+    build-depends:
+        base >=4.7 && <5,
+        lucid >=2.9.12.1 && <2.10,
+        lucid-alpine -any,
+        text >=1.2.4.1 && <1.3
diff --git a/src/Lucid/Alpine.hs b/src/Lucid/Alpine.hs
new file mode 100644
--- /dev/null
+++ b/src/Lucid/Alpine.hs
@@ -0,0 +1,197 @@
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE LambdaCase #-}
+
+module Lucid.Alpine where
+
+import Data.Text
+import Lucid.Base (Attribute, makeAttribute)
+
+-- | x-data
+-- Declare a new Alpine component and its data for a block of HTML
+xData_ :: Maybe Text -> Attribute
+xData_ = \case
+  Nothing     -> makeAttribute "data-x-data" mempty
+  Just object -> makeAttribute "data-x-data" object
+
+{-
+<div x-data="{ open: false }">
+    ...
+</div>
+-}
+
+-- | x-bind
+-- Dynamically set HTML attributes on an element
+xBind_
+  :: Text -- ^ Attribute name
+  -> Text
+  -> Attribute
+xBind_ attr = makeAttribute ("data-x-bind:" <> attr)
+
+{-
+<div x-bind:class="! open ? 'hidden' : ''">
+  ...
+</div>
+-}
+
+-- | x-on
+-- Listen for browser events on an element
+xOn_
+  :: Text -- ^ Event name
+  -> Text
+  -> Attribute
+xOn_ event = makeAttribute ("data-x-on:" <> event)
+
+{-
+<button x-on:click="open = ! open">
+  Toggle
+</button>
+-}
+
+-- | x-text
+-- Set the text content of an element
+xText_ :: Text -> Attribute
+xText_ = makeAttribute "data-x-text"
+
+{-
+<div>
+  Copyright ©
+
+  <span x-text="new Date().getFullYear()"></span>
+</div>
+-}
+
+-- | x-html
+-- Set the inner HTML of an element
+xHtml_ :: Text -> Attribute
+xHtml_ = makeAttribute "data-x-html"
+
+{-
+<div x-html="(await axios.get('/some/html/partial')).data">
+  ...
+</div>
+-}
+
+-- | x-model
+-- Synchronize a piece of data with an input element
+xModel_
+  :: [Text] -- ^ List of x-model modifiers
+  -> Text
+  -> Attribute
+xModel_ mods = case mods of
+  [] -> makeAttribute "data-x-model"
+  _  -> makeAttribute ("data-x-model." <> intercalate "." mods)
+
+{-
+<div x-data="{ search: '' }">
+  <input type="text" x-model="search">
+ 
+  Searching for: <span x-text="search"></span>
+</div>
+-}
+
+-- | x-show
+-- Toggle the visibility of an element
+xShow_ :: Text -> Attribute
+xShow_ = makeAttribute "data-x-show"
+
+{-
+<div x-show="open">
+  ...
+</div>
+-}
+
+-- | x-transition
+-- Transition an element in and out using CSS transitions
+xTransition_
+  :: Maybe Text -- ^ Transition directive
+  -> [Text]     -- ^ List of x-transition modifiers
+  -> Text
+  -> Attribute
+xTransition_ Nothing [] _ = makeAttribute "data-x-transition" mempty -- No directive or modifiers
+xTransition_ (Just dir) [] attrVal = makeAttribute ("data-x-transition:" <> dir) attrVal -- Directive with custom transition classes
+xTransition_ Nothing mods _ = makeAttribute ("data-x-transition." <> intercalate "." mods) mempty -- No directive, but with modifiers
+xTransition_ (Just dir) mods _ = makeAttribute ("data-x-transition:" <> dir <> "." <> intercalate "." mods) mempty -- Directive with modifiers
+
+{-
+<div x-show="open" x-transition>
+  ...
+</div>
+-}
+
+-- | x-for
+-- Repeat a block of HTML based on a data set
+xFor_ :: Text -> Attribute
+xFor_ = makeAttribute "data-x-for"
+
+xForKey_ :: Text -> Attribute
+xForKey_ = makeAttribute ":key"
+
+{-
+<template x-for="post in posts">
+  <h2 x-text="post.title"></h2>
+</template>
+-}
+
+-- | x-if
+-- Conditionally add/remove a block of HTML from the page entirely.
+xIf_ :: Text -> Attribute
+xIf_ = makeAttribute "data-x-if"
+
+{-
+<template x-if="open">
+  <div>...</div>
+</template>
+-}
+
+-- | x-init
+-- Run code when an element is initialized by Alpine
+xInit_ :: Text -> Attribute
+xInit_ = makeAttribute "data-x-init"
+
+{-
+<div x-init="date = new Date()"></div>
+-}
+
+-- | x-effect
+-- Execute a script each time one of its dependancies change
+xEffect_ :: Text -> Attribute
+xEffect_ = makeAttribute "data-x-effect"
+
+{-
+<div x-effect="console.log('Count is '+count)"></div>
+-}
+
+-- | x-ref
+-- Reference elements directly by their specified keys using the $refs magic property
+xRef_ :: Text -> Attribute
+xRef_ = makeAttribute "data-x-ref"
+
+{-
+<input type="text" x-ref="content">
+ 
+<button x-on:click="navigator.clipboard.writeText($refs.content.value)">
+  Copy
+</button>
+-}
+
+-- | x-cloak
+-- Hide a block of HTML until after Alpine is finished initializing its contents
+xCloak_ :: Attribute
+xCloak_ = makeAttribute "data-x-cloak" mempty
+
+{-
+<div x-cloak>
+  ...
+</div>
+-}
+
+-- | x-ignore
+-- Prevent a block of HTML from being initialized by Alpine
+xIgnore_ :: Attribute
+xIgnore_ = makeAttribute "data-x-ignore" mempty
+
+{-
+<div x-ignore>
+  ...
+</div>
+-}
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"
