diff --git a/resources/celerity/map.php b/resources/celerity/map.php
--- a/resources/celerity/map.php
+++ b/resources/celerity/map.php
@@ -155,6 +155,7 @@
     'rsrc/css/phui/phui-fontkit.css' => '1ec937e5',
     'rsrc/css/phui/phui-form-view.css' => '01b796c0',
     'rsrc/css/phui/phui-form.css' => '1f177cb7',
+    'rsrc/css/phui/phui-formation-view.css' => 'aec68a01',
     'rsrc/css/phui/phui-head-thing.css' => 'd7f293df',
     'rsrc/css/phui/phui-header-view.css' => '36c86a58',
     'rsrc/css/phui/phui-hovercard.css' => '6ca90fa0',
@@ -519,6 +520,7 @@
     'rsrc/js/phui/behavior-phui-submenu.js' => 'b5e9bff9',
     'rsrc/js/phui/behavior-phui-tab-group.js' => '242aa08b',
     'rsrc/js/phui/behavior-phui-timer-control.js' => 'f84bcbf4',
+    'rsrc/js/phui/behavior-phuix-formation-view.js' => '1a12beef',
     'rsrc/js/phuix/PHUIXActionListView.js' => 'c68f183f',
     'rsrc/js/phuix/PHUIXActionView.js' => 'aaa08f3b',
     'rsrc/js/phuix/PHUIXAutocomplete.js' => '2fbe234d',
@@ -526,6 +528,9 @@
     'rsrc/js/phuix/PHUIXDropdownMenu.js' => '7acfd98b',
     'rsrc/js/phuix/PHUIXExample.js' => 'c2c500a7',
     'rsrc/js/phuix/PHUIXFormControl.js' => '38c1f3fb',
+    'rsrc/js/phuix/PHUIXFormationColumnView.js' => '08fc09e9',
+    'rsrc/js/phuix/PHUIXFormationFlankView.js' => '6648270a',
+    'rsrc/js/phuix/PHUIXFormationView.js' => '0113c54c',
     'rsrc/js/phuix/PHUIXIconView.js' => 'a5257c4e',
   ),
   'symbols' => array(
@@ -667,6 +672,7 @@
     'javelin-behavior-phui-tab-group' => '242aa08b',
     'javelin-behavior-phui-timer-control' => 'f84bcbf4',
     'javelin-behavior-phuix-example' => 'c2c500a7',
+    'javelin-behavior-phuix-formation-view' => '1a12beef',
     'javelin-behavior-policy-control' => '0eaa33a9',
     'javelin-behavior-policy-rule-editor' => '9347f172',
     'javelin-behavior-project-boards' => '58cb6a88',
@@ -844,6 +850,7 @@
     'phui-fontkit-css' => '1ec937e5',
     'phui-form-css' => '1f177cb7',
     'phui-form-view-css' => '01b796c0',
+    'phui-formation-view-css' => 'aec68a01',
     'phui-head-thing-view-css' => 'd7f293df',
     'phui-header-view-css' => '36c86a58',
     'phui-hovercard' => '074f0783',
@@ -886,6 +893,9 @@
     'phuix-button-view' => '55a24e84',
     'phuix-dropdown-menu' => '7acfd98b',
     'phuix-form-control-view' => '38c1f3fb',
+    'phuix-formation-column-view' => '08fc09e9',
+    'phuix-formation-flank-view' => '6648270a',
+    'phuix-formation-view' => '0113c54c',
     'phuix-icon-view' => 'a5257c4e',
     'policy-css' => 'ceb56a08',
     'policy-edit-css' => '8794e2ed',
@@ -912,6 +922,10 @@
     'unhandled-exception-css' => '9ecfc00d',
   ),
   'requires' => array(
+    '0113c54c' => array(
+      'javelin-install',
+      'javelin-dom',
+    ),
     '0116d3e8' => array(
       'javelin-behavior',
       'javelin-dom',
@@ -984,6 +998,10 @@
       'javelin-util',
       'javelin-magical-init',
     ),
+    '08fc09e9' => array(
+      'javelin-install',
+      'javelin-dom',
+    ),
     '0922e81d' => array(
       'herald-rule-editor',
       'javelin-behavior',
@@ -1036,6 +1054,12 @@
     '16e97ebc' => array(
       'javelin-dom',
     ),
+    '1a12beef' => array(
+      'javelin-behavior',
+      'phuix-formation-view',
+      'phuix-formation-column-view',
+      'phuix-formation-flank-view',
+    ),
     '1a844c06' => array(
       'javelin-install',
       'javelin-util',
@@ -1520,6 +1544,10 @@
       'javelin-stratcom',
       'javelin-dom',
     ),
+    '6648270a' => array(
+      'javelin-install',
+      'javelin-dom',
+    ),
     '6a1583a8' => array(
       'javelin-behavior',
       'javelin-history',
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
@@ -179,6 +179,7 @@
     'AphrontAccessDeniedQueryException' => 'infrastructure/storage/exception/AphrontAccessDeniedQueryException.php',
     'AphrontAjaxResponse' => 'aphront/response/AphrontAjaxResponse.php',
     'AphrontApplicationConfiguration' => 'aphront/configuration/AphrontApplicationConfiguration.php',
+    'AphrontAutoIDView' => 'view/AphrontAutoIDView.php',
     'AphrontBarView' => 'view/widget/bars/AphrontBarView.php',
     'AphrontBaseMySQLDatabaseConnection' => 'infrastructure/storage/connection/mysql/AphrontBaseMySQLDatabaseConnection.php',
     'AphrontBoolHTTPParameterType' => 'aphront/httpparametertype/AphrontBoolHTTPParameterType.php',
@@ -2035,6 +2036,14 @@
     'PHUIFormLayoutView' => 'view/form/PHUIFormLayoutView.php',
     'PHUIFormNumberControl' => 'view/form/control/PHUIFormNumberControl.php',
     'PHUIFormTimerControl' => 'view/form/control/PHUIFormTimerControl.php',
+    'PHUIFormationColumnDynamicView' => 'view/formation/PHUIFormationColumnDynamicView.php',
+    'PHUIFormationColumnItem' => 'view/formation/PHUIFormationColumnItem.php',
+    'PHUIFormationColumnView' => 'view/formation/PHUIFormationColumnView.php',
+    'PHUIFormationContentView' => 'view/formation/PHUIFormationContentView.php',
+    'PHUIFormationExpanderView' => 'view/formation/PHUIFormationExpanderView.php',
+    'PHUIFormationFlankView' => 'view/formation/PHUIFormationFlankView.php',
+    'PHUIFormationResizerView' => 'view/formation/PHUIFormationResizerView.php',
+    'PHUIFormationView' => 'view/formation/PHUIFormationView.php',
     'PHUIHandleListView' => 'applications/phid/view/PHUIHandleListView.php',
     'PHUIHandleTagListView' => 'applications/phid/view/PHUIHandleTagListView.php',
     'PHUIHandleView' => 'applications/phid/view/PHUIHandleView.php',
@@ -6193,6 +6202,7 @@
     'AphrontAccessDeniedQueryException' => 'AphrontQueryException',
     'AphrontAjaxResponse' => 'AphrontResponse',
     'AphrontApplicationConfiguration' => 'Phobject',
+    'AphrontAutoIDView' => 'AphrontView',
     'AphrontBarView' => 'AphrontView',
     'AphrontBaseMySQLDatabaseConnection' => 'AphrontDatabaseConnection',
     'AphrontBoolHTTPParameterType' => 'AphrontHTTPParameterType',
@@ -8315,6 +8325,14 @@
     'PHUIFormLayoutView' => 'AphrontView',
     'PHUIFormNumberControl' => 'AphrontFormControl',
     'PHUIFormTimerControl' => 'AphrontFormControl',
+    'PHUIFormationColumnDynamicView' => 'PHUIFormationColumnView',
+    'PHUIFormationColumnItem' => 'Phobject',
+    'PHUIFormationColumnView' => 'AphrontAutoIDView',
+    'PHUIFormationContentView' => 'PHUIFormationColumnView',
+    'PHUIFormationExpanderView' => 'AphrontAutoIDView',
+    'PHUIFormationFlankView' => 'PHUIFormationColumnDynamicView',
+    'PHUIFormationResizerView' => 'PHUIFormationColumnView',
+    'PHUIFormationView' => 'AphrontView',
     'PHUIHandleListView' => 'AphrontTagView',
     'PHUIHandleTagListView' => 'AphrontTagView',
     'PHUIHandleView' => 'AphrontView',
diff --git a/src/view/AphrontAutoIDView.php b/src/view/AphrontAutoIDView.php
new file mode 100644
--- /dev/null
+++ b/src/view/AphrontAutoIDView.php
@@ -0,0 +1,15 @@
+<?php
+
+abstract class AphrontAutoIDView
+  extends AphrontView {
+
+  private $id;
+
+  final public function getID() {
+    if (!$this->id) {
+      $this->id = celerity_generate_unique_node_id();
+    }
+    return $this->id;
+  }
+
+}
diff --git a/src/view/formation/PHUIFormationColumnDynamicView.php b/src/view/formation/PHUIFormationColumnDynamicView.php
new file mode 100644
--- /dev/null
+++ b/src/view/formation/PHUIFormationColumnDynamicView.php
@@ -0,0 +1,37 @@
+<?php
+
+abstract class PHUIFormationColumnDynamicView
+  extends PHUIFormationColumnView {
+
+  private $isVisible = true;
+  private $isResizable;
+  private $width;
+
+  public function setIsVisible($is_visible) {
+    $this->isVisible = $is_visible;
+    return $this;
+  }
+
+  public function getIsVisible() {
+    return $this->isVisible;
+  }
+
+  public function setIsResizable($is_resizable) {
+    $this->isResizable = $is_resizable;
+    return $this;
+  }
+
+  public function getIsResizable() {
+    return $this->isResizable;
+  }
+
+  public function setWidth($width) {
+    $this->width = $width;
+    return $this;
+  }
+
+  public function getWidth() {
+    return $this->width;
+  }
+
+}
diff --git a/src/view/formation/PHUIFormationColumnItem.php b/src/view/formation/PHUIFormationColumnItem.php
new file mode 100644
--- /dev/null
+++ b/src/view/formation/PHUIFormationColumnItem.php
@@ -0,0 +1,116 @@
+<?php
+
+final class PHUIFormationColumnItem
+  extends Phobject {
+
+  private $id;
+  private $column;
+  private $controlItem;
+  private $resizerItem;
+  private $isRightAligned;
+  private $expander;
+  private $expanders = array();
+
+  public function getID() {
+    if (!$this->id) {
+      $this->id = celerity_generate_unique_node_id();
+    }
+    return $this->id;
+  }
+
+  public function setColumn(PHUIFormationColumnView $column) {
+    $this->column = $column;
+    return $this;
+  }
+
+  public function getColumn() {
+    return $this->column;
+  }
+
+  public function setControlItem(PHUIFormationColumnItem $control_item) {
+    $this->controlItem = $control_item;
+    return $this;
+  }
+
+  public function getControlItem() {
+    return $this->controlItem;
+  }
+
+  public function setIsRightAligned($is_right_aligned) {
+    $this->isRightAligned = $is_right_aligned;
+    return $this;
+  }
+
+  public function getIsRightAligned() {
+    return $this->isRightAligned;
+  }
+
+  public function setResizerItem(PHUIFormationColumnItem $resizer_item) {
+    $this->resizerItem = $resizer_item;
+    return $this;
+  }
+
+  public function getResizerItem() {
+    return $this->resizerItem;
+  }
+
+  public function setExpander(PHUIFormationExpanderView $expander) {
+    $this->expander = $expander;
+    return $this;
+  }
+
+  public function getExpander() {
+    return $this->expander;
+  }
+
+  public function appendExpander(PHUIFormationExpanderView $expander) {
+    $this->expanders[] = $expander;
+    return $this;
+  }
+
+  public function getExpanders() {
+    return $this->expanders;
+  }
+
+  public function newClientProperties() {
+    $expander_id = null;
+
+    $expander = $this->getExpander();
+    if ($expander) {
+      $expander_id = $expander->getID();
+    }
+
+
+    $resizer_details = null;
+    $resizer_item = $this->getResizerItem();
+    if ($resizer_item) {
+      $resizer_details = array(
+        'itemID' => $resizer_item->getID(),
+        'controlID' => $resizer_item->getColumn()->getID(),
+      );
+    }
+
+    $column = $this->getColumn();
+
+    $width = $column->getWidth();
+    if ($width !== null) {
+      $width = (int)$width;
+    }
+
+    $is_visible = (bool)$column->getIsVisible();
+    $is_right_aligned = $this->getIsRightAligned();
+
+    $column_details = $column->newClientProperties();
+
+    return array(
+      'itemID' => $this->getID(),
+      'width' => $width,
+      'isVisible' => $is_visible,
+      'isRightAligned' => $is_right_aligned,
+      'expanderID' => $expander_id,
+      'resizer' => $resizer_details,
+      'column' => $column_details,
+    );
+  }
+
+}
diff --git a/src/view/formation/PHUIFormationColumnView.php b/src/view/formation/PHUIFormationColumnView.php
new file mode 100644
--- /dev/null
+++ b/src/view/formation/PHUIFormationColumnView.php
@@ -0,0 +1,37 @@
+<?php
+
+abstract class PHUIFormationColumnView
+  extends AphrontAutoIDView {
+
+  private $item;
+
+  final public function setColumnItem(PHUIFormationColumnItem $item) {
+    $this->item = $item;
+    return $this;
+  }
+
+  final public function getColumnItem() {
+    return $this->item;
+  }
+
+  public function getWidth() {
+    return null;
+  }
+
+  public function getIsResizable() {
+    return false;
+  }
+
+  public function getIsVisible() {
+    return true;
+  }
+
+  public function getIsControlColumn() {
+    return false;
+  }
+
+  public function newClientProperties() {
+    return null;
+  }
+
+}
diff --git a/src/view/formation/PHUIFormationContentView.php b/src/view/formation/PHUIFormationContentView.php
new file mode 100644
--- /dev/null
+++ b/src/view/formation/PHUIFormationContentView.php
@@ -0,0 +1,21 @@
+<?php
+
+final class PHUIFormationContentView
+  extends PHUIFormationColumnView {
+
+  public function getIsControlColumn() {
+    return true;
+  }
+
+  public function render() {
+    require_celerity_resource('phui-formation-view-css');
+
+    return phutil_tag(
+      'div',
+      array(
+        'class' => 'phui-formation-view-content',
+      ),
+      $this->renderChildren());
+  }
+
+}
diff --git a/src/view/formation/PHUIFormationExpanderView.php b/src/view/formation/PHUIFormationExpanderView.php
new file mode 100644
--- /dev/null
+++ b/src/view/formation/PHUIFormationExpanderView.php
@@ -0,0 +1,64 @@
+<?php
+
+final class PHUIFormationExpanderView
+  extends AphrontAutoIDView {
+
+  private $tooltip;
+  private $columnItem;
+
+  public function setTooltip($tooltip) {
+    $this->tooltip = $tooltip;
+    return $this;
+  }
+
+  public function getTooltip() {
+    return $this->tooltip;
+  }
+
+  public function setColumnItem($column_item) {
+    $this->columnItem = $column_item;
+    return $this;
+  }
+
+  public function getColumnItem() {
+    return $this->columnItem;
+  }
+
+  public function render() {
+    $classes = array();
+    $classes[] = 'phui-formation-view-expander';
+
+    $is_right = $this->getColumnItem()->getIsRightAligned();
+    if ($is_right) {
+      $icon = id(new PHUIIconView())
+        ->setIcon('fa-chevron-left grey');
+      $classes[] = 'phui-formation-view-expander-right';
+    } else {
+      $icon = id(new PHUIIconView())
+        ->setIcon('fa-chevron-right grey');
+      $classes[] = 'phui-formation-view-expander-left';
+    }
+
+    $icon_view = phutil_tag(
+      'div',
+      array(
+        'class' => 'phui-formation-view-expander-icon',
+      ),
+      $icon);
+
+    return javelin_tag(
+      'div',
+      array(
+        'id' => $this->getID(),
+        'class' => implode(' ', $classes),
+        'sigil' => 'has-tooltip',
+        'style' => 'display: none',
+        'meta' => array(
+          'tip' => $this->getTooltip(),
+          'align' => 'E',
+        ),
+      ),
+      $icon_view);
+  }
+
+}
diff --git a/src/view/formation/PHUIFormationFlankView.php b/src/view/formation/PHUIFormationFlankView.php
new file mode 100644
--- /dev/null
+++ b/src/view/formation/PHUIFormationFlankView.php
@@ -0,0 +1,177 @@
+<?php
+
+final class PHUIFormationFlankView
+  extends PHUIFormationColumnDynamicView {
+
+  private $isFixed;
+
+  private $head;
+  private $body;
+  private $tail;
+
+  private $headID;
+  private $bodyID;
+  private $tailID;
+
+  private $headerText;
+
+  public function setIsFixed($fixed) {
+    $this->isFixed = $fixed;
+    return $this;
+  }
+
+  public function getIsFixed() {
+    return $this->isFixed;
+  }
+
+  public function setHead($head) {
+    $this->head = $head;
+    return $this;
+  }
+
+  public function setBody($body) {
+    $this->body = $body;
+    return $this;
+  }
+
+  public function setTail($tail) {
+    $this->tail = $tail;
+    return $this;
+  }
+
+  public function getHeadID() {
+    if (!$this->headID) {
+      $this->headID = celerity_generate_unique_node_id();
+    }
+    return $this->headID;
+  }
+
+  public function getBodyID() {
+    if (!$this->bodyID) {
+      $this->bodyID = celerity_generate_unique_node_id();
+    }
+    return $this->bodyID;
+  }
+
+  public function getTailID() {
+    if (!$this->tailID) {
+      $this->tailID = celerity_generate_unique_node_id();
+    }
+    return $this->tailID;
+  }
+
+  public function setHeaderText($header_text) {
+    $this->headerText = $header_text;
+    return $this;
+  }
+
+  public function getHeaderText() {
+    return $this->headerText;
+  }
+
+  public function newClientProperties() {
+    return array(
+      'type' => 'flank',
+      'nodeID' => $this->getID(),
+      'isFixed' => (bool)$this->getIsFixed(),
+      'headID' => $this->getHeadID(),
+      'bodyID' => $this->getBodyID(),
+      'tailID' => $this->getTailID(),
+    );
+  }
+
+  public function render() {
+    require_celerity_resource('phui-formation-view-css');
+
+    $width = $this->getWidth();
+
+    $style = array();
+    $style[] = sprintf('width: %dpx;', $width);
+
+    $classes = array();
+    $classes[] = 'phui-flank-view';
+
+    if ($this->getIsFixed()) {
+      $classes[] = 'phui-flank-view-fixed';
+    }
+
+    $head_id = $this->getHeadID();
+    $body_id = $this->getBodyID();
+    $tail_id = $this->getTailID();
+
+    $head_content = phutil_tag(
+      'div',
+      array(
+        'class' => 'phui-flank-header',
+      ),
+      array(
+        phutil_tag(
+          'div',
+          array(
+            'class' => 'phui-flank-header-text',
+          ),
+          $this->getHeaderText()),
+        $this->newHideButton(),
+      ));
+
+    $content = phutil_tag(
+      'div',
+      array(
+        'id' => $this->getID(),
+        'class' => implode(' ', $classes),
+        'style' => implode(' ', $style),
+      ),
+      array(
+        phutil_tag(
+          'div',
+          array(
+            'id' => $head_id,
+            'class' => 'phui-flank-view-head',
+          ),
+          $head_content),
+        phutil_tag(
+          'div',
+          array(
+            'id' => $body_id,
+            'class' => 'phui-flank-view-body',
+          ),
+          $this->getBody()),
+        phutil_tag(
+          'div',
+          array(
+            'id' => $tail_id,
+            'class' => 'phui-flank-view-tail',
+          ),
+          $this->getTail()),
+      ));
+
+    return $content;
+  }
+
+  private function newHideButton() {
+    $item = $this->getColumnItem();
+    $is_right = $item->getIsRightAligned();
+
+    $hide_classes = array();
+    $hide_classes[] = 'phui-flank-header-hide';
+
+    if ($is_right) {
+      $hide_icon = id(new PHUIIconView())
+        ->setIcon('fa-chevron-right grey');
+      $hide_classes[] = 'phui-flank-header-hide-right';
+    } else {
+      $hide_icon = id(new PHUIIconView())
+        ->setIcon('fa-chevron-left grey');
+      $hide_classes[] = 'phui-flank-header-hide-left';
+    }
+
+    return javelin_tag(
+      'div',
+      array(
+        'sigil' => 'phui-flank-header-hide',
+        'class' => implode(' ', $hide_classes),
+      ),
+      $hide_icon);
+  }
+
+}
diff --git a/src/view/formation/PHUIFormationResizerView.php b/src/view/formation/PHUIFormationResizerView.php
new file mode 100644
--- /dev/null
+++ b/src/view/formation/PHUIFormationResizerView.php
@@ -0,0 +1,34 @@
+<?php
+
+final class PHUIFormationResizerView
+  extends PHUIFormationColumnView {
+
+  private $isVisible;
+
+  public function setIsVisible($is_visible) {
+    $this->isVisible = $is_visible;
+    return $this;
+  }
+
+  public function getIsVisible() {
+    return $this->isVisible;
+  }
+
+  public function getWidth() {
+    return 8;
+  }
+
+  public function render() {
+    $width = $this->getWidth();
+    $style = sprintf('width: %dpx;', $width);
+
+    return phutil_tag(
+      'div',
+      array(
+        'id' => $this->getID(),
+        'class' => 'phui-formation-resizer',
+        'style' => $style,
+      ));
+  }
+
+}
diff --git a/src/view/formation/PHUIFormationView.php b/src/view/formation/PHUIFormationView.php
new file mode 100644
--- /dev/null
+++ b/src/view/formation/PHUIFormationView.php
@@ -0,0 +1,188 @@
+<?php
+
+final class PHUIFormationView
+  extends AphrontView {
+
+  private $items = array();
+
+  public function newFlankColumn() {
+    $item = $this->newItem(new PHUIFormationFlankView());
+    return $item->getColumn();
+  }
+
+  public function newContentColumn() {
+    $item = $this->newItem(new PHUIFormationContentView());
+    return $item->getColumn();
+  }
+
+  private function newItem(PHUIFormationColumnView $column) {
+    $item = id(new PHUIFormationColumnItem())
+      ->setColumn($column);
+
+    $column->setColumnItem($item);
+
+    $this->items[] = $item;
+
+    return $item;
+  }
+
+  public function render() {
+    require_celerity_resource('phui-formation-view-css');
+
+    $items = $this->items;
+
+    $items = $this->generateControlBindings($items);
+    $items = $this->generateExpanders($items);
+    $items = $this->generateResizers($items);
+
+    $cells = array();
+    foreach ($items as $item) {
+      $style = array();
+
+      $column = $item->getColumn();
+
+      $width = $column->getWidth();
+      if ($width !== null) {
+        $style[] = sprintf('width: %dpx;', $width);
+      }
+
+      if (!$column->getIsVisible()) {
+        $style[] = 'display: none;';
+      }
+
+      $cells[] = phutil_tag(
+        'td',
+        array(
+          'id' => $item->getID(),
+          'style' => implode(' ', $style),
+        ),
+        array(
+          $column,
+          $item->getExpanders(),
+        ));
+    }
+
+    $formation_id = celerity_generate_unique_node_id();
+
+    $table_row = phutil_tag('tr', array(), $cells);
+    $table_body = phutil_tag('tbody', array(), $table_row);
+    $table = phutil_tag(
+      'table',
+      array(
+        'class' => 'phui-formation-view',
+        'id' => $formation_id,
+      ),
+      $table_body);
+
+    $phuix_columns = array();
+    foreach ($items as $item) {
+      $phuix_columns[] = $item->newClientProperties();
+    }
+
+    Javelin::initBehavior(
+      'phuix-formation-view',
+      array(
+        'nodeID' => $formation_id,
+        'columns' => $phuix_columns,
+      ));
+
+    return $table;
+  }
+
+  private function newColumnExpanderView() {
+    return new PHUIFormationExpanderView();
+  }
+
+  private function newResizerItem() {
+    return $this->newItem(new PHUIFormationResizerView());
+  }
+
+  private function generateControlBindings(array $items) {
+    $count = count($items);
+
+    if (!$count) {
+      return $items;
+    }
+
+    $last_control = null;
+
+    for ($ii = 0; $ii < $count; $ii++) {
+      $item = $items[$ii];
+      $column = $item->getColumn();
+
+      $is_control = $column->getIsControlColumn();
+      if ($is_control) {
+        $last_control = $ii;
+      }
+    }
+
+    if ($last_control === null) {
+      return $items;
+    }
+
+    for ($ii = ($count - 1); $ii >= 0; $ii--) {
+      $item = $items[$ii];
+      $column = $item->getColumn();
+
+      $is_control = $column->getIsControlColumn();
+      if ($is_control) {
+        $last_control = $ii;
+        continue;
+      }
+
+      $is_right = ($last_control < $ii);
+
+      $item
+        ->setControlItem($items[$last_control])
+        ->setIsRightAligned($is_right);
+    }
+
+    return $items;
+  }
+
+  private function generateResizers(array $items) {
+    $result = array();
+    foreach ($items as $item) {
+      $column = $item->getColumn();
+
+      $resizer_item = null;
+      if ($column->getIsResizable()) {
+        $resizer_item = $this->newResizerItem();
+        $item->setResizerItem($resizer_item);
+
+        $resizer_item
+          ->getColumn()
+          ->setIsVisible($column->getIsVisible());
+      }
+
+      if (!$resizer_item) {
+        $result[] = $item;
+      } else if ($item->getIsRightAligned()) {
+        $result[] = $resizer_item;
+        $result[] = $item;
+      } else {
+        $result[] = $item;
+        $result[] = $resizer_item;
+      }
+    }
+
+    return $result;
+  }
+
+  private function generateExpanders(array $items) {
+    foreach ($items as $item) {
+      $control_item = $item->getControlItem();
+      if ($control_item) {
+        $expander = $this->newColumnExpanderView();
+
+        $expander->setColumnItem($item);
+        $item->setExpander($expander);
+
+        $control_item->appendExpander($expander);
+      }
+    }
+
+    return $items;
+  }
+
+}
diff --git a/webroot/rsrc/css/phui/phui-formation-view.css b/webroot/rsrc/css/phui/phui-formation-view.css
new file mode 100644
--- /dev/null
+++ b/webroot/rsrc/css/phui/phui-formation-view.css
@@ -0,0 +1,145 @@
+/**
+ * @provides phui-formation-view-css
+ */
+
+.phui-formation-view {
+  table-layout: fixed;
+  width: 100%;
+}
+
+.phui-formation-view-expander {
+  position: fixed;
+  width: 24px;
+  height: 36px;
+  top: 64px;
+  border-style: solid;
+  box-shadow: 1px 1px 2px rgba(0, 0, 0, 0.1);
+  border-color: {$lightgreyborder};
+  background: {$lightgreybackground};
+  z-index: 4;
+}
+
+.phui-formation-view-expander-left {
+  border-radius: 0 12px 12px 0;
+  border-width: 1px 1px 1px 0;
+  cursor: e-resize;
+}
+
+.phui-formation-view-expander-right {
+  border-radius: 12px 0 0 12px;
+  border-width: 1px 0 1px 1px;
+  cursor: w-resize;
+}
+
+.phui-formation-view-expander-icon {
+  position: absolute;
+  width: 18px;
+  height: 18px;
+  top: 9px;
+  left: 3px;
+  text-align: center;
+}
+
+.device-desktop .phui-formation-view-expander:hover {
+  box-shadow: 2px 2px 2px rgba(0, 0, 0, 0.1);
+  background: {$darkgreybackground};
+  transition: 0.1s;
+}
+
+.device-desktop .phui-formation-view-expander:hover
+  .phui-icon-view {
+  color: {$bluetext};
+  transition: 0.1s;
+}
+
+.phui-flank-header {
+  padding: 8px;
+  background: {$greybackground};
+  border-bottom: 1px solid {$lightgreyborder};
+}
+
+.phui-flank-header-text {
+  color: {$darkgreytext};
+  font-weight: bold;
+}
+
+.phui-flank-header-hide {
+  font-size: {$normalfontsize};
+  position: absolute;
+  display: inline-block;
+  top: 6px;
+  right: 6px;
+  width: 20px;
+  height: 20px;
+  text-align: center;
+  border: 1px solid {$lightgreyborder};
+  border-radius: 4px;
+  line-height: 20px;
+}
+
+.phui-flank-header-hide-left {
+  cursor: w-resize;
+}
+
+
+.device-desktop .phui-flank-header-hide:hover {
+  box-shadow: inset 1px 1px 1px rgba(0, 0, 0, 0.05);
+  background: {$darkgreybackground};
+  transition: 0.1s;
+}
+
+.device-desktop .phui-flank-header-hide:hover
+  .phui-icon-view {
+  color: {$bluetext};
+  transition: 0.1s;
+}
+
+.phui-formation-resizer {
+  position: fixed;
+  top: 0;
+  bottom: 0;
+
+  cursor: col-resize;
+  background: #f5f5f5;
+  border-style: solid;
+  border-width: 0 1px 0 1px;
+  border-color: #fff #999c9e #fff #999c9e;
+  box-sizing: border-box;
+
+  box-shadow: inset -1px 0px 1px rgba({$alphablack}, 0.15);
+
+  background-image: url(/rsrc/image/divot.png);
+  background-position: center;
+  background-repeat: no-repeat;
+}
+
+.phui-flank-view-fixed {
+  position: fixed;
+  top: {$menu.main.height};
+  bottom: 0;
+  overflow: hidden;
+}
+
+.phui-flank-view-fixed .phui-flank-view-body {
+  overflow: hidden auto;
+}
+
+.device-desktop .phui-flank-view-fixed
+  .phui-flank-view-body::-webkit-scrollbar {
+  height: 6px;
+  width: 6px;
+  background: rgba(0, 0, 0, 0.1);
+  border-radius: 4px;
+}
+
+.device-desktop .phui-flank-view-fixed
+  .phui-flank-view-body::-webkit-scrollbar-thumb {
+  background: rgba(0, 0, 0, 0.25);
+  border-radius: 4px;
+}
+
+.phui-flank-view-fixed .phui-flank-view-tail {
+  position: absolute;
+  bottom: 0;
+  width: 100%;
+}
diff --git a/webroot/rsrc/js/phui/behavior-phuix-formation-view.js b/webroot/rsrc/js/phui/behavior-phuix-formation-view.js
new file mode 100644
--- /dev/null
+++ b/webroot/rsrc/js/phui/behavior-phuix-formation-view.js
@@ -0,0 +1,54 @@
+/**
+ * @provides javelin-behavior-phuix-formation-view
+ * @requires javelin-behavior
+ *           phuix-formation-view
+ *           phuix-formation-column-view
+ *           phuix-formation-flank-view
+ */
+
+JX.behavior('phuix-formation-view', function(config) {
+
+  var formation_node = JX.$(config.nodeID);
+  var formation = new JX.PHUIXFormationView(formation_node);
+
+  var count = config.columns.length;
+  for (var ii = 0; ii < count; ii++) {
+    var spec = config.columns[ii];
+    var node = JX.$(spec.itemID);
+
+    var column = new JX.PHUIXFormationColumnView(node)
+      .setIsRightAligned(spec.isRightAligned)
+      .setWidth(spec.width)
+      .setIsVisible(spec.isVisible);
+
+    if (spec.expanderID) {
+      column.setExpanderNode(JX.$(spec.expanderID));
+    }
+
+    if (spec.resizer) {
+      column
+        .setResizerItem(JX.$(spec.resizer.itemID))
+        .setResizerControl(JX.$(spec.resizer.controlID));
+    }
+
+    var colspec = spec.column;
+    if (colspec) {
+      if (colspec.type === 'flank') {
+        var flank_node = JX.$(colspec.nodeID);
+
+        var head = JX.$(colspec.headID);
+        var body = JX.$(colspec.bodyID);
+        var tail = JX.$(colspec.tailID);
+
+        var flank = new JX.PHUIXFormationFlankView(flank_node, head, body, tail)
+          .setIsFixed(colspec.isFixed);
+
+        column.setFlank(flank);
+      }
+    }
+
+    formation.addColumn(column);
+  }
+
+  formation.start();
+});
diff --git a/webroot/rsrc/js/phuix/PHUIXFormationColumnView.js b/webroot/rsrc/js/phuix/PHUIXFormationColumnView.js
new file mode 100644
--- /dev/null
+++ b/webroot/rsrc/js/phuix/PHUIXFormationColumnView.js
@@ -0,0 +1,174 @@
+/**
+ * @provides phuix-formation-column-view
+ * @requires javelin-install
+ *           javelin-dom
+ */
+
+JX.install('PHUIXFormationColumnView', {
+
+  construct: function(node) {
+    this._node = node;
+  },
+
+  properties: {
+    isRightAligned: false,
+    isVisible: true,
+    expanderNode: null,
+    resizerItem: null,
+    resizerControl: null,
+    width: null,
+    flank: null
+  },
+
+  members: {
+    _node: null,
+    _resizingWidth: null,
+    _resizingBarPosition: null,
+    _dragging: null,
+
+    start: function() {
+      var onshow = JX.bind(this, this._setVisibility, true);
+      var onhide = JX.bind(this, this._setVisibility, false);
+
+      JX.DOM.listen(this._node, 'click', 'phui-flank-header-hide', onhide);
+
+      var expander = this.getExpanderNode();
+      if (expander) {
+        JX.DOM.listen(expander, 'click', null, onshow);
+      }
+
+      var resizer = this.getResizerItem();
+      if (resizer) {
+        var ondown = JX.bind(this, this._onresizestart);
+        JX.DOM.listen(resizer, 'mousedown', null, ondown);
+
+        var onmove = JX.bind(this, this._onresizemove);
+        JX.Stratcom.listen('mousemove', null, onmove);
+
+        var onup = JX.bind(this, this._onresizeend);
+        JX.Stratcom.listen('mouseup', null, onup);
+      }
+
+      this.repaint();
+    },
+
+    _onresizestart: function(e) {
+      if (!e.isNormalMouseEvent()) {
+        return;
+      }
+
+      this._dragging = JX.$V(e);
+      this._resizingWidth = this.getWidth();
+      this._resizingBarPosition = JX.$V(this.getResizerControl());
+
+      // Show the "col-resize" cursor on the whole document while we're
+      // dragging, since the mouse will slip off the actual bar fairly often
+      // and we don't want it to flicker.
+      JX.DOM.alterClass(document.body, 'jx-drag-col', true);
+
+      e.kill();
+    },
+
+    _onresizemove: function(e) {
+      if (!this._dragging) {
+        return;
+      }
+
+      var dx = (JX.$V(e).x - this._dragging.x);
+
+      var width;
+      if (this.getIsRightAligned()) {
+        width = this.getWidth() - dx;
+      } else {
+        width = this.getWidth() + dx;
+      }
+
+      // TODO: Make these configurable?
+      width = Math.max(width, 150);
+      width = Math.min(width, 512);
+
+      this._resizingWidth = width;
+
+      this._node.style.width = this._resizingWidth + 'px';
+
+      var adjust_x = (this._resizingWidth - this.getWidth());
+      if (this.getIsRightAligned()) {
+        adjust_x = -adjust_x;
+      }
+
+      this.getResizerControl().style.left =
+        (this._resizingBarPosition.x + adjust_x) + 'px';
+
+      var flank = this.getFlank();
+      if (flank) {
+        flank
+          .setWidth(this._resizingWidth)
+          .repaint();
+      }
+    },
+
+    _onresizeend: function(e) {
+      if (!this._dragging) {
+        return;
+      }
+
+      this.setWidth(this._resizingWidth);
+
+      JX.log('new width is ' + this.getWidth());
+
+      JX.DOM.alterClass(document.body, 'jx-drag-col', false);
+      this._dragging = null;
+
+      // TODO: Save new width setting.
+
+    // new JX.Request('/settings/adjust/', JX.bag)
+    //   .setData(
+    //     {
+    //       key: 'filetree.width',
+    //       value: get_width()
+    //     })
+    //   .send();
+
+    },
+
+    _setVisibility: function(visible, e) {
+      e.kill();
+
+      // TODO: Save the visibility setting.
+
+      this.setIsVisible(visible);
+      this.repaint();
+    },
+
+    repaint: function() {
+      var resizer = this.getResizerItem();
+      var expander = this.getExpanderNode();
+
+      if (this.getIsVisible()) {
+        JX.DOM.show(this._node);
+        if (resizer) {
+          JX.DOM.show(resizer);
+        }
+        if (expander) {
+          JX.DOM.hide(expander);
+        }
+      } else {
+        JX.DOM.hide(this._node);
+        if (resizer) {
+          JX.DOM.hide(resizer);
+        }
+        if (expander) {
+          JX.DOM.show(expander);
+        }
+      }
+
+      if (this.getFlank()) {
+        this.getFlank().repaint();
+      }
+
+    },
+
+
+  }
+
+});
diff --git a/webroot/rsrc/js/phuix/PHUIXFormationFlankView.js b/webroot/rsrc/js/phuix/PHUIXFormationFlankView.js
new file mode 100644
--- /dev/null
+++ b/webroot/rsrc/js/phuix/PHUIXFormationFlankView.js
@@ -0,0 +1,57 @@
+/**
+ * @provides phuix-formation-flank-view
+ * @requires javelin-install
+ *           javelin-dom
+ */
+
+JX.install('PHUIXFormationFlankView', {
+
+  construct: function(node, head, body, tail) {
+    this._node = node;
+
+    this._headNode = head;
+    this._bodyNode = body;
+    this._tailNode = tail;
+  },
+
+  properties: {
+    isFixed: false,
+    bannerHeight: null,
+    width: null
+  },
+
+  members: {
+    _node: null,
+    _headNode: null,
+    _bodyNode: null,
+    _tailNode: null,
+
+    getBodyNode: function() {
+      return this._bodyNode;
+    },
+
+    getTailNode: function() {
+      return this._tailNode;
+    },
+
+    repaint: function()  {
+      if (!this.getIsFixed()) {
+        return;
+      }
+
+      this._node.style.top = this.getBannerHeight() + 'px';
+      this._node.style.width = this.getWidth() + 'px';
+
+      var body = this.getBodyNode();
+      var body_pos = JX.$V(body);
+
+      var tail = this.getTailNode();
+      var tail_pos = JX.$V(tail);
+
+      var max_height = (tail_pos.y - body_pos.y);
+
+      body.style.maxHeight = max_height + 'px';
+    }
+  }
+
+});
diff --git a/webroot/rsrc/js/phuix/PHUIXFormationView.js b/webroot/rsrc/js/phuix/PHUIXFormationView.js
new file mode 100644
--- /dev/null
+++ b/webroot/rsrc/js/phuix/PHUIXFormationView.js
@@ -0,0 +1,68 @@
+/**
+ * @provides phuix-formation-view
+ * @requires javelin-install
+ *           javelin-dom
+ */
+
+JX.install('PHUIXFormationView', {
+
+  construct: function() {
+    this._columns = [];
+  },
+
+  members: {
+    _columns: null,
+
+    addColumn: function(column) {
+      this._columns.push(column);
+    },
+
+    start: function() {
+      JX.enableDispatch(document.body, 'mousemove');
+
+      for (var ii = 0; ii < this._columns.length; ii++) {
+        this._columns[ii].start();
+      }
+
+      var repaint = JX.bind(this, this.repaint);
+      JX.Stratcom.listen(['scroll', 'resize'], null, repaint);
+
+      this.repaint();
+    },
+
+    repaint: function(e) {
+      // Unless we've scrolled past it, the page has a 44px main menu banner.
+      var menu_height = (44 - JX.Vector.getScroll().y);
+
+      // When the buoyant header is visible, move the menu down below it. This
+      // is a bit of a hack.
+      var banner_height = 0;
+      try {
+        var banner = JX.$('diff-banner');
+        banner_height = JX.Vector.getDim(banner).y;
+      } catch (error) {
+        // Ignore if there's no banner on the page.
+      }
+
+      var header_height = Math.max(0, menu_height, banner_height);
+
+      var column;
+      var flank;
+      for (var ii = 0; ii < this._columns.length; ii++) {
+        column = this._columns[ii];
+
+        flank = column.getFlank();
+        if (!flank) {
+          continue;
+        }
+
+        flank
+          .setBannerHeight(header_height)
+          .setWidth(column.getWidth())
+          .repaint();
+      }
+    }
+
+  }
+
+});