diff --git a/resources/celerity/map.php b/resources/celerity/map.php
--- a/resources/celerity/map.php
+++ b/resources/celerity/map.php
@@ -52,6 +52,7 @@
     'rsrc/css/application/conpherence/widget-pane.css' => 'bf275a6c',
     'rsrc/css/application/contentsource/content-source-view.css' => '4b8b05d4',
     'rsrc/css/application/countdown/timer.css' => '86b7b0a0',
+    'rsrc/css/application/dashboard/dashboard.css' => '5b532b7b',
     'rsrc/css/application/diff/inline-comment-summary.css' => '8cfd34e8',
     'rsrc/css/application/differential/add-comment.css' => 'c478bcaa',
     'rsrc/css/application/differential/changeset-view.css' => '1570a1ff',
@@ -358,6 +359,7 @@
     'rsrc/js/application/conpherence/behavior-widget-pane.js' => '40b1ff90',
     'rsrc/js/application/countdown/timer.js' => '889c96f3',
     'rsrc/js/application/dashboard/behavior-dashboard-async-panel.js' => '4398eabb',
+    'rsrc/js/application/dashboard/behavior-dashboard-move-panels.js' => 'aa3f313b',
     'rsrc/js/application/differential/DifferentialInlineCommentEditor.js' => 'f2441746',
     'rsrc/js/application/differential/behavior-add-reviewers-and-ccs.js' => '533a187b',
     'rsrc/js/application/differential/behavior-comment-jump.js' => '71755c79',
@@ -552,6 +554,7 @@
     'javelin-behavior-countdown-timer' => '889c96f3',
     'javelin-behavior-dark-console' => 'e9fdb5e5',
     'javelin-behavior-dashboard-async-panel' => '4398eabb',
+    'javelin-behavior-dashboard-move-panels' => 'aa3f313b',
     'javelin-behavior-device' => '03d6ed07',
     'javelin-behavior-differential-add-reviewers-and-ccs' => '533a187b',
     'javelin-behavior-differential-comment-jump' => '71755c79',
@@ -698,6 +701,7 @@
     'phabricator-core-css' => '40151074',
     'phabricator-countdown-css' => '86b7b0a0',
     'phabricator-crumbs-view-css' => '6a23399c',
+    'phabricator-dashboard-css' => '5b532b7b',
     'phabricator-drag-and-drop-file-upload' => 'ae6abfba',
     'phabricator-draggable-list' => '1681c4d4',
     'phabricator-fatal-config-template-css' => '25d446d6',
@@ -1266,6 +1270,18 @@
       2 => 'javelin-util',
       3 => 'phabricator-shaped-request',
     ),
+    '7319e029' =>
+    array(
+      0 => 'javelin-behavior',
+      1 => 'javelin-dom',
+    ),
+    '62e18640' =>
+    array(
+      0 => 'javelin-install',
+      1 => 'javelin-util',
+      2 => 'javelin-dom',
+      3 => 'javelin-typeahead-normalizer',
+    ),
     '6453c869' =>
     array(
       0 => 'javelin-install',
@@ -1313,18 +1329,6 @@
       1 => 'javelin-stratcom',
       2 => 'javelin-dom',
     ),
-    '7319e029' =>
-    array(
-      0 => 'javelin-behavior',
-      1 => 'javelin-dom',
-    ),
-    '62e18640' =>
-    array(
-      0 => 'javelin-install',
-      1 => 'javelin-util',
-      2 => 'javelin-dom',
-      3 => 'javelin-typeahead-normalizer',
-    ),
     '76f4ebed' =>
     array(
       0 => 'javelin-install',
@@ -1594,6 +1598,15 @@
       1 => 'javelin-stratcom',
       2 => 'javelin-dom',
     ),
+    'aa3f313b' =>
+    array(
+      0 => 'javelin-behavior',
+      1 => 'javelin-dom',
+      2 => 'javelin-util',
+      3 => 'javelin-stratcom',
+      4 => 'javelin-workflow',
+      5 => 'phabricator-draggable-list',
+    ),
     'ad7a69ca' =>
     array(
       0 => 'javelin-install',
diff --git a/resources/sql/autopatches/20140509.dashboardlayoutconfig.sql b/resources/sql/autopatches/20140509.dashboardlayoutconfig.sql
new file mode 100644
--- /dev/null
+++ b/resources/sql/autopatches/20140509.dashboardlayoutconfig.sql
@@ -0,0 +1,4 @@
+ALTER TABLE {$NAMESPACE}_dashboard.dashboard
+  ADD COLUMN layoutConfig LONGTEXT NOT NULL COLLATE utf8_bin AFTER name;
+
+UPDATE {$NAMESPACE}_dashboard.dashboard SET layoutConfig  = '[]';
diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php
--- a/src/__phutil_library_map__.php
+++ b/src/__phutil_library_map__.php
@@ -1456,10 +1456,13 @@
     'PhabricatorDaemonTaskGarbageCollector' => 'applications/daemon/garbagecollector/PhabricatorDaemonTaskGarbageCollector.php',
     'PhabricatorDashboard' => 'applications/dashboard/storage/PhabricatorDashboard.php',
     'PhabricatorDashboardAddPanelController' => 'applications/dashboard/controller/PhabricatorDashboardAddPanelController.php',
+    'PhabricatorDashboardArrangeController' => 'applications/dashboard/controller/PhabricatorDashboardArrangeController.php',
     'PhabricatorDashboardController' => 'applications/dashboard/controller/PhabricatorDashboardController.php',
     'PhabricatorDashboardDAO' => 'applications/dashboard/storage/PhabricatorDashboardDAO.php',
     'PhabricatorDashboardEditController' => 'applications/dashboard/controller/PhabricatorDashboardEditController.php',
+    'PhabricatorDashboardLayoutConfig' => 'applications/dashboard/layoutconfig/PhabricatorDashboardLayoutConfig.php',
     'PhabricatorDashboardListController' => 'applications/dashboard/controller/PhabricatorDashboardListController.php',
+    'PhabricatorDashboardMovePanelController' => 'applications/dashboard/controller/PhabricatorDashboardMovePanelController.php',
     'PhabricatorDashboardPHIDTypeDashboard' => 'applications/dashboard/phid/PhabricatorDashboardPHIDTypeDashboard.php',
     'PhabricatorDashboardPHIDTypePanel' => 'applications/dashboard/phid/PhabricatorDashboardPHIDTypePanel.php',
     'PhabricatorDashboardPanel' => 'applications/dashboard/storage/PhabricatorDashboardPanel.php',
@@ -4235,10 +4238,12 @@
       1 => 'PhabricatorPolicyInterface',
     ),
     'PhabricatorDashboardAddPanelController' => 'PhabricatorDashboardController',
+    'PhabricatorDashboardArrangeController' => 'PhabricatorDashboardController',
     'PhabricatorDashboardController' => 'PhabricatorController',
     'PhabricatorDashboardDAO' => 'PhabricatorLiskDAO',
     'PhabricatorDashboardEditController' => 'PhabricatorDashboardController',
     'PhabricatorDashboardListController' => 'PhabricatorDashboardController',
+    'PhabricatorDashboardMovePanelController' => 'PhabricatorDashboardController',
     'PhabricatorDashboardPHIDTypeDashboard' => 'PhabricatorPHIDType',
     'PhabricatorDashboardPHIDTypePanel' => 'PhabricatorPHIDType',
     'PhabricatorDashboardPanel' =>
diff --git a/src/applications/dashboard/application/PhabricatorApplicationDashboard.php b/src/applications/dashboard/application/PhabricatorApplicationDashboard.php
--- a/src/applications/dashboard/application/PhabricatorApplicationDashboard.php
+++ b/src/applications/dashboard/application/PhabricatorApplicationDashboard.php
@@ -21,10 +21,11 @@
         '(?:query/(?P<queryKey>[^/]+)/)?'
           => 'PhabricatorDashboardListController',
         'view/(?P<id>\d+)/' => 'PhabricatorDashboardViewController',
+        'arrange/(?P<id>\d+)/' => 'PhabricatorDashboardArrangeController',
         'create/' => 'PhabricatorDashboardEditController',
         'edit/(?:(?P<id>\d+)/)?' => 'PhabricatorDashboardEditController',
         'addpanel/(?P<id>\d+)/' => 'PhabricatorDashboardAddPanelController',
-
+        'movepanel/(?P<id>\d+)/' => 'PhabricatorDashboardMovePanelController',
         'panel/' => array(
           '(?:query/(?P<queryKey>[^/]+)/)?'
             => 'PhabricatorDashboardPanelListController',
diff --git a/src/applications/dashboard/controller/PhabricatorDashboardAddPanelController.php b/src/applications/dashboard/controller/PhabricatorDashboardAddPanelController.php
--- a/src/applications/dashboard/controller/PhabricatorDashboardAddPanelController.php
+++ b/src/applications/dashboard/controller/PhabricatorDashboardAddPanelController.php
@@ -26,7 +26,14 @@
       return new Aphront404Response();
     }
 
-    $dashboard_uri = $this->getApplicationURI('view/'.$dashboard->getID().'/');
+    if ($request->getStr('src', 'edit') == 'edit') {
+      $redirect_uri = $this->getApplicationURI(
+        'view/'.$dashboard->getID().'/');
+    } else {
+      $redirect_uri = $this->getApplicationURI(
+        'arrange/'.$dashboard->getID().'/');
+    }
+    $layout_config = $dashboard->getLayoutConfigObject();
 
     $v_panel = $request->getStr('panel');
     $e_panel = true;
@@ -61,6 +68,13 @@
               ),
             ));
 
+        if ($layout_config->isMultiColumnLayout()) {
+          $layout_config->setPanelLocation(
+            $request->getInt('column'),
+            $panel->getPHID());
+          $dashboard->setLayoutConfigFromObject($layout_config);
+        }
+
         $editor = id(new PhabricatorDashboardTransactionEditor())
           ->setActor($viewer)
           ->setContentSourceFromRequest($request)
@@ -68,12 +82,13 @@
           ->setContinueOnNoEffect(true)
           ->applyTransactions($dashboard, $xactions);
 
-        return id(new AphrontRedirectResponse())->setURI($dashboard_uri);
+        return id(new AphrontRedirectResponse())->setURI($redirect_uri);
       }
     }
 
     $form = id(new AphrontFormView())
       ->setUser($viewer)
+      ->addHiddenInput('src', $request->getStr('src', 'edit'))
       ->appendRemarkupInstructions(
         pht('Enter a panel monogram like `W123`.'))
       ->appendChild(
@@ -83,11 +98,23 @@
           ->setValue($v_panel)
           ->setError($e_panel));
 
+    if ($layout_config->isMultiColumnLayout()) {
+      $form
+        ->appendRemarkupInstructions(
+          pht('Choose which column the panel should reside in.'))
+        ->appendChild(
+          id(new AphrontFormSelectControl())
+          ->setName('column')
+          ->setLabel(pht('Column'))
+          ->setOptions($layout_config->getColumnSelectOptions())
+          ->setValue($request->getInt('column')));
+    }
+
     return $this->newDialog()
       ->setTitle(pht('Add Panel'))
       ->setErrors($errors)
       ->appendChild($form->buildLayoutView())
-      ->addCancelButton($dashboard_uri)
+      ->addCancelButton($redirect_uri)
       ->addSubmitButton(pht('Add Panel'));
   }
 
diff --git a/src/applications/dashboard/controller/PhabricatorDashboardArrangeController.php b/src/applications/dashboard/controller/PhabricatorDashboardArrangeController.php
new file mode 100644
--- /dev/null
+++ b/src/applications/dashboard/controller/PhabricatorDashboardArrangeController.php
@@ -0,0 +1,54 @@
+<?php
+
+final class PhabricatorDashboardArrangeController
+  extends PhabricatorDashboardController {
+
+  private $id;
+
+  public function willProcessRequest(array $data) {
+    $this->id = $data['id'];
+  }
+
+  public function processRequest() {
+    $request = $this->getRequest();
+    $viewer = $request->getUser();
+
+    $dashboard = id(new PhabricatorDashboardQuery())
+      ->setViewer($viewer)
+      ->withIDs(array($this->id))
+      ->needPanels(true)
+      ->requireCapabilities(
+        array(
+          PhabricatorPolicyCapability::CAN_VIEW,
+          PhabricatorPolicyCapability::CAN_EDIT,
+        ))
+      ->executeOne();
+    if (!$dashboard) {
+      return new Aphront404Response();
+    }
+
+    $title = $dashboard->getName();
+    $crumbs = $this->buildApplicationCrumbs();
+    $crumbs->addTextCrumb(
+      pht('Dashboard %d', $dashboard->getID()),
+      $this->getApplicationURI('view/'.$dashboard->getID().'/'));
+    $crumbs->addTextCrumb(pht('Arrange'));
+
+    $rendered_dashboard = id(new PhabricatorDashboardRenderingEngine())
+      ->setViewer($viewer)
+      ->setDashboard($dashboard)
+      ->setArrangeMode(true)
+      ->renderDashboard();
+
+    return $this->buildApplicationPage(
+      array(
+        $crumbs,
+        $rendered_dashboard,
+      ),
+      array(
+        'title' => $title,
+        'device' => true,
+      ));
+  }
+
+}
diff --git a/src/applications/dashboard/controller/PhabricatorDashboardEditController.php b/src/applications/dashboard/controller/PhabricatorDashboardEditController.php
--- a/src/applications/dashboard/controller/PhabricatorDashboardEditController.php
+++ b/src/applications/dashboard/controller/PhabricatorDashboardEditController.php
@@ -17,6 +17,7 @@
       $dashboard = id(new PhabricatorDashboardQuery())
         ->setViewer($viewer)
         ->withIDs(array($this->id))
+        ->needPanels(true)
         ->requireCapabilities(
           array(
             PhabricatorPolicyCapability::CAN_VIEW,
@@ -56,19 +57,25 @@
     }
 
     $v_name = $dashboard->getName();
+    $v_layout_mode = $dashboard->getLayoutConfigObject()->getLayoutMode();
     $e_name = true;
 
     $validation_exception = null;
     if ($request->isFormPost()) {
       $v_name = $request->getStr('name');
+      $v_layout_mode = $request->getStr('layout_mode');
 
       $xactions = array();
 
       $type_name = PhabricatorDashboardTransaction::TYPE_NAME;
+      $type_layout_mode = PhabricatorDashboardTransaction::TYPE_LAYOUT_MODE;
 
       $xactions[] = id(new PhabricatorDashboardTransaction())
         ->setTransactionType($type_name)
         ->setNewValue($v_name);
+      $xactions[] = id(new PhabricatorDashboardTransaction())
+        ->setTransactionType($type_layout_mode)
+        ->setNewValue($v_layout_mode);
 
       try {
         $editor = id(new PhabricatorDashboardTransactionEditor())
@@ -77,8 +84,12 @@
           ->setContentSourceFromRequest($request)
           ->applyTransactions($dashboard, $xactions);
 
-        return id(new AphrontRedirectResponse())
-          ->setURI($this->getApplicationURI('view/'.$dashboard->getID().'/'));
+        if ($is_new) {
+          $uri = $this->getApplicationURI('arrange/'.$dashboard->getID().'/');
+        } else {
+          $uri = $this->getApplicationURI('view/'.$dashboard->getID().'/');
+        }
+        return id(new AphrontRedirectResponse())->setURI($uri);
       } catch (PhabricatorApplicationTransactionValidationException $ex) {
         $validation_exception = $ex;
 
@@ -86,6 +97,8 @@
       }
     }
 
+    $layout_mode_options =
+      PhabricatorDashboardLayoutConfig::getLayoutModeSelectOptions();
     $form = id(new AphrontFormView())
       ->setUser($viewer)
       ->appendChild(
@@ -95,11 +108,16 @@
           ->setValue($v_name)
           ->setError($e_name))
       ->appendChild(
+        id(new AphrontFormSelectControl())
+          ->setLabel(pht('Layout Mode'))
+          ->setName('layout_mode')
+          ->setValue($v_layout_mode)
+          ->setOptions($layout_mode_options))
+      ->appendChild(
         id(new AphrontFormSubmitControl())
           ->setValue($button)
           ->addCancelButton($cancel_uri));
 
-
     $box = id(new PHUIObjectBoxView())
       ->setHeaderText($header)
       ->setForm($form)
diff --git a/src/applications/dashboard/controller/PhabricatorDashboardMovePanelController.php b/src/applications/dashboard/controller/PhabricatorDashboardMovePanelController.php
new file mode 100644
--- /dev/null
+++ b/src/applications/dashboard/controller/PhabricatorDashboardMovePanelController.php
@@ -0,0 +1,86 @@
+<?php
+
+final class PhabricatorDashboardMovePanelController
+  extends PhabricatorDashboardController {
+
+  private $id;
+
+  public function willProcessRequest(array $data) {
+    $this->id = $data['id'];
+  }
+
+  public function processRequest() {
+    $request = $this->getRequest();
+    $viewer = $request->getUser();
+
+    $column_id = $request->getStr('columnID');
+    $panel_phid = $request->getStr('objectPHID');
+    $after_phid = $request->getStr('afterPHID');
+    $before_phid = $request->getStr('beforePHID');
+
+    $dashboard = id(new PhabricatorDashboardQuery())
+      ->setViewer($viewer)
+      ->withIDs(array($this->id))
+      ->needPanels(true)
+      ->requireCapabilities(
+        array(
+          PhabricatorPolicyCapability::CAN_VIEW,
+          PhabricatorPolicyCapability::CAN_EDIT,
+        ))
+      ->executeOne();
+    if (!$dashboard) {
+      return new Aphront404Response();
+    }
+    $panels = mpull($dashboard->getPanels(), null, 'getPHID');
+    $panel = idx($panels, $panel_phid);
+    if (!$panel) {
+      return new Aphront404Response();
+    }
+
+    $layout_config = $dashboard->getLayoutConfigObject();
+    $panel_location_grid = $layout_config->getPanelLocations();
+
+    foreach ($panel_location_grid as $column => $panel_columns) {
+      $found_old_column = array_search($panel_phid, $panel_columns);
+      if ($found_old_column !== false) {
+        $new_panel_columns = $panel_columns;
+        array_splice(
+          $new_panel_columns,
+          $found_old_column,
+          1,
+          array());
+        $panel_location_grid[$column] = $new_panel_columns;
+        break;
+      }
+    }
+    $panel_columns = idx($panel_location_grid, $column_id, array());
+    if ($panel_columns) {
+      $insert_at = 0;
+      $new_panel_columns = $panel_columns;
+      foreach ($panel_columns as $index => $curr_panel_phid) {
+        if ($curr_panel_phid === $before_phid) {
+          $insert_at = max($index - 1, 0);
+          break;
+        }
+        if ($curr_panel_phid === $after_phid) {
+          $insert_at = $index;
+          break;
+        }
+      }
+      array_splice(
+        $new_panel_columns,
+        $insert_at,
+        0,
+        array($panel_phid));
+    } else {
+      $new_panel_columns = array(0 => $panel_phid);
+    }
+    $panel_location_grid[$column_id] = $new_panel_columns;
+    $layout_config->setPanelLocations($panel_location_grid);
+    $dashboard->setLayoutConfigFromObject($layout_config);
+    $dashboard->save();
+
+    return id(new AphrontAjaxResponse())->setContent('');
+ }
+
+}
diff --git a/src/applications/dashboard/controller/PhabricatorDashboardViewController.php b/src/applications/dashboard/controller/PhabricatorDashboardViewController.php
--- a/src/applications/dashboard/controller/PhabricatorDashboardViewController.php
+++ b/src/applications/dashboard/controller/PhabricatorDashboardViewController.php
@@ -86,6 +86,14 @@
 
     $actions->addAction(
       id(new PhabricatorActionView())
+        ->setName(pht('Arrange Dashboard'))
+        ->setIcon('fa-arrows')
+        ->setHref($this->getApplicationURI("arrange/{$id}/"))
+        ->setDisabled(!$can_edit)
+        ->setWorkflow(!$can_edit));
+
+    $actions->addAction(
+      id(new PhabricatorActionView())
         ->setName(pht('Add Panel'))
         ->setIcon('fa-plus')
         ->setHref($this->getApplicationURI("addpanel/{$id}/"))
diff --git a/src/applications/dashboard/editor/PhabricatorDashboardTransactionEditor.php b/src/applications/dashboard/editor/PhabricatorDashboardTransactionEditor.php
--- a/src/applications/dashboard/editor/PhabricatorDashboardTransactionEditor.php
+++ b/src/applications/dashboard/editor/PhabricatorDashboardTransactionEditor.php
@@ -11,6 +11,7 @@
     $types[] = PhabricatorTransactions::TYPE_EDGE;
 
     $types[] = PhabricatorDashboardTransaction::TYPE_NAME;
+    $types[] = PhabricatorDashboardTransaction::TYPE_LAYOUT_MODE;
 
     return $types;
   }
@@ -24,6 +25,12 @@
           return null;
         }
         return $object->getName();
+      case PhabricatorDashboardTransaction::TYPE_LAYOUT_MODE:
+        if ($this->getIsNewObject()) {
+          return null;
+        }
+        $layout_config = $object->getLayoutConfigObject();
+        return $layout_config->getLayoutMode();
     }
 
     return parent::getCustomTransactionOldValue($object, $xaction);
@@ -34,6 +41,7 @@
     PhabricatorApplicationTransaction $xaction) {
     switch ($xaction->getTransactionType()) {
       case PhabricatorDashboardTransaction::TYPE_NAME:
+      case PhabricatorDashboardTransaction::TYPE_LAYOUT_MODE:
         return $xaction->getNewValue();
     }
     return parent::getCustomTransactionNewValue($object, $xaction);
@@ -46,6 +54,21 @@
       case PhabricatorDashboardTransaction::TYPE_NAME:
         $object->setName($xaction->getNewValue());
         return;
+      case PhabricatorDashboardTransaction::TYPE_LAYOUT_MODE:
+        $old_layout = $object->getLayoutConfigObject();
+        $new_layout = clone $old_layout;
+        $new_layout->setLayoutMode($xaction->getNewValue());
+        if ($old_layout->isMultiColumnLayout() !=
+            $new_layout->isMultiColumnLayout()) {
+          $panel_phids = $object->getPanelPHIDs();
+          $new_locations = $new_layout->getDefaultPanelLocations();
+          foreach ($panel_phids as $panel_phid) {
+            $new_locations[0][] = $panel_phid;
+          }
+          $new_layout->setPanelLocations($new_locations);
+        }
+        $object->setLayoutConfigFromObject($new_layout);
+        return;
       case PhabricatorTransactions::TYPE_VIEW_POLICY:
         $object->setViewPolicy($xaction->getNewValue());
         return;
@@ -65,6 +88,7 @@
 
     switch ($xaction->getTransactionType()) {
       case PhabricatorDashboardTransaction::TYPE_NAME:
+      case PhabricatorDashboardTransaction::TYPE_LAYOUT_MODE:
         return;
       case PhabricatorTransactions::TYPE_EDGE:
         return;
diff --git a/src/applications/dashboard/engine/PhabricatorDashboardPanelRenderingEngine.php b/src/applications/dashboard/engine/PhabricatorDashboardPanelRenderingEngine.php
--- a/src/applications/dashboard/engine/PhabricatorDashboardPanelRenderingEngine.php
+++ b/src/applications/dashboard/engine/PhabricatorDashboardPanelRenderingEngine.php
@@ -79,6 +79,9 @@
       ));
 
     return id(new PHUIObjectBoxView())
+      ->addSigil('dashboard-panel')
+      ->setMetadata(array(
+        'objectPHID' => $panel->getPHID()))
       ->setHeaderText($panel->getName())
       ->setID($panel_id)
       ->appendChild(pht('Loading...'));
diff --git a/src/applications/dashboard/engine/PhabricatorDashboardRenderingEngine.php b/src/applications/dashboard/engine/PhabricatorDashboardRenderingEngine.php
--- a/src/applications/dashboard/engine/PhabricatorDashboardRenderingEngine.php
+++ b/src/applications/dashboard/engine/PhabricatorDashboardRenderingEngine.php
@@ -4,6 +4,7 @@
 
   private $dashboard;
   private $viewer;
+  private $arrangeMode;
 
   public function setViewer(PhabricatorUser $viewer) {
     $this->viewer = $viewer;
@@ -15,20 +16,101 @@
     return $this;
   }
 
+  public function setArrangeMode($mode) {
+    $this->arrangeMode = $mode;
+    return $this;
+  }
+
   public function renderDashboard() {
+    require_celerity_resource('phabricator-dashboard-css');
     $dashboard = $this->dashboard;
     $viewer = $this->viewer;
 
-    $result = array();
-    foreach ($dashboard->getPanels() as $panel) {
-      $result[] = id(new PhabricatorDashboardPanelRenderingEngine())
-        ->setViewer($viewer)
-        ->setPanel($panel)
-        ->setEnableAsyncRendering(true)
-        ->renderPanel();
+    $layout_config = $dashboard->getLayoutConfigObject();
+    $panel_grid_locations = $layout_config->getPanelLocations();
+    $panels = mpull($dashboard->getPanels(), null, 'getPHID');
+    $dashboard_id = celerity_generate_unique_node_id();
+    $result = id(new AphrontMultiColumnView())
+      ->setID($dashboard_id)
+      ->setFluidlayout(true);
+
+    foreach ($panel_grid_locations as $column => $panel_column_locations) {
+      $panel_phids = $panel_column_locations;
+      $column_panels = array_select_keys($panels, $panel_phids);
+      $column_result = array();
+      foreach ($column_panels as $panel) {
+        $column_result[] = id(new PhabricatorDashboardPanelRenderingEngine())
+          ->setViewer($viewer)
+          ->setPanel($panel)
+          ->setEnableAsyncRendering(true)
+          ->renderPanel();
+      }
+      $column_class = $layout_config->getColumnClass(
+        $column,
+        $this->arrangeMode);
+      if ($this->arrangeMode) {
+        $column_result[] = $this->renderAddPanelPlaceHolder($column);
+        $column_result[] = $this->renderAddPanelUI($column);
+      }
+      $result->addColumn(
+        $column_result,
+        $column_class,
+        $sigil = 'dashboard-column',
+        $metadata = array('columnID' => $column));
+    }
+
+    if ($this->arrangeMode) {
+      Javelin::initBehavior(
+        'dashboard-move-panels',
+        array(
+          'dashboardID' => $dashboard_id,
+          'moveURI' => '/dashboard/movepanel/'.$dashboard->getID().'/',
+        ));
     }
 
     return $result;
   }
 
+  private function renderAddPanelPlaceHolder($column) {
+    $uri = $this->getAddPanelURI($column);
+
+    $dashboard = $this->dashboard;
+    $panels = $dashboard->getPanels();
+    $layout_config = $dashboard->getLayoutConfigObject();
+    if ($layout_config->isMultiColumnLayout() && count($panels)) {
+      $text = pht('Drag a panel here or click to add a panel.');
+    } else {
+      $text = pht('Click to add a panel.');
+    }
+    return javelin_tag(
+      'a',
+      array(
+        'sigil' => 'workflow',
+        'class' => 'drag-ghost dashboard-panel-placeholder',
+        'href' => (string) $uri),
+      $text);
+  }
+
+  private function renderAddPanelUI($column) {
+    $uri = $this->getAddPanelURI($column);
+
+    return id(new PHUIButtonView())
+      ->setTag('a')
+      ->setHref((string) $uri)
+      ->setWorkflow(true)
+      ->setColor(PHUIButtonView::GREY)
+      ->setIcon(id(new PHUIIconView())
+        ->setIconFont('fa-plus'))
+      ->setText(pht('Add Panel'))
+      ->addClass(PHUI::MARGIN_LARGE);
+  }
+
+  private function getAddPanelURI($column) {
+    $dashboard = $this->dashboard;
+    $uri = id(new PhutilURI('/dashboard/addpanel/'.$dashboard->getID().'/'))
+      ->setQueryParam('column', $column)
+      ->setQueryParam('src', 'arrange');
+    return $uri;
+  }
+
 }
diff --git a/src/applications/dashboard/layoutconfig/PhabricatorDashboardLayoutConfig.php b/src/applications/dashboard/layoutconfig/PhabricatorDashboardLayoutConfig.php
new file mode 100644
--- /dev/null
+++ b/src/applications/dashboard/layoutconfig/PhabricatorDashboardLayoutConfig.php
@@ -0,0 +1,133 @@
+<?php
+
+final class PhabricatorDashboardLayoutConfig {
+
+  const MODE_FULL                = 'layout-mode-full';
+  const MODE_HALF_AND_HALF       = 'layout-mode-half-and-half';
+  const MODE_THIRD_AND_THIRDS    = 'layout-mode-third-and-thirds';
+  const MODE_THIRDS_AND_THIRD    = 'layout-mode-thirds-and-third';
+
+  private $layoutMode     = self::MODE_FULL;
+  private $panelLocations = array();
+
+  public function setLayoutMode($mode) {
+    $this->layoutMode = $mode;
+    return $this;
+  }
+  public function getLayoutMode() {
+    return $this->layoutMode;
+  }
+
+  public function setPanelLocation($which_column, $panel_phid) {
+    $this->panelLocations[$which_column][] = $panel_phid;
+    return $this;
+  }
+
+  public function setPanelLocations(array $locations) {
+    $this->panelLocations = $locations;
+    return $this;
+  }
+
+  public function getPanelLocations() {
+    return $this->panelLocations;
+  }
+
+  public function getDefaultPanelLocations() {
+    switch ($this->getLayoutMode()) {
+      case self::MODE_HALF_AND_HALF:
+      case self::MODE_THIRD_AND_THIRDS:
+      case self::MODE_THIRDS_AND_THIRD:
+        $locations = array(array(), array());
+        break;
+      case self::MODE_FULL:
+      default:
+        $locations = array(array());
+        break;
+    }
+    return $locations;
+  }
+
+  public function getColumnClass($column_index, $grippable = false) {
+    switch ($this->getLayoutMode()) {
+      case self::MODE_HALF_AND_HALF:
+        $class = 'half';
+        break;
+      case self::MODE_THIRD_AND_THIRDS:
+        if ($column_index) {
+          $class = 'thirds';
+        } else {
+          $class = 'third';
+        }
+        break;
+      case self::MODE_THIRDS_AND_THIRD:
+        if ($column_index) {
+          $class = 'third';
+        } else {
+          $class = 'thirds';
+        }
+        break;
+      case self::MODE_FULL:
+      default:
+        $class = null;
+        break;
+    }
+    if ($grippable) {
+      $class .= ' grippable';
+    }
+    return $class;
+  }
+
+  public function isMultiColumnLayout() {
+    return $this->getLayoutMode() != self::MODE_FULL;
+  }
+
+  public function getColumnSelectOptions() {
+    $options = array();
+
+    switch ($this->getLayoutMode()) {
+      case self::MODE_HALF_AND_HALF:
+      case self::MODE_THIRD_AND_THIRDS:
+      case self::MODE_THIRDS_AND_THIRD:
+        return array(
+          0 => pht('Left'),
+          1 => pht('Right'));
+        break;
+      case self::MODE_FULL:
+        throw new Exception('There is only one column in mode full.');
+        break;
+      default:
+        throw new Exception('Unknown layout mode!');
+        break;
+    }
+
+    return $options;
+  }
+
+  public static function getLayoutModeSelectOptions() {
+    return array(
+      self::MODE_FULL             => pht('One full-width column'),
+      self::MODE_HALF_AND_HALF    => pht('Two columns, 1/2 and 1/2'),
+      self::MODE_THIRD_AND_THIRDS => pht('Two columns, 1/3 and 2/3'),
+      self::MODE_THIRDS_AND_THIRD => pht('Two columns, 2/3 and 1/3'),
+    );
+  }
+
+  public static function newFromDictionary(array $dict) {
+    $layout_config = id(new PhabricatorDashboardLayoutConfig())
+      ->setLayoutMode(idx($dict, 'layoutMode', self::MODE_FULL));
+    $layout_config->setPanelLocations(idx(
+      $dict,
+      'panelLocations',
+      $layout_config->getDefaultPanelLocations()));
+
+    return $layout_config;
+  }
+
+  public function toDictionary() {
+    return array(
+      'layoutMode' => $this->getLayoutMode(),
+      'panelLocations' => $this->getPanelLocations()
+    );
+  }
+
+}
diff --git a/src/applications/dashboard/paneltype/PhabricatorDashboardPanelType.php b/src/applications/dashboard/paneltype/PhabricatorDashboardPanelType.php
--- a/src/applications/dashboard/paneltype/PhabricatorDashboardPanelType.php
+++ b/src/applications/dashboard/paneltype/PhabricatorDashboardPanelType.php
@@ -47,6 +47,9 @@
     $content = $this->renderPanelContent($viewer, $panel);
 
     return id(new PHUIObjectBoxView())
+      ->addSigil('dashboard-panel')
+      ->setMetadata(array(
+        'objectPHID' => $panel->getPHID()))
       ->setHeaderText($panel->getName())
       ->appendChild($content);
   }
diff --git a/src/applications/dashboard/storage/PhabricatorDashboard.php b/src/applications/dashboard/storage/PhabricatorDashboard.php
--- a/src/applications/dashboard/storage/PhabricatorDashboard.php
+++ b/src/applications/dashboard/storage/PhabricatorDashboard.php
@@ -9,6 +9,7 @@
   protected $name;
   protected $viewPolicy;
   protected $editPolicy;
+  protected $layoutConfig = array();
 
   private $panelPHIDs = self::ATTACHABLE;
   private $panels = self::ATTACHABLE;
@@ -17,12 +18,16 @@
     return id(new PhabricatorDashboard())
       ->setName('')
       ->setViewPolicy(PhabricatorPolicies::POLICY_USER)
-      ->setEditPolicy($actor->getPHID());
+      ->setEditPolicy($actor->getPHID())
+      ->attachPanels(array())
+      ->attachPanelPHIDs(array());
   }
 
   public function getConfiguration() {
     return array(
       self::CONFIG_AUX_PHID => true,
+      self::CONFIG_SERIALIZATION => array(
+        'layoutConfig' => self::SERIALIZATION_JSON),
     ) + parent::getConfiguration();
   }
 
@@ -31,6 +36,17 @@
       PhabricatorDashboardPHIDTypeDashboard::TYPECONST);
   }
 
+  public function getLayoutConfigObject() {
+    return PhabricatorDashboardLayoutConfig::newFromDictionary(
+      $this->getLayoutConfig());
+  }
+
+  public function setLayoutConfigFromObject(
+    PhabricatorDashboardLayoutConfig $object) {
+    $this->setLayoutConfig($object->toDictionary());
+    return $this;
+  }
+
   public function attachPanelPHIDs(array $phids) {
     $this->panelPHIDs = $phids;
     return $this;
diff --git a/src/applications/dashboard/storage/PhabricatorDashboardTransaction.php b/src/applications/dashboard/storage/PhabricatorDashboardTransaction.php
--- a/src/applications/dashboard/storage/PhabricatorDashboardTransaction.php
+++ b/src/applications/dashboard/storage/PhabricatorDashboardTransaction.php
@@ -4,6 +4,7 @@
   extends PhabricatorApplicationTransaction {
 
   const TYPE_NAME = 'dashboard:name';
+  const TYPE_LAYOUT_MODE = 'dashboard:layoutmode';
 
   public function getApplicationName() {
     return 'dashboard';
@@ -86,4 +87,15 @@
 
     return parent::getColor();
   }
+
+  public function shouldHide() {
+    $old = $this->getOldValue();
+    $new = $this->getNewValue();
+
+    switch ($this->getTransactionType()) {
+      case self::TYPE_LAYOUT_MODE:
+        return true;
+    }
+    return parent::shouldHide();
+  }
 }
diff --git a/src/view/layout/AphrontMultiColumnView.php b/src/view/layout/AphrontMultiColumnView.php
--- a/src/view/layout/AphrontMultiColumnView.php
+++ b/src/view/layout/AphrontMultiColumnView.php
@@ -6,14 +6,32 @@
   const GUTTER_MEDIUM = 'mmr';
   const GUTTER_LARGE = 'mlr';
 
+  private $id;
   private $columns = array();
   private $fluidLayout = false;
   private $fluidishLayout = false;
   private $gutter;
   private $border;
 
-  public function addColumn($column) {
-    $this->columns[] = $column;
+  public function setID($id) {
+    $this->id = $id;
+    return $this;
+  }
+
+  public function getID() {
+    return $this->id;
+  }
+
+  public function addColumn(
+    $column,
+    $class = null,
+    $sigil = null,
+    $metadata = null) {
+    $this->columns[] = array(
+      'column' => $column,
+      'class' => $class,
+      'sigil' => $sigil,
+      'metadata' => $metadata);
     return $this;
   }
 
@@ -55,30 +73,34 @@
     $classes[] = 'aphront-multi-column-'.count($this->columns).'-up';
 
     $columns = array();
-    $column_class = array();
-    $column_class[] = 'aphront-multi-column-column';
-    $outer_class = array();
-    $outer_class[] = 'aphront-multi-column-column-outer';
-    if ($this->gutter) {
-      $column_class[] = $this->gutter;
-    }
     $i = 0;
-    foreach ($this->columns as $column) {
+    foreach ($this->columns as $column_data) {
+      $column_class = array('aphront-multi-column-column');
+      if ($this->gutter) {
+        $column_class[] = $this->gutter;
+      }
+      $outer_class = array('aphront-multi-column-column-outer');
       if (++$i === count($this->columns)) {
         $column_class[] = 'aphront-multi-column-column-last';
         $outer_class[] = 'aphront-multi-colum-column-outer-last';
       }
-      $column_inner = phutil_tag(
+      $column = $column_data['column'];
+      if ($column_data['class']) {
+        $outer_class[] = $column_data['class'];
+      }
+      $column_sigil = idx($column_data, 'sigil');
+      $column_metadata = idx($column_data, 'metadata');
+      $column_inner = javelin_tag(
         'div',
-          array(
-          'class' => implode(' ', $column_class)
-          ),
+        array(
+          'class' => implode(' ', $column_class),
+          'sigil' => $column_sigil,
+          'meta'  => $column_metadata),
         $column);
       $columns[] = phutil_tag(
         'div',
-          array(
-          'class' => implode(' ', $outer_class)
-          ),
+        array(
+          'class' => implode(' ', $outer_class)),
         $column_inner);
     }
 
@@ -120,7 +142,8 @@
     return phutil_tag(
       'div',
         array(
-          'class' => 'aphront-multi-column-view'
+          'class' => 'aphront-multi-column-view',
+          'id' => $this->getID(),
         ),
         $board);
   }
diff --git a/webroot/rsrc/css/application/dashboard/dashboard.css b/webroot/rsrc/css/application/dashboard/dashboard.css
new file mode 100644
--- /dev/null
+++ b/webroot/rsrc/css/application/dashboard/dashboard.css
@@ -0,0 +1,59 @@
+/**
+ * @provides phabricator-dashboard-css
+ */
+
+.aphront-multi-column-fluid .aphront-multi-column-2-up
+.aphront-multi-column-column-outer.half {
+  width: 50%;
+}
+
+.aphront-multi-column-fluid .aphront-multi-column-2-up
+.aphront-multi-column-column-outer.third {
+  width: 33.34%;
+}
+
+.aphront-multi-column-fluid .aphront-multi-column-2-up
+.aphront-multi-column-column-outer.thirds {
+  width: 66.66%;
+}
+
+.aphront-multi-column-fluid
+.aphront-multi-column-column-outer.grippable
+.aphront-multi-column-column .phui-object-box {
+  cursor: move;
+}
+
+.aphront-multi-column-fluid
+.aphront-multi-column-column .drag-ghost {
+  list-style-type: none;
+  margin: 16px;
+}
+
+.aphront-multi-column-fluid
+.aphront-multi-column-column
+.dashboard-panel-placeholder {
+  display: none;
+}
+
+.aphront-multi-column-fluid
+.aphront-multi-column-column.dashboard-column-empty
+.dashboard-panel-placeholder {
+  color: {$greytext};
+  display: block;
+  padding: 24px;
+  margin: 16px 16px 0px 16px;
+}
+
+.aphront-multi-column-fluid
+.aphront-multi-column-column.dashboard-column-empty
+.dashboard-panel-placeholder:hover {
+  text-decoration: none;
+  border: 1px {$greyborder} dashed;
+  color: {$darkgreytext};
+}
+
+.aphront-multi-column-fluid
+.aphront-multi-column-column.drag-target-list
+.dashboard-panel-placeholder {
+  display: none;
+}
diff --git a/webroot/rsrc/js/application/dashboard/behavior-dashboard-move-panels.js b/webroot/rsrc/js/application/dashboard/behavior-dashboard-move-panels.js
new file mode 100644
--- /dev/null
+++ b/webroot/rsrc/js/application/dashboard/behavior-dashboard-move-panels.js
@@ -0,0 +1,103 @@
+/**
+ * @provides javelin-behavior-dashboard-move-panels
+ * @requires javelin-behavior
+ *           javelin-dom
+ *           javelin-util
+ *           javelin-stratcom
+ *           javelin-workflow
+ *           phabricator-draggable-list
+ */
+
+JX.behavior('dashboard-move-panels', function(config) {
+
+  var itemSigil = 'dashboard-panel';
+
+  function finditems(col) {
+    return JX.DOM.scry(col, 'div', itemSigil);
+  }
+
+  function markcolempty(col, toggle) {
+    JX.DOM.alterClass(col, 'dashboard-column-empty', toggle);
+  }
+
+  function onupdate(col) {
+    markcolempty(col, !this.findItems().length);
+  }
+
+  function onresponse(response, item, list) {
+    list.unlock();
+    JX.DOM.alterClass(item, 'drag-sending', false);
+  }
+
+  function ondrop(list, item, after, from) {
+    list.lock();
+    JX.DOM.alterClass(item, 'drag-sending', true);
+
+    var item_phid = JX.Stratcom.getData(item).objectPHID;
+    var data = {
+      objectPHID: item_phid,
+      columnID: JX.Stratcom.getData(list.getRootNode()).columnID
+    };
+
+    var after_phid = null;
+    var items = finditems(list.getRootNode());
+    if (after) {
+      after_phid = JX.Stratcom.getData(after).objectPHID;
+      data.afterPHID = after_phid;
+    }
+    var ii;
+    var ii_item;
+    var ii_item_phid;
+    var ii_prev_item_phid = null;
+    var before_phid = null;
+    for (ii = 0; ii < items.length; ii++) {
+      ii_item = items[ii];
+      ii_item_phid = JX.Stratcom.getData(ii_item).objectPHID;
+      if (ii_item_phid == item_phid) {
+        // skip the item we just dropped
+        continue;
+      }
+      // note this handles when there is no after phid - we are at the top of
+      // the list - quite nicely
+      if (ii_prev_item_phid == after_phid) {
+        before_phid = ii_item_phid;
+        break;
+      }
+      ii_prev_item_phid = ii_item_phid;
+    }
+    if (before_phid) {
+      data.beforePHID = before_phid;
+    }
+
+    var workflow = new JX.Workflow(config.moveURI, data)
+      .setHandler(function(response) {
+        onresponse(response, item, list);
+      });
+
+    workflow.start();
+  }
+
+  var lists = [];
+  var ii;
+  var cols = JX.DOM.scry(JX.$(config.dashboardID), 'div', 'dashboard-column');
+  var col = null;
+
+  for (ii = 0; ii < cols.length; ii++) {
+    col = cols[ii];
+    var list = new JX.DraggableList(itemSigil, col)
+      .setFindItemsHandler(JX.bind(null, finditems, col));
+
+    list.listen('didSend', JX.bind(list, onupdate, col));
+    list.listen('didReceive', JX.bind(list, onupdate, col));
+
+    list.listen('didDrop', JX.bind(null, ondrop, list));
+
+    lists.push(list);
+    markcolempty(col, finditems(col).length === 0);
+  }
+
+  for (ii = 0; ii < lists.length; ii++) {
+    lists[ii].setGroup(lists);
+  }
+
+});