diff --git a/include/ajax.forms.php b/include/ajax.forms.php
index 69da6ec54787e352ce32244a8c7e5f176faa941a..0e440573c8532e544afef4cc9192e8c809430eb8 100644
--- a/include/ajax.forms.php
+++ b/include/ajax.forms.php
@@ -206,6 +206,38 @@ class DynamicFormsAjaxAPI extends AjaxController {
         )));
     }
 
+    function searchListItems($list_id) {
+        global $thisstaff;
+
+        if (!$thisstaff)
+            Http::response(403, 'Login required');
+        elseif (!($list = DynamicList::lookup($list_id)))
+            Http::response(404, 'No such list');
+        elseif (!($q = $_GET['q']))
+            Http::response(400, '"q" query arg is required');
+
+        $items = clone $list->getAllItems();
+        $items->filter(Q::any(array(
+            'value__startswith' => $q,
+            'extra__contains' => $q,
+            'properties__contains' => '"'.$q,
+        )));
+
+        $results = array();
+        foreach ($items as $I) {
+            $display = $I->value;
+            if ($I->extra)
+              $display .= " ({$I->extra})";
+            $results[] = array(
+                'value' => $I->value,
+                'display' => $display,
+                'id' => $I->id,
+                'list_id' => $I->list_id,
+            );
+        }
+        return $this->encode($results);
+    }
+
     function addListItem($list_id) {
         global $thisstaff;
 
diff --git a/include/class.list.php b/include/class.list.php
index 82142b90ea86c50b822c926512284fed8a43b739..b6c8c7327147441b74357fcac7e479193769f239 100644
--- a/include/class.list.php
+++ b/include/class.list.php
@@ -135,6 +135,11 @@ class DynamicList extends VerySimpleModel implements CustomList {
         'table' => LIST_TABLE,
         'ordering' => array('name'),
         'pk' => array('id'),
+        'joins' => array(
+            'items' => array(
+                'reverse' => 'DynamicListItem.list',
+            ),
+        ),
     );
 
     // Required fields
diff --git a/include/staff/templates/list-item-properties.tmpl.php b/include/staff/templates/list-item-properties.tmpl.php
index 61ef1b8f6ba3c0d25c3cfed5ceefd3040a1b8f18..35895173e86bc61d1d864a781360a90abe737f9f 100644
--- a/include/staff/templates/list-item-properties.tmpl.php
+++ b/include/staff/templates/list-item-properties.tmpl.php
@@ -1,8 +1,14 @@
+<?php
+    $properties_form = $item ? $item->getConfigurationForm($_POST ?: null)
+        : $list->getConfigurationForm();
+    $hasProperties = count($properties_form->getFields()) > 0;
+?>
 <h3 class="drag-handle"><?php echo $list->getName(); ?> &mdash; <?php
     echo $item ? $item->getValue() : __('Add New List Item'); ?></h3>
 <a class="close" href=""><i class="icon-remove-circle"></i></a>
 <hr/>
 
+<?php if ($hasProperties) { ?>
 <ul class="tabs" id="item_tabs">
     <li class="active">
         <a href="#value"><i class="icon-reorder"></i>
@@ -12,6 +18,7 @@
         <?php echo __('Item Properties'); ?></a>
     </li>
 </ul>
+<?php } ?>
 
 <form method="post" id="item_tabs_container" action="<?php echo $action; ?>">
     <?php
@@ -28,9 +35,10 @@
 
 <div class="tab_content hidden" id="properties">
 <?php
-    $form = $item ? $item->getConfigurationForm($_POST ?: null)
-        : $list->getConfigurationForm();
-    include 'dynamic-form-simple.tmpl.php';
+    if ($hasProperties) {
+        $form = $properties_form;
+        include 'dynamic-form-simple.tmpl.php';
+    }
 ?>
 </div>
 
diff --git a/include/staff/templates/list-items.tmpl.php b/include/staff/templates/list-items.tmpl.php
index c1df5299910bdd514d015b4f163df6c089fa76bc..a2dbb1630e683ffd794b021a45fcfc2f87deceb6 100644
--- a/include/staff/templates/list-items.tmpl.php
+++ b/include/staff/templates/list-items.tmpl.php
@@ -13,19 +13,10 @@
     ?>
     <div style="margin: 5px 0">
     <div class="pull-left">
-        <input type="search" size="25" id="search" value="<?php
+        <input type="text" placeholder="<?php echo __('Search items'); ?>"
+            data-url="ajax.php/list/<?php echo $list->getId(); ?>/items/search"
+            size="25" id="items-search" value="<?php
             echo Format::htmlchars($_POST['search']); ?>"/>
-        <button type="submit" onclick="javascript:
-            event.preventDefault();
-            $.pjax({type: 'POST', data: { search: $('#search').val() }, container: '#pjax-container'});
-            return false;
-"><?php echo __('Search'); ?></button>
-        <?php if ($_POST['search']) { ?>
-        <a href="#" onclick="javascript:
-            $.pjax.reload('#pjax-container'); return false; "
-            ><i class="icon-remove-sign"></i> <?php
-                echo __('clear'); ?></a>
-        <?php } ?>
     </div>
     <?php if ($list) { ?>
     <div class="pull-right">
@@ -102,14 +93,6 @@ if ($list) {
             $icon = ($list->get('sort_mode') == 'SortCol')
                 ? '<i class="icon-sort"></i>&nbsp;' : '';
             $items = $list->getAllItems();
-            if ($_POST['search']) {
-                $items->filter(Q::any(array(
-                    'value__contains'=>$_POST['search'],
-                    'extra__contains'=>$_POST['search'],
-                    'properties__contains'=>$_POST['search'],
-                )));
-                $search = true;
-            }
             $items = $pageNav->paginate($items);
             // Emit a marker for the first sort offset ?>
             <input type="hidden" id="sort-offset" value="<?php echo
@@ -125,3 +108,39 @@ if ($list) {
     <div><?php echo __('Page').':'.$pageNav->getPageLinks('items', $pjax_container); ?></div>
 <?php } ?>
 </div>
+<script type="text/javascript">
+$(function() {
+  var last_req;
+  $('input#items-search').typeahead({
+    source: function (typeahead, query) {
+      if (last_req)
+        last_req.abort();
+      var $el = this.$element;
+      var url = $el.data('url')+'?q='+query;
+      last_req = $.ajax({
+        url: url,
+        dataType: 'json',
+        success: function (data) {
+          typeahead.process(data);
+        }
+      });
+    },
+    onselect: function (obj) {
+      var $el = this.$element,
+          url = 'ajax.php/list/{0}/item/{1}/update'
+            .replace('{0}', obj.list_id)
+            .replace('{1}', obj.id);
+      $.dialog(url, [201], function (xhr, resp) {
+        var json = $.parseJSON(resp);
+        if (json && json.success) {
+          if (json.id && json.row) {
+            $('#list-item-' + json.id).replaceWith(json.row);
+          }
+        }
+      });
+      this.$element.val('');
+    },
+    property: "display"
+  });
+});
+</script>
diff --git a/scp/ajax.php b/scp/ajax.php
index 6971008a6eeafa87e576ec48f01c205d207c6332..c2ff371d5aa6a898a05036bc23d9c20f3221f565 100644
--- a/scp/ajax.php
+++ b/scp/ajax.php
@@ -68,6 +68,7 @@ $dispatcher = patterns('',
     )),
     url('^/list/', patterns('ajax.forms.php:DynamicFormsAjaxAPI',
         url_get('^(?P<list>\w+)/items$', 'getListItems'),
+        url_get('^(?P<list>\w+)/items/search$', 'searchListItems'),
         url_get('^(?P<list>\w+)/item/(?P<id>\d+)/update$', 'getListItem'),
         url_post('^(?P<list>\w+)/item/(?P<id>\d+)/update$', 'saveListItem'),
         url('^(?P<list>\w+)/item/add$', 'addListItem'),