diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,31 @@
+Copyright (c) 2015, Doug Beardsley
+Copyright (c) 2021, Tim Johns
+
+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 Doug Beardsley 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/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/reflex-dom-ace.cabal b/reflex-dom-ace.cabal
new file mode 100644
--- /dev/null
+++ b/reflex-dom-ace.cabal
@@ -0,0 +1,47 @@
+cabal-version: 1.24
+Name: reflex-dom-ace
+version: 0.3.0.0
+Synopsis: Basic support for using the Ace editor with Reflex.
+Description:
+  Provides basic support for using the Ace editor with Reflex-DOM web apps.
+  .
+  Ace editor: <https://ace.c9.io/>.
+License: BSD3
+License-file: LICENSE
+Author: Doug Beardsley, Tim Johns
+Maintainer: slimtim10@gmail.com
+Category: Web
+Build-type: Simple
+Stability: Experimental
+
+library
+  hs-source-dirs: src
+
+  exposed-modules:
+    Reflex.Dom.Ace
+
+  build-depends:
+      base >= 4.7 && < 4.15
+    , containers == 0.6.*
+    , data-default >= 0.5 && < 0.8
+    , ghcjs-dom >= 0.9.1.0 && < 0.10
+    , jsaddle >= 0.9.0.0 && < 0.10
+    , lens >= 4.7 && < 5
+    , mtl >= 2.1 && < 2.3
+    , reflex-dom-core >= 0.6.1.0 && < 0.7
+    , text == 1.2.*
+
+  default-extensions:
+    OverloadedStrings
+    FlexibleContexts
+
+  if impl(ghcjs)
+    cpp-options: -DGHCJS_GC_INTERVAL=60000
+
+  ghc-options: -Wall -fno-warn-unused-do-bind -fno-warn-orphans
+
+  default-language: Haskell2010
+
+source-repository head
+  type: git
+  location: https://github.com/SlimTim10/reflex-dom-ace
diff --git a/src/Reflex/Dom/Ace.hs b/src/Reflex/Dom/Ace.hs
new file mode 100644
--- /dev/null
+++ b/src/Reflex/Dom/Ace.hs
@@ -0,0 +1,329 @@
+{-|
+
+Basic support for using the Ace editor with Reflex.
+
+-}
+
+module Reflex.Dom.Ace where
+
+import Control.Lens ((^.))
+import Control.Monad (unless, void, join)
+import Data.Default (Default)
+import Data.Map (Map)
+import Data.Maybe (fromMaybe)
+import Data.Text (Text)
+import qualified Data.Text as T
+import qualified Control.Monad.Trans as Trans
+import qualified Data.Bifunctor as Bi
+
+import qualified Language.Javascript.JSaddle as JS
+import Language.Javascript.JSaddle.Object ((<#))
+import qualified Reflex.Dom.Core as R
+
+
+data AceTheme
+  = AceTheme_Chrome
+  | AceTheme_Clouds
+  | AceTheme_CrimsonEditor
+  | AceTheme_Dawn
+  | AceTheme_Dreamweaver
+  | AceTheme_Eclipse
+  | AceTheme_Github
+  | AceTheme_Iplastic
+  | AceTheme_SolarizedLight
+  | AceTheme_Textmate
+  | AceTheme_Tomorrow
+  | AceTheme_Xcode
+  | AceTheme_Kuroir
+  | AceTheme_Katzenmilch
+  | AceTheme_Sqlserver
+  | AceTheme_Ambiance
+  | AceTheme_Chaos
+  | AceTheme_CloudsMidnight
+  | AceTheme_Cobalt
+  | AceTheme_Gruvbox
+  | AceTheme_IdleFingers
+  | AceTheme_KrTheme
+  | AceTheme_Merbivore
+  | AceTheme_MerbivoreSoft
+  | AceTheme_MonoIndustrial
+  | AceTheme_Monokai
+  | AceTheme_PastelOnDark
+  | AceTheme_SolarizedDark
+  | AceTheme_Terminal
+  | AceTheme_TomorrowNight
+  | AceTheme_TomorrowNightBlue
+  | AceTheme_TomorrowNightBright
+  | AceTheme_TomorrowNightEighties
+  | AceTheme_Twilight
+  | AceTheme_VibrantInk
+  deriving (Eq,Ord,Enum,Bounded)
+
+instance Show AceTheme where
+    show AceTheme_Ambiance              = "ambiance"
+    show AceTheme_Chaos                 = "chaos"
+    show AceTheme_Chrome                = "chrome"
+    show AceTheme_Clouds                = "clouds"
+    show AceTheme_CloudsMidnight        = "clouds_midnight"
+    show AceTheme_Cobalt                = "cobalt"
+    show AceTheme_CrimsonEditor         = "crimson_editor"
+    show AceTheme_Dawn                  = "dawn"
+    show AceTheme_Dreamweaver           = "dreamweaver"
+    show AceTheme_Eclipse               = "eclipse"
+    show AceTheme_Github                = "github"
+    show AceTheme_Gruvbox               = "gruvbox"
+    show AceTheme_IdleFingers           = "idle_fingers"
+    show AceTheme_Iplastic              = "iplastic"
+    show AceTheme_Katzenmilch           = "katzenmilch"
+    show AceTheme_KrTheme               = "kr_theme"
+    show AceTheme_Kuroir                = "kuroir"
+    show AceTheme_Merbivore             = "merbivore"
+    show AceTheme_MerbivoreSoft         = "merbivore_soft"
+    show AceTheme_MonoIndustrial        = "mono_industrial"
+    show AceTheme_Monokai               = "monokai"
+    show AceTheme_PastelOnDark          = "pastel_on_dark"
+    show AceTheme_SolarizedDark         = "solarized_dark"
+    show AceTheme_SolarizedLight        = "solarized_light"
+    show AceTheme_Sqlserver             = "sqlserver"
+    show AceTheme_Terminal              = "terminal"
+    show AceTheme_Textmate              = "textmate"
+    show AceTheme_Tomorrow              = "tomorrow"
+    show AceTheme_TomorrowNight         = "tomorrow_night"
+    show AceTheme_TomorrowNightBlue     = "tomorrow_night_blue"
+    show AceTheme_TomorrowNightBright   = "tomorrow_night_bright"
+    show AceTheme_TomorrowNightEighties = "tomorrow_night_eighties"
+    show AceTheme_Twilight              = "twilight"
+    show AceTheme_VibrantInk            = "vibrant_ink"
+    show AceTheme_Xcode                 = "xcode"
+
+
+data AceConfig = AceConfig
+    { _aceConfigElemAttrs       :: Map Text Text
+    , _aceConfigBasePath        :: Maybe Text
+    , _aceConfigMode            :: Maybe Text
+    , _aceConfigWordWrap        :: Bool
+    , _aceConfigShowPrintMargin :: Bool
+    }
+
+
+data AceDynConfig = AceDynConfig
+    { _aceDynConfigTheme :: Maybe AceTheme
+    }
+
+
+instance Default AceConfig where
+    def = AceConfig R.def R.def R.def False False
+
+
+newtype AceInstance = AceInstance { unAceInstance :: JS.JSVal }
+
+
+data Ace t = Ace
+    { aceRef   :: R.Dynamic t (Maybe AceInstance)
+    , aceValue :: R.Dynamic t Text
+    }
+
+
+------------------------------------------------------------------------------
+-- The type of editor session line annotation.
+data AnnotationType = AnnotationError
+                    | AnnotationWarning
+                    deriving (Show, Read)
+
+------------------------------------------------------------------------------
+instance JS.ToJSVal AnnotationType where
+  toJSVal AnnotationError   = JS.toJSVal ("error":: Text)
+  toJSVal AnnotationWarning = JS.toJSVal ("warning" :: Text)
+
+
+------------------------------------------------------------------------------
+-- A line annotation for marking a specific line within the editor session as
+-- an error or a warning.
+data Annotation = Annotation { annotationRow    :: Int
+                             , annotationColumn :: Int
+                             , annotationText   :: Text
+                             , annotationType   :: AnnotationType
+                             } deriving (Show, Read)
+
+
+------------------------------------------------------------------------------
+instance JS.MakeObject Annotation where
+  makeObject (Annotation row col txt typ) = do
+    o <- JS.create
+    (o <# ("row" :: Text)   ) row
+    (o <# ("column" :: Text)) col
+    (o <# ("text" :: Text)  ) txt
+    (o <# ("type" :: Text)  ) typ
+    return o
+
+
+instance JS.ToJSVal Annotation where
+  toJSVal = (JS.toJSVal =<<) . JS.makeObject
+
+
+------------------------------------------------------------------------------
+startAce :: JS.MonadJSM m => Text -> AceConfig -> m AceInstance
+startAce containerId ac = JS.liftJSM $ do
+  aceVal <- JS.jsg ("ace" :: Text)
+  let
+    (basePath, mode) = join Bi.bimap (fromMaybe (T.pack "") . ($ ac))
+      (_aceConfigBasePath, _aceConfigMode)
+  -- Set the base path if given
+  unless (T.null basePath) $ do
+    config <- aceVal ^. JS.js ("config" :: Text)
+    void $ config ^. JS.js2 ("set" :: Text) ("basePath" :: Text) basePath
+  -- Start and return an editing session
+  editor <- aceVal ^. JS.js1 ("edit" :: Text) containerId
+  let aceInst = AceInstance editor
+  -- Set the mode if given
+  unless (T.null mode) $ do
+    setModeAce mode aceInst
+  setUseWrapMode (_aceConfigWordWrap ac) aceInst
+  setShowPrintMargin (_aceConfigShowPrintMargin ac) aceInst
+  return aceInst
+
+
+------------------------------------------------------------------------------
+moveCursorToPosition :: JS.MonadJSM m => (Int, Int) -> AceInstance -> m ()
+moveCursorToPosition (r, c) (AceInstance ace) =
+  JS.liftJSM $ void $ ace ^. JS.js2 ("gotoLine" :: Text) r c
+
+
+------------------------------------------------------------------------------
+setThemeAce :: JS.MonadJSM m => Maybe AceTheme -> AceInstance -> m ()
+setThemeAce Nothing      _                 = return ()
+setThemeAce (Just theme) (AceInstance ace) =
+  JS.liftJSM $ void $ ace ^. JS.js1 ("setTheme" :: Text) themeStr
+  where themeStr = "ace/theme/" <> show theme
+
+
+------------------------------------------------------------------------------
+setModeAce :: JS.MonadJSM m => Text -> AceInstance -> m ()
+setModeAce mode (AceInstance ace) = JS.liftJSM $ do
+  session <- ace ^. JS.js ("session" :: Text)
+  void $ session ^. JS.js1 ("setMode" :: Text) modeStr
+  where modeStr = "ace/mode/" <> mode
+
+
+------------------------------------------------------------------------------
+setUseWrapMode :: JS.MonadJSM m => Bool -> AceInstance -> m ()
+setUseWrapMode shouldWrap (AceInstance ace) = JS.liftJSM $ do
+  session <- ace ^. JS.js0 ("getSession" :: Text)
+  void $ session ^. JS.js1 ("setUseWrapMode" :: Text) shouldWrap
+
+
+------------------------------------------------------------------------------
+setShowPrintMargin :: JS.MonadJSM m => Bool -> AceInstance -> m ()
+setShowPrintMargin shouldShow (AceInstance ace) =
+  JS.liftJSM $ void $ ace ^. JS.js2 ("setOption" :: Text) ("showPrintMargin" :: Text) shouldShow
+
+
+------------------------------------------------------------------------------
+setUseWorker :: JS.MonadJSM m => Bool -> AceInstance -> m ()
+setUseWorker shouldUse (AceInstance ace) =
+  JS.liftJSM $ void $ ace ^. JS.js2 ("setOption" :: Text) ("useWorker" :: Text) shouldUse
+
+
+------------------------------------------------------------------------------
+setAnnotations :: JS.MonadJSM m => [Annotation] -> AceInstance -> m ()
+setAnnotations as (AceInstance ace) = JS.liftJSM $ do
+  session <- ace ^. JS.js0 ("getSession" :: Text)
+  annotations <- JS.toJSValListOf as
+  void $ session ^. JS.js1 ("setAnnotations" :: Text) annotations
+
+
+------------------------------------------------------------------------------
+setConfigAce :: JS.MonadJSM m => Text -> Text -> AceInstance -> m ()
+setConfigAce t1 t2 (AceInstance ace) = JS.liftJSM $ do
+  cfg <- ace ^. JS.js ("config" :: Text)
+  void $ cfg ^. JS.js2 ("set" :: Text) t1 t2
+
+
+------------------------------------------------------------------------------
+getValueAce :: JS.MonadJSM m => AceInstance -> m Text
+getValueAce (AceInstance ace) =
+  JS.liftJSM $ ace ^. JS.js0 ("getValue" :: Text) >>= JS.fromJSValUnchecked
+
+
+------------------------------------------------------------------------------
+setValueAce :: JS.MonadJSM m => Text -> AceInstance -> m ()
+setValueAce t (AceInstance ace) =
+  JS.liftJSM $ void $ ace ^. JS.js2 ("setValue" :: Text) t (-1 :: Int)
+
+
+------------------------------------------------------------------------------
+setupValueListener
+  :: ( JS.MonadJSM (R.Performable m)
+     , R.DomBuilder t m
+     , R.PostBuild t m
+     , R.TriggerEvent t m
+     , R.PerformEvent t m
+     )
+  => AceInstance
+  -> m (R.Event t Text)
+setupValueListener (AceInstance ace) = do
+  pb  <- R.getPostBuild
+  let act cb = JS.liftJSM $ do
+        jscb <- JS.asyncFunction $ \_ _ _ ->
+          getValueAce (AceInstance ace) >>= Trans.liftIO . cb
+        void $ ace ^. JS.js2 ("on" :: Text) ("change" :: Text) jscb
+  R.performEventAsync (act <$ pb)
+
+
+------------------------------------------------------------------------------
+-- | Main entry point
+--
+-- IMPORTANT NOTE:
+--
+-- This currently does not work if your app is using reflex-dom's
+-- mainWidgetWithHead or mainWidgetWithCss.
+aceWidget
+    :: ( R.DomBuilder t m
+       , R.PostBuild t m
+       , R.MonadHold t m
+       , JS.MonadJSM m
+       , R.TriggerEvent t m
+       , R.PerformEvent t m
+       , JS.MonadJSM (R.Performable m)
+       )
+    => AceConfig -- ^ Ace editor configurations
+    -> AceDynConfig -- ^ Ace editor theme
+    -> R.Event t AceDynConfig -- ^ Updatable Ace editor theme
+    -> Text -- ^ ID of desired container element
+    -> Text -- ^ Initial Ace editor contents
+    -> R.Event t Text -- ^ Updatable Ace editor contents
+    -> m (Ace t)
+aceWidget ac adc adcUps containerId initContents contentsUps = do
+    aceInstance <- startAce containerId ac
+    onChange <- setupValueListener aceInstance
+    updatesDyn <- R.holdDyn initContents onChange
+
+    let ace = Ace (R.constDyn $ pure aceInstance) updatesDyn
+    setThemeAce (_aceDynConfigTheme adc) aceInstance
+    void $ withAceInstance ace (setThemeAce . _aceDynConfigTheme <$> adcUps)
+    R.performEvent_ $ R.ffor contentsUps $ \c -> setValueAce c aceInstance
+    return ace
+
+
+------------------------------------------------------------------------------
+-- | Convenient helper function for running functions that need an AceInstance.
+withAceInstance
+    :: R.PerformEvent t m
+    => Ace t
+    -> R.Event t (AceInstance -> R.Performable m ())
+    -> m (R.Event t ())
+withAceInstance ace evt = withAceInstance' ace (f <$> evt)
+  where
+    f _ Nothing  = return ()
+    f g (Just a) = g a
+
+
+------------------------------------------------------------------------------
+-- | More powerful function for running functions that need an AceInstance.
+withAceInstance'
+    :: R.PerformEvent t m
+    => Ace t
+    -> R.Event t (Maybe AceInstance -> R.Performable m a)
+    -> m (R.Event t a)
+withAceInstance' ace =
+  R.performEvent . R.attachPromptlyDynWith (flip ($)) (aceRef ace)
