diff --git a/reflex-dom-contrib.cabal b/reflex-dom-contrib.cabal
--- a/reflex-dom-contrib.cabal
+++ b/reflex-dom-contrib.cabal
@@ -1,5 +1,5 @@
 name:                reflex-dom-contrib
-version:             0.1
+version:             0.2
 synopsis:            A playground for experimenting with infrastructure and common code for reflex applications
 description:         This library is intended to be a public playground for
                      developing infrastructure, higher level APIs, and widget
@@ -38,6 +38,7 @@
     Reflex.Dom.Contrib.Widgets.CheckboxList
     Reflex.Dom.Contrib.Widgets.Common
     Reflex.Dom.Contrib.Widgets.DynTabs
+    Reflex.Dom.Contrib.Widgets.EditInPlace
 
   build-depends:
     aeson        >= 0.8  && < 0.10,
@@ -46,18 +47,21 @@
     bytestring   >= 0.10 && < 0.11,
     containers   >= 0.5  && < 0.6,
     data-default >= 0.5  && < 0.6,
-    ghcjs-base   >= 0.1  && < 0.2,
     ghcjs-dom    >= 0.1  && < 0.2,
     http-types   >= 0.8  && < 0.9,
     lens         >= 4.9  && < 4.13,
     mtl          >= 2.0  && < 2.3,
     readable     >= 0.3  && < 0.4,
-    reflex       >= 0.2  && < 0.3,
-    reflex-dom   >= 0.1  && < 0.2,
+    reflex       >= 0.2  && < 0.4,
+    reflex-dom   >= 0.1  && < 0.3,
+    safe         >= 0.3  && < 0.4,
     string-conv  >= 0.1  && < 0.2,
     text         >= 1.2  && < 1.3,
     time         >= 1.5  && < 1.6
 
+  if impl(ghcjs)
+    build-depends:
+      ghcjs-base   >= 0.1  && < 0.2
 
   default-language:    Haskell2010
 
diff --git a/src/Reflex/Contrib/Utils.hs b/src/Reflex/Contrib/Utils.hs
--- a/src/Reflex/Contrib/Utils.hs
+++ b/src/Reflex/Contrib/Utils.hs
@@ -66,3 +66,30 @@
     -> m (Dynamic t b)
 extractDyn f = liftM joinDyn . mapDyn f
 
+
+------------------------------------------------------------------------------
+-- | This function has the slight flaw that if (f initValue) == False, it will
+-- still get through.
+filterDyn
+    :: (MonadHold t m, Reflex t, Functor m)
+    => (a -> Bool)
+    -> Dynamic t a
+    -> m (Dynamic t a)
+filterDyn f d = fmap joinDyn $ holdDyn d $ fmap constDyn $
+    ffilter f (updated d)
+
+
+------------------------------------------------------------------------------
+-- | Similar to filterDyn, but here the initial value problem is visible
+-- because of the Maybe.
+fmapMaybeDyn
+    :: (MonadHold t m, Reflex t, Functor m)
+    => (a -> Maybe b)
+    -> Dynamic t a
+    -> m (Dynamic t (Maybe b))
+fmapMaybeDyn f d = do
+    d' <- mapDyn f d
+    fmap joinDyn $ holdDyn d' $ fmap (constDyn . Just) $
+      fmapMaybe f (updated d)
+
+
diff --git a/src/Reflex/Dom/Contrib/KeyEvent.hs b/src/Reflex/Dom/Contrib/KeyEvent.hs
--- a/src/Reflex/Dom/Contrib/KeyEvent.hs
+++ b/src/Reflex/Dom/Contrib/KeyEvent.hs
@@ -21,32 +21,14 @@
 import           Data.Char
 import           GHCJS.DOM.EventM (event)
 import           GHCJS.DOM.Types hiding (Event)
+#ifdef ghcjs_HOST_OS
 import           GHCJS.Types
+#endif
 import           Reflex.Dom
 ------------------------------------------------------------------------------
 
 
 ------------------------------------------------------------------------------
-#ifdef ghcjs_HOST_OS
-
-foreign import javascript unsafe "$1.ctrlKey"
-  js_uiEventGetCtrlKey :: JSRef UIEvent -> IO Bool
-
-foreign import javascript unsafe "$1.shiftKey"
-  js_uiEventGetShiftKey :: JSRef UIEvent -> IO Bool
-
-#else
-
-js_uiEventGetCtrlKey :: JSRef UIEvent -> IO Bool
-js_uiEventGetCtrlKey = error "js_uiEventGetCtrlKey only works in GHCJS."
-
-js_uiEventGetShiftKey :: JSRef UIEvent -> IO Bool
-js_uiEventGetShiftKey = error "js_uiEventGetShiftKey only works in GHCJS."
-
-#endif
-
-
-------------------------------------------------------------------------------
 -- | Data structure with the details of key events.
 data KeyEvent = KeyEvent
    { keKeyCode :: Int
@@ -79,6 +61,7 @@
 
 ------------------------------------------------------------------------------
 getKeyEvent :: ReaderT (t, UIEvent) IO KeyEvent
+#ifdef ghcjs_HOST_OS
 getKeyEvent = do
   e <- event
   code <- Reflex.Dom.getKeyEvent
@@ -86,4 +69,12 @@
                     <*> js_uiEventGetCtrlKey (unUIEvent e)
                     <*> js_uiEventGetShiftKey (unUIEvent e)
 
+foreign import javascript unsafe "$1['ctrlKey']"
+  js_uiEventGetCtrlKey :: JSRef UIEvent -> IO Bool
+
+foreign import javascript unsafe "$1['shiftKey']"
+  js_uiEventGetShiftKey :: JSRef UIEvent -> IO Bool
+#else
+getKeyEvent = error "getKeyEvent: can only be used with GHCJS"
+#endif
 
diff --git a/src/Reflex/Dom/Contrib/Pagination.hs b/src/Reflex/Dom/Contrib/Pagination.hs
--- a/src/Reflex/Dom/Contrib/Pagination.hs
+++ b/src/Reflex/Dom/Contrib/Pagination.hs
@@ -110,7 +110,7 @@
 -- Some convenient type aliases
 type PaginationCache k v = Map k [CacheVal v]
 type PaginationInput k = (k, PaginationQuery)
-type PaginationOutput k v = (k, CacheVal v)
+type PaginationOutput k v = (k, Maybe (CacheVal v))
 
 
 ------------------------------------------------------------------------------
@@ -151,14 +151,14 @@
   -> Event t (Map String ByteString, PaginationInput k)
   -- ^ Param map and pagination structure.  k is any additional information
   -- that you need to disambiguate multiple PaginationQuery entries.
-  -> m (Event t (PaginationResults a))
+  -> m (Event t (Maybe (PaginationResults a)))
 paginatedQuery pqp matchSearchString url input = do
     rec pcache <- foldDyn ($) mempty (addToCache pqp <$> r)
         let eme = attachWith (cachedQuery matchSearchString url)
                              (current pcache) input
         de <- widgetHold (return never) eme
         let r = switchPromptlyDyn $ de
-    return $ _pvValue . snd <$> r
+    return $ fmap _pvValue . snd <$> r
 
 
 ------------------------------------------------------------------------------
@@ -169,8 +169,8 @@
     -> PaginationCache k (PaginationResults v)
     -> PaginationCache k (PaginationResults v)
 addToCache PQParams{..} (k, v) m =
-    if _pvShouldStore v
-      then M.insertWith (++) k [v] m2
+    if fmap _pvShouldStore v == Just True
+      then M.insertWith (++) k [fromJust v] m2
       else m
   where
     m2 = if M.size m > pqpMaxCacheSize then prune pqpPruneAmount m else m
@@ -209,7 +209,7 @@
     pb <- getPostBuild
     let getData = do
           res <- getAndDecode (mkFullPath input <$ pb)
-          return $ (\v -> (k, CacheVal pq True $ fromMaybe err v)) <$> res
+          return $ (\v -> (k, CacheVal pq True <$> v)) <$> res
     case M.lookup k cache of
       Nothing -> getData
       Just pvs ->
@@ -219,9 +219,8 @@
               let pv2 = pv & (pvValue . prResults) %~
                              filter (matchSearchString $ _pqSearchString pq)
                            & pvShouldStore .~ False
-              return $ (k,pv2) <$ pb
+              return $ (k,Just pv2) <$ pb
   where
-    err = error "Error decoding pagination results"
     mkFullPath p = url <> "?" <> queryString p
     queryString (ps, (_,pq)) = formEncode $
       M.insert "pagination" (encode pq) ps
diff --git a/src/Reflex/Dom/Contrib/Utils.hs b/src/Reflex/Dom/Contrib/Utils.hs
--- a/src/Reflex/Dom/Contrib/Utils.hs
+++ b/src/Reflex/Dom/Contrib/Utils.hs
@@ -9,9 +9,13 @@
 -}
 
 module Reflex.Dom.Contrib.Utils
-  ( confirmEvent
+  ( alertEvent
+  , js_alert
+  , confirmEvent
+  , js_confirm
   , getWindowLocationPath
   , windowHistoryPushState
+  , setWindowLoc
   , widgetHoldHelper
   , putDebugLn
   , putDebugLnE
@@ -21,71 +25,92 @@
 import           Control.Monad
 import           Control.Monad.Reader
 import           GHCJS.DOM.Types hiding (Event)
+#ifdef ghcjs_HOST_OS
 import           GHCJS.Foreign
 import           GHCJS.Marshal
 import           GHCJS.Types
+#endif
 import           Reflex
 import           Reflex.Dom
 ------------------------------------------------------------------------------
 
 
 ------------------------------------------------------------------------------
+-- | Convenient function that pops up a javascript alert dialog box when an
+-- event fires with a message to display.
+alertEvent :: MonadWidget t m => (a -> String) -> Event t a -> m ()
 #ifdef ghcjs_HOST_OS
-
-foreign import javascript unsafe
-  "confirm($1)"
-  js_confirm :: JSString -> IO Bool
-
-foreign import javascript unsafe
-  "$1.location.pathname"
-  js_windowLocationPath :: JSRef DOMWindow ->  IO JSString
+alertEvent str e = performEvent_ (alert <$> e)
+  where
+    alert a = liftIO $ js_alert $ toJSString $ str a
 
 foreign import javascript unsafe
-  "window.history.pushState({},\"\",$1)"
-  js_windowHistoryPushState :: JSString -> IO ()
-
+  "alert($1)"
+  js_alert :: JSString -> IO ()
 #else
-
-js_confirm :: JSString -> IO Bool
-js_confirm = error "js_confirm only works in GHCJS."
-
-js_windowLocationPath :: JSRef DOMWindow -> IO JSString
-js_windowLocationPath _ =
-    error "Window location can only be retrieved in GHCJS."
-
-js_windowHistoryPushState :: JSString -> IO ()
-js_windowHistoryPushState =
-    error "Window pushState can only be changed in GHCJS."
-
+alertEvent = error "alertEvent: can only be used with GHCJS"
+js_alert = error "js_alert: can only be used with GHCJS"
 #endif
 
-
 ------------------------------------------------------------------------------
 -- | Convenient function that pops up a javascript confirmation dialog box
 -- when an event fires with a message to display.
 confirmEvent :: MonadWidget t m => (a -> String) -> Event t a -> m (Event t a)
+#ifdef ghcjs_HOST_OS
 confirmEvent str e = liftM (fmapMaybe id) $ performEvent (confirm <$> e)
   where
     confirm a = do
         ok <- liftIO $ js_confirm $ toJSString $ str a
         return $ if ok then Just a else Nothing
 
+foreign import javascript unsafe
+  "confirm($1)"
+  js_confirm :: JSString -> IO Bool
+#else
+confirmEvent = error "confirmEvent: can only be used with GHCJS"
+js_confirm = error "js_confirm: can only be used with GHCJS"
+#endif
 
 ------------------------------------------------------------------------------
 -- | Gets the current path of the DOM Window (i.e., the contents of the
 -- address bar after the host, beginning with a "/").
 -- https://developer.mozilla.org/en-US/docs/Web/API/Location
 getWindowLocationPath :: DOMWindow -> IO String
+#ifdef ghcjs_HOST_OS
 getWindowLocationPath w = do
     jw <- toJSRef w
     liftM fromJSString $ js_windowLocationPath jw
 
+foreign import javascript unsafe
+  "$1['location']['pathname']"
+  js_windowLocationPath :: JSRef DOMWindow ->  IO JSString
+#else
+getWindowLocationPath = error "getWindowLocationPath: can only be used with GHCJS"
+#endif
 
 ------------------------------------------------------------------------------
 -- | Pushes a new URL to the window history.
 windowHistoryPushState :: String -> IO ()
+#ifdef ghcjs_HOST_OS
 windowHistoryPushState = js_windowHistoryPushState . toJSString
 
+foreign import javascript unsafe
+  "window['history']['pushState']({},\"\",$1)"
+  js_windowHistoryPushState :: JSString -> IO ()
+#else
+windowHistoryPushState = error "windowHistoryPushState: can only be used with GHCJS"
+#endif
+
+setWindowLoc :: String -> IO ()
+#ifdef ghcjs_HOST_OS
+setWindowLoc = js_setWindowLoc . toJSString
+
+foreign import javascript unsafe
+  "window.location = window['location']['origin'] + $1;"
+  js_setWindowLoc :: JSString -> IO ()
+#else
+setWindowLoc = error "setWindowLoc: can only be used with GHCJS"
+#endif
 
 ------------------------------------------------------------------------------
 -- | A common form for widgetHold calls that mirrors the pattern seen in hold
diff --git a/src/Reflex/Dom/Contrib/Widgets/BoundedList.hs b/src/Reflex/Dom/Contrib/Widgets/BoundedList.hs
--- a/src/Reflex/Dom/Contrib/Widgets/BoundedList.hs
+++ b/src/Reflex/Dom/Contrib/Widgets/BoundedList.hs
@@ -31,6 +31,7 @@
 import           Reflex.Dom
 ------------------------------------------------------------------------------
 import           Reflex.Contrib.Interfaces
+import           Reflex.Contrib.Utils
 ------------------------------------------------------------------------------
 
 
@@ -87,18 +88,18 @@
 -- used items if that size is exceeded.
 boundedSelectList'
     :: (MonadWidget t m, Show k, Ord k, Show v)
-    => Event t (Map k v -> Map k v)
-    -- ^ Event that updates individual item values
-    -> Limit
+    => Limit
     -- ^ Maximum number of items to keep in the DOM at a time
     -> Dynamic t k
     -- ^ Currently selected item
+    -> Event t (Map k v -> Map k v)
+    -- ^ Event that updates individual item values
     -> ReflexMap t k v
     -- ^ Interface for updating the list
     -> (k -> Dynamic t v -> Dynamic t Bool -> m a)
     -- ^ Function to render a single item
     -> m (Dynamic t (Map k a))
-boundedSelectList' updateEvent itemLimit curSelected
+boundedSelectList' itemLimit curSelected updateEvent
                   ReflexMap{..} renderSingle = do
     -- Map holding the full item list.
     items <- foldDyn ($) rmInitialItems $ leftmost
@@ -106,20 +107,22 @@
       , rmDeleteFunc <$> rmDeleteItems
       , updateEvent
       ]
-    
+
     counter <- count $ updated curSelected
     curItem <- combineDyn findCurItem items curSelected
-    let addCounter c (k,v) = (k, ((-c), v))
+    let addCounter c (k,v) = (k, ((-c), (k, v)))
         taggedInitial = M.fromList $ zipWith addCounter [1..] $
                           M.toList rmInitialItems
     let initMap = limitMap taggedInitial itemLimit
     activeItems <- foldDyn ($) initMap $
       boundedInsert itemLimit <$>
-      attachDyn counter (fmapMaybe id $ updated curItem)
+      attachDynWith (\c (k,v) -> (c, (k, (k,v))))
+        counter (fmapMaybe id $ updated curItem)
     selectViewListWithKey curSelected activeItems wrapSingle
   where
+    --wrapSingle :: k -> Dynamic t (BornAt, (k,v)) -> Dynamic t Bool -> m a
     wrapSingle k v b = do
-        v' <- mapDyn snd v
+        v' <- mapDyn (snd . snd) =<< filterDyn (\x -> fst (snd x) == k) v
         renderSingle k v' b
 
 
@@ -130,7 +133,7 @@
 -- never deleted externally.  Instead of returning a Map of all the item
 -- results, this function only returns the result for the item that is
 -- currently selected.
-boundedSelectList
+boundedSelectList0
     :: (MonadWidget t m, Show k, Ord k, Show v)
     => Limit
     -- ^ Maximum number of items to keep in the DOM at a time
@@ -138,6 +141,7 @@
     -- ^ Currently selected item.  New items are added to the list when the
     -- currently selected item changes and the new item is not already in the
     -- list.
+    -> Event t (Map k v -> Map k v)
     -> (a -> k)
     -- ^ Gets the portion of a used as the key for the map of items
     -> (a -> Maybe a)
@@ -146,33 +150,70 @@
     -> (Event t a -> m (Event t (k,v)))
     -- ^ Gets a new key/value pair.  This function is run when curSelected
     -- changes.
-    -> b
-    -- ^ Default value to return if nothing is in the list
     -> (k -> Dynamic t v -> Dynamic t Bool -> m b)
     -- ^ Function to render a single item
-    -> m (Dynamic t b)
-boundedSelectList itemLimit curSelected getKey shouldRunExpensive
-                  expensiveGetNew defaultVal renderSingle = do
+    -> m (Dynamic t (Map k b))
+boundedSelectList0 itemLimit curSelected updateEvent getKey shouldRunExpensive
+                   expensiveGetNew renderSingle = do
     pb <- getPostBuild
-    e0 <- expensiveGetNew $ tagDyn curSelected pb
     rec
-      let insertEvent = fmapMaybe id $
-            attachDynWith isAlreadyPresent res (updated curSelected)
+      let insertEvent = leftmost
+            [ fmapMaybe id $
+                attachDynWith isAlreadyPresent res (updated curSelected)
+            , tagDyn curSelected pb
+            ]
       newVal <- expensiveGetNew insertEvent
-      let rm = ReflexMap mempty ((:[]) <$> leftmost [newVal, e0]) never
+      let rm = ReflexMap mempty ((:[]) <$> newVal) never
       curK <- mapDyn getKey curSelected
       res :: Dynamic t (Map k b) <-
-        boundedSelectList' never itemLimit curK rm renderSingle
+        boundedSelectList' itemLimit curK updateEvent rm renderSingle
+    return res
+  where
+    isAlreadyPresent fieldListMap cur =
+        case M.lookup (getKey cur) fieldListMap of
+          Nothing -> Just cur
+          Just _ -> shouldRunExpensive cur
+
+
+------------------------------------------------------------------------------
+-- | Implements a common use of boundedSelectList' where only the currently
+-- selected item from a list is displayed.  In this case a Dynamic
+-- representing the current selection is used to drive insertions and they are
+-- never deleted externally.  Instead of returning a Map of all the item
+-- results, this function only returns the result for the item that is
+-- currently selected.
+boundedSelectList
+    :: (MonadWidget t m, Show k, Ord k, Show v)
+    => Limit
+    -- ^ Maximum number of items to keep in the DOM at a time
+    -> Dynamic t a
+    -- ^ Currently selected item.  New items are added to the list when the
+    -- currently selected item changes and the new item is not already in the
+    -- list.
+    -> Event t (Map k v -> Map k v)
+    -> (a -> k)
+    -- ^ Gets the portion of a used as the key for the map of items
+    -> (a -> Maybe a)
+    -- ^ Decides whether to run expensiveGetNew in the case that the key is
+    -- already in the cache.
+    -> (Event t a -> m (Event t (k,v)))
+    -- ^ Gets a new key/value pair.  This function is run when curSelected
+    -- changes.
+    -> b
+    -- ^ Default value to return if nothing is in the list
+    -> (k -> Dynamic t v -> Dynamic t Bool -> m b)
+    -- ^ Function to render a single item
+    -> m (Dynamic t b)
+boundedSelectList itemLimit curSelected updateEvent getKey shouldRunExpensive
+                  expensiveGetNew defaultVal renderSingle = do
+    res <- boundedSelectList0 itemLimit curSelected updateEvent getKey
+                              shouldRunExpensive expensiveGetNew renderSingle
     combineDyn getCurrent curSelected res
   where
     getCurrent cur listMap =
         case M.lookup (getKey cur) listMap of
           Nothing -> defaultVal
           Just v -> v
-    isAlreadyPresent fieldListMap cur =
-        case M.lookup (getKey cur) fieldListMap of
-          Nothing -> Just cur
-          Just _ -> shouldRunExpensive cur
 
 
 ------------------------------------------------------------------------------
diff --git a/src/Reflex/Dom/Contrib/Widgets/CheckboxList.hs b/src/Reflex/Dom/Contrib/Widgets/CheckboxList.hs
--- a/src/Reflex/Dom/Contrib/Widgets/CheckboxList.hs
+++ b/src/Reflex/Dom/Contrib/Widgets/CheckboxList.hs
@@ -51,3 +51,45 @@
           mapWidget (\b -> if b then [item] else []) cb
       wconcat es
 
+
+------------------------------------------------------------------------------
+-- | Takes a list of labels to make checkboxes for and returns the labels of
+-- the boxes that are checked.
+checkboxListView
+    :: forall t m a b. (MonadWidget t m, Ord a, Show a)
+    => (a -> String)
+    -- ^ Function to show each item
+    -> (String -> a -> Bool)
+    -- ^ Function to filter each item
+    -> (a -> Bool -> b)
+    -> Event t Bool
+    -- ^ Blanket event to apply to all list items.  Allows you to have "select
+    -- all" and "select none" buttons.  Fire True to select all and False to
+    -- select none.
+    -> Dynamic t String
+    -- ^ A search string for filtering the list of items.
+    -> Set a
+    -- ^ Set of items that should be initially checked
+    -> [a]
+    -- ^ List of items to show checkboxes for
+    -> m (Event t b)
+    -- ^ Events changing the selected set
+checkboxListView showFunc filterFunc updateFunc blanketEvent searchString
+                 onItems items = do
+    el "ul" $ do
+      es <- forM items $ \item -> do
+        let shown = showFunc item
+            mkAttrs search =
+              if filterFunc search item
+                then mempty
+                else "style" =: "display:none"
+        attrs <- liftM nubDyn $ mapDyn mkAttrs searchString
+        elDynAttr "li" attrs $ el "label" $ do
+          cb <- htmlCheckbox $ WidgetConfig
+                  (leftmost [blanketEvent])
+                  (S.member item onItems)
+                  (constDyn mempty)
+          text shown
+          return $ updateFunc item <$> _hwidget_change cb
+      return $ leftmost es
+
diff --git a/src/Reflex/Dom/Contrib/Widgets/Common.hs b/src/Reflex/Dom/Contrib/Widgets/Common.hs
--- a/src/Reflex/Dom/Contrib/Widgets/Common.hs
+++ b/src/Reflex/Dom/Contrib/Widgets/Common.hs
@@ -24,12 +24,14 @@
 
 module Reflex.Dom.Contrib.Widgets.Common where
 
+import Debug.Trace
 ------------------------------------------------------------------------------
 import           Control.Lens
 import           Control.Monad
 import           Data.Default
 import           Data.Map (Map)
 import qualified Data.Map as M
+import           Data.Maybe
 import           Data.Monoid
 import           Data.Readable
 import           Data.String.Conv
@@ -37,11 +39,18 @@
 import           GHCJS.DOM.HTMLInputElement
 import           Reflex
 import           Reflex.Dom
+import           Safe
 ------------------------------------------------------------------------------
 import           Reflex.Contrib.Utils
 ------------------------------------------------------------------------------
 
 
+class HasChange a where
+  type Change a :: *
+  change :: a -> Change a
+
+
+
 ------------------------------------------------------------------------------
 -- | Generic config structure common to most widgets.  The attributes field
 -- may not be used for all widgets, but in that case it can just be ignored.
@@ -53,6 +62,10 @@
                    , _widgetConfig_attributes :: Dynamic t (Map String String)
                    }
 
+instance Reflex t => Functor (WidgetConfig t) where
+    fmap f (WidgetConfig sv iv a) = WidgetConfig (f <$> sv) (f iv) a
+
+
 makeLenses ''WidgetConfig
 
 instance (Reflex t, Default a) => Default (WidgetConfig t a) where
@@ -70,8 +83,70 @@
   setValue = widgetConfig_setValue
 
 
+class IsWidget w where
+  ----------------------------------------------------------------------------
+  -- | HtmlWidget with a constant value that never fires any events.
+  constWidget :: Reflex t => a -> w t a
+
+  ----------------------------------------------------------------------------
+  -- | We can't make a Functor instance until Dynamic gets a Functor instance.
+  mapWidget :: MonadWidget t m => (a -> b) -> w t a -> m (w t b)
+
+
+  combineWidgets :: MonadWidget t m => (a -> b -> c) -> w t a -> w t b -> m (w t c)
+
+  ----------------------------------------------------------------------------
+  -- | Combines multiple widgets over a Monoid operation.
+  wconcat :: (MonadWidget t m, Foldable f, Monoid a) => f (w t a) -> m (w t a)
+  wconcat = foldM (combineWidgets (<>)) (constWidget mempty)
+
+  ----------------------------------------------------------------------------
+  -- | Since widgets contain Dynamics and Events inside them, we can pull
+  -- Dynamic widgets out of the Dynamic.
+  extractWidget :: MonadWidget t m => Dynamic t (w t a) -> m (w t a)
+
+
 ------------------------------------------------------------------------------
 -- | A general-purpose widget return value.
+data Widget0 t a = Widget0
+    { _widget0_value    :: Dynamic t a
+      -- ^ The authoritative value for this widget.
+    , _widget0_change   :: Event t a
+      -- ^ Event that fires when the widget changes internally (not via a
+      -- setValue event).
+    }
+
+makeLenses ''Widget0
+
+
+instance HasValue (Widget0 t a) where
+  type Value (Widget0 t a) = Dynamic t a
+  value = _widget0_value
+
+
+instance HasChange (Widget0 t a) where
+  type Change (Widget0 t a) = Event t a
+  change = _widget0_change
+
+
+instance IsWidget Widget0 where
+  constWidget a = Widget0 (constDyn a) never
+  mapWidget f w = do
+    b <- mapDyn f $ value w
+    return $ Widget0 b (f <$> _widget0_change w)
+  combineWidgets f a b = do
+    c <- combineDyn f (value a) (value b)
+    let cChange = tagDyn c $ leftmost
+          [() <$ _widget0_change a, () <$ _widget0_change b]
+    return $ Widget0 c cChange
+  extractWidget dw = do
+    v <- extractDyn value dw
+    c <- extractEvent _widget0_change dw
+    return $ Widget0 v c
+
+
+------------------------------------------------------------------------------
+-- | A general-purpose widget return value.
 data HtmlWidget t a = HtmlWidget
     { _hwidget_value    :: Dynamic t a
       -- ^ The authoritative value for this widget.
@@ -92,74 +167,43 @@
   value = _hwidget_value
 
 
-------------------------------------------------------------------------------
--- | Generalized form of many widget functions.
-type GWidget t m a = WidgetConfig t a -> m (HtmlWidget t a)
-
-
-------------------------------------------------------------------------------
--- | HtmlWidget with a constant value that never fires any events and does not
--- have focus.
-constWidget :: Reflex t => a -> HtmlWidget t a
-constWidget a = HtmlWidget (constDyn a) never never never never (constDyn False)
-
-
-------------------------------------------------------------------------------
--- | We can't make a Functor instance for HtmlWidget until Dynamic gets a
--- Functor instance.  So until then, this will have to do.
-mapWidget
-    :: MonadWidget t m
-    => (a -> b)
-    -> HtmlWidget t a
-    -> m (HtmlWidget t b)
-mapWidget f w = do
-    newVal <- mapDyn f $ value w
-    return $ HtmlWidget
-      newVal
-      (f <$> _hwidget_change w)
-      (_hwidget_keypress w)
-      (_hwidget_keydown w)
-      (_hwidget_keyup w)
-      (_hwidget_hasFocus w)
+instance HasChange (HtmlWidget t a) where
+  type Change (HtmlWidget t a) = Event t a
+  change = _hwidget_change
 
 
-------------------------------------------------------------------------------
--- | Combine function does the expected thing for _value and _change and
--- applies leftmost to each of the widget events.
-combineWidgets
-    :: MonadWidget t m
-    => (a -> b -> c)
-    -> HtmlWidget t a
-    -> HtmlWidget t b
-    -> m (HtmlWidget t c)
-combineWidgets f a b = do
-    newVal <- combineDyn f (value a) (value b)
-    let newChange = tag (current newVal) $ leftmost
-          [() <$ _hwidget_change a, () <$ _hwidget_change b]
-    newFocus <- combineDyn (||) (_hwidget_hasFocus a) (_hwidget_hasFocus b)
-    return $ HtmlWidget
-      newVal newChange
-      (leftmost [_hwidget_keypress a, _hwidget_keypress b])
-      (leftmost [_hwidget_keydown a, _hwidget_keydown b])
-      (leftmost [_hwidget_keyup a, _hwidget_keyup b])
-      newFocus
+htmlTo0 :: HtmlWidget t a -> Widget0 t a
+htmlTo0 w = Widget0 (_hwidget_value w) (_hwidget_change w)
 
 
 ------------------------------------------------------------------------------
--- | Combines multiple widgets over a Monoid operation.
-wconcat
-    :: (MonadWidget t m, Foldable f, Monoid a)
-    => f (HtmlWidget t a) -> m (HtmlWidget t a)
-wconcat = foldM (combineWidgets (<>)) (constWidget mempty)
+-- | Generalized form of many widget functions.
+type GWidget t m a = WidgetConfig t a -> m (HtmlWidget t a)
 
 
-------------------------------------------------------------------------------
--- | Convenience for extracting HtmlWidget from a Dynamic.
-extractWidget
-    :: MonadWidget t m
-    => Dynamic t (HtmlWidget t a)
-    -> m (HtmlWidget t a)
-extractWidget dynWidget = do
+instance IsWidget HtmlWidget where
+  constWidget a = HtmlWidget (constDyn a) never never never never (constDyn False)
+  mapWidget f w = do
+      newVal <- mapDyn f $ value w
+      return $ HtmlWidget
+        newVal
+        (f <$> _hwidget_change w)
+        (_hwidget_keypress w)
+        (_hwidget_keydown w)
+        (_hwidget_keyup w)
+        (_hwidget_hasFocus w)
+  combineWidgets f a b = do
+      newVal <- combineDyn f (value a) (value b)
+      let newChange = tagDyn newVal $ leftmost
+            [() <$ _hwidget_change a, () <$ _hwidget_change b]
+      newFocus <- combineDyn (||) (_hwidget_hasFocus a) (_hwidget_hasFocus b)
+      return $ HtmlWidget
+        newVal newChange
+        (leftmost [_hwidget_keypress a, _hwidget_keypress b])
+        (leftmost [_hwidget_keydown a, _hwidget_keydown b])
+        (leftmost [_hwidget_keyup a, _hwidget_keyup b])
+        newFocus
+  extractWidget dynWidget = do
     v <- extractDyn value dynWidget
     c <- extractEvent _hwidget_change dynWidget
     kp <- extractEvent _hwidget_keypress dynWidget
@@ -300,6 +344,59 @@
 
 
 ------------------------------------------------------------------------------
+-- | Dropdown widget that takes a dynamic list of items and a function
+-- generating a String representation of those items.
+htmlDropdown
+    :: (MonadWidget t m, Eq b)
+    => Dynamic t [a]
+    -> (a -> String)
+    -> (a -> b)
+    -> WidgetConfig t b
+    -> m (Widget0 t b)
+htmlDropdown items f payload cfg = do
+    pairs <- mapDyn (zip [(0::Int)..]) items
+    m <- mapDyn M.fromList pairs
+    dynItems <- mapDyn (M.map f) m
+    let findIt ps a = maybe 0 fst $ headMay (filter (\ (_,x) -> payload x == a) ps)
+    let setVal = attachDynWith findIt pairs $ _widgetConfig_setValue cfg
+    d <- dropdown 0 dynItems $
+           DropdownConfig setVal (_widgetConfig_attributes cfg)
+    val <- combineDyn (\k x -> payload $ fromJust $ M.lookup k x) (_dropdown_value d) m
+    return $ Widget0 val (tagDyn val $ _dropdown_change d)
+
+
+------------------------------------------------------------------------------
+-- | Dropdown widget that takes a list of items and a function generating a
+-- String representation of those items.
+--
+-- This widget doesn't require your data type to have Read and Show instances
+-- like reflex-dom's dropdown function.  It does this by using Int indices
+-- into your static list of items in the actual rendered dropdown element.
+--
+-- But this comes with a price--it has unexpected behavior under insertions,
+-- deletions, and reorderings of the list of options.  Because of this, you
+-- should probably only use this for static dropdowns where the list of
+-- options never changes.
+htmlDropdownStatic
+    :: (MonadWidget t m, Eq b)
+    => [a]
+    -> (a -> String)
+    -> (a -> b)
+    -> WidgetConfig t b
+    -> m (Widget0 t b)
+htmlDropdownStatic items f payload cfg = do
+    let pairs = zip [(0::Int)..] items
+        m = M.fromList pairs
+        dynItems = M.map f m
+    let findIt a = maybe 0 fst $ headMay (filter (\ (_,x) -> payload x == a) pairs)
+    let setVal = findIt <$> _widgetConfig_setValue cfg
+    d <- dropdown (findIt $ _widgetConfig_initialValue cfg) (constDyn dynItems) $
+           DropdownConfig setVal (_widgetConfig_attributes cfg)
+    val <- mapDyn (\k -> payload $ fromJust $ M.lookup k m) (_dropdown_value d)
+    return $ Widget0 val (tagDyn val $ _dropdown_change d)
+
+
+------------------------------------------------------------------------------
 -- | Returns an event that fires when the widget loses focus or enter is
 -- pressed.
 blurOrEnter
@@ -311,6 +408,24 @@
     fireEvent = leftmost [ () <$ (ffilter (==13) $ _hwidget_keypress w)
                          , () <$ (ffilter not $ updated $ _hwidget_hasFocus w)
                          ]
+
+
+------------------------------------------------------------------------------
+-- | Allows you to restrict when a widget fires.  For instance,
+-- @restrictWidget blurOrEnter@ restricts a widget so it only fires on blur
+-- or when enter is pressed.
+restrictWidget
+    :: MonadWidget t m
+    => (HtmlWidget t a -> Event t a)
+    -> GWidget t m a
+    -> GWidget t m a
+restrictWidget restrictFunc wFunc cfg = do
+    w <- wFunc cfg
+    let e = restrictFunc w
+    v <- holdDyn (_widgetConfig_initialValue cfg) e
+    return $ w { _hwidget_value = v
+               , _hwidget_change = e
+               }
 
 
 ------------------------------------------------------------------------------
diff --git a/src/Reflex/Dom/Contrib/Widgets/DynTabs.hs b/src/Reflex/Dom/Contrib/Widgets/DynTabs.hs
--- a/src/Reflex/Dom/Contrib/Widgets/DynTabs.hs
+++ b/src/Reflex/Dom/Contrib/Widgets/DynTabs.hs
@@ -29,6 +29,7 @@
 ------------------------------------------------------------------------------
 import qualified Data.Map as M
 import           Data.Monoid
+import qualified Data.Set as S
 import           Reflex
 import           Reflex.Dom
 ------------------------------------------------------------------------------
@@ -37,7 +38,7 @@
 
 
 ------------------------------------------------------------------------------
-class Eq tab => Tab m tab where
+class (Eq tab, Ord tab) => Tab m tab where
     tabIndicator :: tab -> m ()
 
 
@@ -52,11 +53,13 @@
     -- ^ Dynamic list of the displayed tabs
     -> Event t tab
     -- ^ Event updating the currently selected tab
+    -> Dynamic t (S.Set tab)
+    -- ^ Set containing tabs that are currently disabled
     -> m (Dynamic t tab)
-tabBar tabClass initialSelected initialTabs tabs curTab = do
+tabBar tabClass initialSelected initialTabs tabs curTab disabledTabs = do
     divClass "dyn-tab-bar" $ do
       elAttr "ul" ("class" =: tabClass) $ do
-        rec let tabFunc = mapM (mkTab currentTab)
+        rec let tabFunc = mapM (mkTab currentTab disabledTabs)
             foo <- widgetHoldHelper tabFunc initialTabs tabs
             let bar :: Event t tab = switch $ fmap leftmost $ current foo
             currentTab <- holdDyn initialSelected $ leftmost [bar, curTab]
@@ -67,12 +70,14 @@
 mkTab
   :: (MonadWidget t m, Tab m tab)
   => Dynamic t tab
+  -> Dynamic t (S.Set tab)
   -> tab
   -> m (Event t tab)
-mkTab currentTab t = do
+mkTab currentTab disabledTabs t = do
     isSelected <- mapDyn (==t) currentTab
-    e <- activeHelper "li" (el "a" $ tabIndicator t) isSelected
-    return (t <$ e)
+    isDisabled <- mapDyn (S.member t) disabledTabs
+    e <- activeHelper "li" (el "a" $ tabIndicator t) isSelected isDisabled
+    return $ gate (not <$> current isDisabled) (t <$ e)
 
 
 ------------------------------------------------------------------------------
@@ -93,10 +98,18 @@
   => String
   -> m ()
   -> Dynamic t Bool
+  -- ^ Is selected
+  -> Dynamic t Bool
+  -- ^ Is disabled
   -> m (Event t ())
-activeHelper elName children isSelected = do
-    attrs <- forDyn isSelected $ \selected ->
-      if selected then "class" =: "active" else M.empty
+activeHelper elName children isSelected isDisabled = do
+    let mkAttrs selected disabled =
+          if disabled
+            then "class" =: "disabled"
+            else if selected
+                   then "class" =: "active"
+                   else M.empty
+    attrs <- combineDyn mkAttrs isSelected isDisabled
     (li, _) <- elDynAttr' elName attrs children
-    return $ _el_clicked li
+    return $ domEvent Click li
 
diff --git a/src/Reflex/Dom/Contrib/Widgets/EditInPlace.hs b/src/Reflex/Dom/Contrib/Widgets/EditInPlace.hs
new file mode 100644
--- /dev/null
+++ b/src/Reflex/Dom/Contrib/Widgets/EditInPlace.hs
@@ -0,0 +1,131 @@
+{-# LANGUAGE ConstraintKinds           #-}
+{-# LANGUAGE FlexibleContexts          #-}
+{-# LANGUAGE LambdaCase                #-}
+{-# LANGUAGE MultiWayIf                #-}
+{-# LANGUAGE NoMonomorphismRestriction #-}
+{-# LANGUAGE OverloadedStrings         #-}
+{-# LANGUAGE RankNTypes                #-}
+{-# LANGUAGE RecordWildCards           #-}
+{-# LANGUAGE RecursiveDo               #-}
+{-# LANGUAGE ScopedTypeVariables       #-}
+{-# LANGUAGE TemplateHaskell           #-}
+{-# LANGUAGE TupleSections             #-}
+{-# LANGUAGE TypeFamilies              #-}
+
+module Reflex.Dom.Contrib.Widgets.EditInPlace
+  ( editInPlace
+  ) where
+
+------------------------------------------------------------------------------
+import           Control.Lens hiding ((.=))
+import           Control.Monad.Trans
+import           Data.Default
+import           Data.Map (Map)
+import           GHCJS.DOM.Element
+import           Reflex
+import           Reflex.Dom
+import           Reflex.Dom.Contrib.Utils
+import           Reflex.Dom.Contrib.Widgets.Common
+------------------------------------------------------------------------------
+
+
+------------------------------------------------------------------------------
+data EditState = Viewing
+               | Editing
+  deriving (Eq,Show,Ord,Enum)
+
+
+------------------------------------------------------------------------------
+-- | Control that can be used in place of dynText whenever you also want that
+-- text to be editable in place.
+--
+-- This control creates a span that either holds text or a text input field
+-- allowing that text to be edited.  The edit state is activated by clicking
+-- on the text.  Edits are saved when the user presses enter or abandoned if
+-- the user presses escape or the text input loses focus.
+editInPlace
+    :: MonadWidget t m
+    => Behavior t Bool
+    -- ^ Whether or not click-to-edit is enabled
+    -> Dynamic t String
+    -- ^ The definitive value of the thing being edited
+    -> m (Event t String)
+    -- ^ Event that fires when the text is edited
+editInPlace active val = do
+    rec editState <- holdDyn Viewing $ leftmost
+          [ fmapMaybe id $ attachWith
+              (\c n -> if c == Editing then Nothing else Just n)
+              (current editState) startEditing
+          , Viewing <$ sheetEdit
+          ]
+        cls <- mapDyn mkClass editState
+        (e, sheetEdit) <- elDynAttr' "span" cls $ do
+          de <- widgetHoldHelper (chooser val) Viewing (updated editState)
+          return $ switch $ current de
+        let selActive = tag active $ domEvent Click e
+        let startEditing = fmapMaybe id $
+              (\a -> if a then Just Editing else Nothing) <$> selActive
+    return $ fmapMaybe e2maybe sheetEdit
+
+
+------------------------------------------------------------------------------
+mkClass :: EditState -> Map String String
+mkClass es = "class" =: (unwords ["editInPlace", ev])
+  where
+    ev = case es of
+           Viewing -> "viewing"
+           Editing -> "editing"
+
+
+------------------------------------------------------------------------------
+e2maybe :: SheetEditEvent -> Maybe String
+e2maybe EditClose = Nothing
+e2maybe (NameChange s) = Just s
+
+
+------------------------------------------------------------------------------
+chooser
+    :: MonadWidget t m
+    => Dynamic t String
+    -> EditState
+    -> m (Event t SheetEditEvent)
+chooser name Editing = editor name
+chooser name Viewing = viewer name
+
+
+------------------------------------------------------------------------------
+data SheetEditEvent = NameChange String
+                    | EditClose
+  deriving (Read,Show,Eq,Ord)
+
+
+------------------------------------------------------------------------------
+editor
+    :: MonadWidget t m
+    => Dynamic t String
+    -> m (Event t SheetEditEvent)
+editor name = do
+  pb <- getPostBuild
+  (e,w) <- htmlTextInput' "text" $
+    def & widgetConfig_setValue .~ tagDyn name pb
+  performEvent_ $ ffor pb $ \_ -> do
+    liftIO $ elementFocus e
+  let acceptEvent = leftmost
+        [ () <$ ffilter (==13) (_hwidget_keypress w)
+        , () <$ ffilter not (updated $ _hwidget_hasFocus w)
+        ]
+  return $ leftmost
+    [ NameChange <$> tag (current $ value w) acceptEvent
+    , EditClose <$ ffilter (==27) (_hwidget_keydown w)
+    ]
+
+
+------------------------------------------------------------------------------
+viewer
+    :: MonadWidget t m
+    => Dynamic t String
+    -> m (Event t SheetEditEvent)
+viewer name = do
+  dynText name
+  return never
+
