diff --git a/src/sort/MultiSort.js b/src/sort/MultiSort.js
new file mode 100644
index 0000000..37c7d35
--- /dev/null
+++ b/src/sort/MultiSort.js
@@ -0,0 +1,136 @@
+/**
+ * Sorting with multiple criteria
+ *
+ * Copyright (C) 2015 LoVullo Associates, Inc.
+ *
+ * This file is part of liza.
+ *
+ * liza is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ * @needsLove
+ * - References to "quote" should be replaced with generic terminology
+ * representing a document.
+ * - Dependencies need to be liberated:
+ * - ElementStyler;
+ * - BucketDataValidator.
+ * - Global references (e.g. jQuery) must be removed.
+ * - Checkbox-specific logic must be extracted.
+ * - This class is doing too much.
+ * @end needsLove
+ */
+
+var Class = require( 'easejs' ).Class;
+
+
+/**
+ * A simple recursive sorter with support for multiple criteria
+ *
+ * For simplicity's sake, this simply uses JavaScript's built-in sort()
+ * method using the supplied predicates. It then iterates through the sorted
+ * result and, using the supplied predicates, determines how the results
+ * should be grouped for sub-sorting. Because of this extra iteration, this
+ * isn't a very efficient algorithm, but it doesn't need to be for our
+ * purposes.
+ *
+ * Sorting is then performed recursively using the determined groups and the
+ * next provided predicate.
+ */
+module.exports = Class( 'MultiSort',
+{
+ /**
+ * Recursively sorts the given data using the provided predicates
+ *
+ * The predicate used depends on the depth of the sort. Results will be
+ * grouped according to similarity and recursively sorted until either no
+ * predicates remain or until the results are so dissimilar that they cannot
+ * be further sorted.
+ *
+ * @param {Array} data data to be sorted
+ * @param {Array.} preds predicates for arbitrary depth
+ *
+ * @return {Array} sorted data
+ */
+ 'public sort': function( data, preds )
+ {
+ // nothing can be done if we (a) don't have a length (non-array?), (b)
+ // the array is empty or (c) if we have no more preds
+ if ( ( preds.length === 0 ) || ( data.length < 2 ) )
+ {
+ return data;
+ }
+
+ var sorted = Array.prototype.slice.call( data ),
+ pred = preds[ 0 ],
+ next_preds = Array.prototype.slice.call( preds, 1 );
+
+ // sort according to the current predicate
+ sorted.sort( pred );
+
+ // if we cannot do any more sub-sorting, then simply return this sorted
+ // result
+ if ( preds.length === 1 )
+ {
+ return sorted;
+ }
+
+ return this._subsort( sorted, pred, next_preds );
+ },
+
+
+ /**
+ * Recursively sorts sorted results by grouping similar elements
+ */
+ 'private _subsort': function( sorted, pred, next_preds )
+ {
+ var i = 0,
+ len = sorted.length;
+
+ var result = [],
+ cur = [ sorted[ 0 ] ];
+
+
+ // note that this increment is intentional---at the bottom of this loop,
+ // we push the current element into the current group. Therefore, this
+ // extra step (past the end of the sorted array) ensures that the last
+ // element will be properly processed as part of the last group. The
+ // fact that we push undefined onto cur before returning is of no
+ // consequence.
+ while ( i++ < len )
+ {
+ // if we are at the last element in the array OR if the current
+ // element is to be sorted differently than the previous, process
+ // the current group of elements before continuing
+ if ( ( i === len )
+ || ( pred( sorted[ i - 1 ], sorted[ i ] ) !== 0 )
+ )
+ {
+ // the element is different; sub-sort
+ var sub = ( cur.length > 1 )
+ ? this.sort( cur, next_preds )
+ : cur;
+
+ for ( var j in sub )
+ {
+ result.push( sub[ j ] );
+ }
+
+ cur = [];
+ }
+
+ cur.push( sorted[ i ] );
+ }
+
+ return result;
+ }
+} );