diff --git a/resources/celerity/map.php b/resources/celerity/map.php
--- a/resources/celerity/map.php
+++ b/resources/celerity/map.php
@@ -9,7 +9,7 @@
   'names' => array(
     'conpherence.pkg.css' => 'e68cf1fa',
     'conpherence.pkg.js' => '15191c65',
-    'core.pkg.css' => 'fdb27ef9',
+    'core.pkg.css' => '5be8063f',
     'core.pkg.js' => '4c79d74f',
     'darkconsole.pkg.js' => '1f9a31bc',
     'differential.pkg.css' => '45951e9e',
@@ -135,14 +135,14 @@
     'rsrc/css/phui/object-item/phui-oi-color.css' => 'cd2b9b77',
     'rsrc/css/phui/object-item/phui-oi-drag-ui.css' => '08f4ccc3',
     'rsrc/css/phui/object-item/phui-oi-flush-ui.css' => '9d9685d6',
-    'rsrc/css/phui/object-item/phui-oi-list-view.css' => 'bf094950',
+    'rsrc/css/phui/object-item/phui-oi-list-view.css' => '73c5f5c4',
     'rsrc/css/phui/object-item/phui-oi-simple-ui.css' => 'a8beebea',
     'rsrc/css/phui/phui-action-list.css' => 'f7f61a34',
     'rsrc/css/phui/phui-action-panel.css' => 'b4798122',
     'rsrc/css/phui/phui-badge.css' => '22c0cf4f',
     'rsrc/css/phui/phui-basic-nav-view.css' => '98c11ab3',
     'rsrc/css/phui/phui-big-info-view.css' => 'acc3492c',
-    'rsrc/css/phui/phui-box.css' => '9f3745fb',
+    'rsrc/css/phui/phui-box.css' => '4bd6cdb9',
     'rsrc/css/phui/phui-chart.css' => '6bf6f78e',
     'rsrc/css/phui/phui-cms.css' => '504b4b23',
     'rsrc/css/phui/phui-comment-form.css' => 'ac68149f',
@@ -523,6 +523,7 @@
     'rsrc/js/core/phtize.js' => 'd254d646',
     'rsrc/js/phui/behavior-phui-dropdown-menu.js' => 'b95d6f7d',
     'rsrc/js/phui/behavior-phui-file-upload.js' => 'b003d4fb',
+    'rsrc/js/phui/behavior-phui-selectable-list.js' => '464259a2',
     'rsrc/js/phui/behavior-phui-submenu.js' => 'a6f7a73b',
     'rsrc/js/phui/behavior-phui-tab-group.js' => '0a0b10e9',
     'rsrc/js/phuix/PHUIXActionListView.js' => 'b5c256b8',
@@ -673,6 +674,7 @@
     'javelin-behavior-phui-dropdown-menu' => 'b95d6f7d',
     'javelin-behavior-phui-file-upload' => 'b003d4fb',
     'javelin-behavior-phui-hovercards' => 'bcaccd64',
+    'javelin-behavior-phui-selectable-list' => '464259a2',
     'javelin-behavior-phui-submenu' => 'a6f7a73b',
     'javelin-behavior-phui-tab-group' => '0a0b10e9',
     'javelin-behavior-phuix-example' => '68af71ca',
@@ -820,7 +822,7 @@
     'phui-badge-view-css' => '22c0cf4f',
     'phui-basic-nav-view-css' => '98c11ab3',
     'phui-big-info-view-css' => 'acc3492c',
-    'phui-box-css' => '9f3745fb',
+    'phui-box-css' => '4bd6cdb9',
     'phui-button-bar-css' => 'f1ff5494',
     'phui-button-css' => '1863cc6e',
     'phui-button-simple-css' => '8e1baf68',
@@ -860,7 +862,7 @@
     'phui-oi-color-css' => 'cd2b9b77',
     'phui-oi-drag-ui-css' => '08f4ccc3',
     'phui-oi-flush-ui-css' => '9d9685d6',
-    'phui-oi-list-view-css' => 'bf094950',
+    'phui-oi-list-view-css' => '73c5f5c4',
     'phui-oi-simple-ui-css' => 'a8beebea',
     'phui-pager-css' => 'edcbc226',
     'phui-pinboard-view-css' => '2495140e',
@@ -1226,6 +1228,11 @@
       'javelin-behavior',
       'javelin-dom',
     ),
+    '464259a2' => array(
+      'javelin-behavior',
+      'javelin-stratcom',
+      'javelin-dom',
+    ),
     '469c0d9e' => array(
       'javelin-behavior',
       'javelin-dom',
diff --git a/src/applications/maniphest/controller/ManiphestBatchEditController.php b/src/applications/maniphest/controller/ManiphestBatchEditController.php
--- a/src/applications/maniphest/controller/ManiphestBatchEditController.php
+++ b/src/applications/maniphest/controller/ManiphestBatchEditController.php
@@ -89,12 +89,7 @@
         ->setURI($job->getMonitorURI());
     }
 
-    $handles = ManiphestTaskListView::loadTaskHandles($viewer, $tasks);
-
-    $list = new ManiphestTaskListView();
-    $list->setTasks($tasks);
-    $list->setUser($viewer);
-    $list->setHandles($handles);
+    $list = $this->newBulkObjectList($tasks);
 
     $template = new AphrontTokenizerTemplateView();
     $template = $template->render();
@@ -142,21 +137,8 @@
         'statusMap'   => ManiphestTaskStatus::getTaskStatusMap(),
       ));
 
-    $form = id(new AphrontFormView())
-      ->setUser($viewer)
-      ->addHiddenInput('board', $board_id)
-      ->setID('maniphest-batch-edit-form');
-
-    foreach ($tasks as $task) {
-      $form->appendChild(
-        phutil_tag(
-          'input',
-          array(
-            'type' => 'hidden',
-            'name' => 'batch[]',
-            'value' => $task->getID(),
-          )));
-    }
+    $form = id(new PHUIFormLayoutView())
+      ->setUser($viewer);
 
     $form->appendChild(
       phutil_tag(
@@ -166,6 +148,7 @@
           'name' => 'actions',
           'id'   => 'batch-form-actions',
         )));
+
     $form->appendChild(
       id(new PHUIFormInsetView())
         ->setTitle(pht('Actions'))
@@ -210,17 +193,63 @@
       ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
       ->setForm($form);
 
-    $view = id(new PHUITwoColumnView())
-      ->setHeader($header)
-      ->setFooter(array(
+
+    $complete_form = phabricator_form(
+      $viewer,
+      array(
+        'action' => $request->getRequestURI(),
+        'method' => 'POST',
+        'id' => 'maniphest-batch-edit-form',
+      ),
+      array(
+        phutil_tag(
+          'input',
+          array(
+            'type' => 'hidden',
+            'name' => 'board',
+            'value' => $board_id,
+          )),
         $task_box,
         $form_box,
       ));
 
+    $view = id(new PHUITwoColumnView())
+      ->setHeader($header)
+      ->setFooter($complete_form);
+
     return $this->newPage()
       ->setTitle($title)
       ->setCrumbs($crumbs)
       ->appendChild($view);
   }
 
+  private function newBulkObjectList(array $objects) {
+    $viewer = $this->getViewer();
+    $objects = mpull($objects, null, 'getPHID');
+
+    $handles = $viewer->loadHandles(array_keys($objects));
+
+    $status_closed = PhabricatorObjectHandle::STATUS_CLOSED;
+
+    $list = id(new PHUIObjectItemListView())
+      ->setViewer($viewer)
+      ->setFlush(true);
+
+    foreach ($objects as $phid => $object) {
+      $handle = $handles[$phid];
+
+      $is_closed = ($handle->getStatus() === $status_closed);
+
+      $item = id(new PHUIObjectItemView())
+        ->setHeader($handle->getFullName())
+        ->setHref($handle->getURI())
+        ->setDisabled($is_closed)
+        ->setSelectable('batch[]', $object->getID(), true);
+
+      $list->addItem($item);
+    }
+
+    return $list;
+  }
+
 }
diff --git a/src/view/phui/PHUIObjectItemView.php b/src/view/phui/PHUIObjectItemView.php
--- a/src/view/phui/PHUIObjectItemView.php
+++ b/src/view/phui/PHUIObjectItemView.php
@@ -29,6 +29,10 @@
   private $coverImage;
   private $description;
 
+  private $selectableName;
+  private $selectableValue;
+  private $isSelected;
+
   public function setDisabled($disabled) {
     $this->disabled = $disabled;
     return $this;
@@ -160,6 +164,13 @@
     return $this;
   }
 
+  public function setSelectable($name, $value, $is_selected) {
+    $this->selectableName = $name;
+    $this->selectableValue = $value;
+    $this->isSelected = $is_selected;
+    return $this;
+  }
+
   public function setEpoch($epoch) {
     $date = phabricator_datetime($epoch, $this->getUser());
     $this->addIcon('none', $date);
@@ -239,6 +250,8 @@
   }
 
   protected function getTagAttributes() {
+    $sigils = array();
+
     $item_classes = array();
     $item_classes[] = 'phui-oi';
 
@@ -286,6 +299,17 @@
         throw new Exception(pht('Invalid effect!'));
     }
 
+    if ($this->isSelected) {
+      $item_classes[] = 'phui-oi-selected';
+    }
+
+    if ($this->selectableName !== null) {
+      $item_classes[] = 'phui-oi-selectable';
+      $sigils[] = 'phui-oi-selectable';
+
+      Javelin::initBehavior('phui-selectable-list');
+    }
+
     if ($this->getGrippable()) {
       $item_classes[] = 'phui-oi-grippable';
     }
@@ -300,6 +324,7 @@
 
     return array(
       'class' => $item_classes,
+      'sigil' => $sigils,
     );
   }
 
@@ -628,6 +653,24 @@
         $countdown);
     }
 
+    if ($this->selectableName !== null) {
+      $checkbox = phutil_tag(
+        'input',
+        array(
+          'type' => 'checkbox',
+          'name' => $this->selectableName,
+          'value' => $this->selectableValue,
+          'checked' => ($this->isSelected ? 'checked' : null),
+        ));
+
+      $column0 = phutil_tag(
+        'div',
+        array(
+          'class' => 'phui-oi-col0 phui-oi-checkbox',
+        ),
+        $checkbox);
+    }
+
     $column1 = phutil_tag(
       'div',
       array(
diff --git a/webroot/rsrc/css/phui/object-item/phui-oi-list-view.css b/webroot/rsrc/css/phui/object-item/phui-oi-list-view.css
--- a/webroot/rsrc/css/phui/object-item/phui-oi-list-view.css
+++ b/webroot/rsrc/css/phui/object-item/phui-oi-list-view.css
@@ -664,3 +664,22 @@
   padding: 0 8px 8px;
   text-align: left;
 }
+
+.phui-oi-col0.phui-oi-checkbox {
+  width: 28px;
+  text-align: center;
+}
+
+.phui-oi-selectable {
+  cursor: pointer;
+  user-select: none;
+  -webkit-user-select: none;
+}
+
+/* When the list selection state can be toggled on the client (as in the bulk
+  editor), keep the border color consistent to make the interaction feel more
+  robust. */
+ul.phui-oi-list-view .phui-oi-selectable
+  .phui-oi-frame {
+  border-color: {$blueborder};
+}
diff --git a/webroot/rsrc/css/phui/phui-box.css b/webroot/rsrc/css/phui/phui-box.css
--- a/webroot/rsrc/css/phui/phui-box.css
+++ b/webroot/rsrc/css/phui/phui-box.css
@@ -103,6 +103,10 @@
   padding: 2px 8px;
 }
 
+.phui-box-blue-property .phui-oi-list-view.phui-oi-list-flush {
+  padding: 0;
+}
+
 body .phui-box-blue-property.phui-object-box.phui-object-box-collapsed {
   padding: 0;
 }
diff --git a/webroot/rsrc/js/phui/behavior-phui-selectable-list.js b/webroot/rsrc/js/phui/behavior-phui-selectable-list.js
new file mode 100644
--- /dev/null
+++ b/webroot/rsrc/js/phui/behavior-phui-selectable-list.js
@@ -0,0 +1,44 @@
+/**
+ * @provides javelin-behavior-phui-selectable-list
+ * @requires javelin-behavior
+ *           javelin-stratcom
+ *           javelin-dom
+ */
+
+JX.behavior('phui-selectable-list', function() {
+
+  JX.Stratcom.listen('click', 'phui-oi-selectable', function(e) {
+    if (!e.isNormalClick()) {
+      return;
+    }
+
+    // If the user clicked a link, ignore it.
+    if (e.getNode('tag:a')) {
+      return;
+    }
+
+    var root = e.getNode('phui-oi-selectable');
+
+    // If the user did not click the checkbox, pretend they did. This makes
+    // the entire element a click target to make changing the selection set a
+    // bit easier.
+    if (!e.getNode('tag:input')) {
+      var checkbox = getCheckbox(root);
+      checkbox.checked = !checkbox.checked;
+
+      e.kill();
+    }
+
+    setTimeout(JX.bind(null, redraw, root), 0);
+  });
+
+  function getCheckbox(root) {
+    return JX.DOM.find(root, 'input');
+  }
+
+  function redraw(root) {
+    var checkbox = getCheckbox(root);
+    JX.DOM.alterClass(root, 'phui-oi-selected', !!checkbox.checked);
+  }
+
+});