diff --git src/wp-admin/customize.php src/wp-admin/customize.php
index 7828ee4..109bc07 100644
--- src/wp-admin/customize.php
+++ src/wp-admin/customize.php
@@ -53,8 +53,6 @@ do_action( 'customize_controls_init' );
 wp_enqueue_script( 'customize-controls' );
 wp_enqueue_style( 'customize-controls' );
 
-wp_enqueue_script( 'accordion' );
-
 /**
  * Enqueue Customizer control scripts.
  *
@@ -160,13 +158,9 @@ do_action( 'customize_controls_print_scripts' );
 				<?php endif; ?>
 			</div>
 
-			<div id="customize-theme-controls"><ul>
-				<?php
-				foreach ( $wp_customize->containers() as $container ) {
-					$container->maybe_render();
-				}
-				?>
-			</ul></div>
+			<div id="customize-theme-controls">
+				<ul><?php // Panels and sections are managed here via JavaScript ?></ul>
+			</div>
 		</div>
 		</div>
 
@@ -249,6 +243,8 @@ do_action( 'customize_controls_print_scripts' );
 		),
 		'settings' => array(),
 		'controls' => array(),
+		'panels'   => array(),
+		'sections' => array(),
 		'nonce'    => array(
 			'save'    => wp_create_nonce( 'save-customize_' . $wp_customize->get_stylesheet() ),
 			'preview' => wp_create_nonce( 'preview-customize_' . $wp_customize->get_stylesheet() )
@@ -263,10 +259,22 @@ do_action( 'customize_controls_print_scripts' );
 		);
 	}
 
-	// Prepare Customize Control objects to pass to Javascript.
+	// Prepare Customize Control objects to pass to JavaScript.
 	foreach ( $wp_customize->controls() as $id => $control ) {
-		$control->to_json();
-		$settings['controls'][ $id ] = $control->json;
+		$settings['controls'][ $id ] = $control->json();
+	}
+
+	// Prepare Customize Section objects to pass to JavaScript.
+	foreach ( $wp_customize->sections() as $id => $section ) {
+		$settings['sections'][ $id ] = $section->json();
+	}
+
+	// Prepare Customize Panel objects to pass to JavaScript.
+	foreach ( $wp_customize->panels() as $id => $panel ) {
+		$settings['panels'][ $id ] = $panel->json();
+		foreach ( $panel->sections as $section_id => $section ) {
+			$settings['sections'][ $section_id ] = $section->json();
+		}
 	}
 
 	?>
diff --git src/wp-admin/js/accordion.js src/wp-admin/js/accordion.js
index 6cb1c1c..63e14e8 100644
--- src/wp-admin/js/accordion.js
+++ src/wp-admin/js/accordion.js
@@ -58,8 +58,6 @@
 		});
 	});
 
-	var sectionContent = $( '.accordion-section-content' );
-
 	/**
 	 * Close the current accordion section and open a new one.
 	 *
@@ -69,7 +67,7 @@
 	function accordionSwitch ( el ) {
 		var section = el.closest( '.accordion-section' ),
 			siblings = section.closest( '.accordion-container' ).find( '.open' ),
-			content = section.find( sectionContent );
+			content = section.find( '.accordion-section-content' );
 
 		// This section has no content and cannot be expanded.
 		if ( section.hasClass( 'cannot-expand' ) ) {
@@ -87,7 +85,7 @@
 			content.toggle( true ).slideToggle( 150 );
 		} else {
 			siblings.removeClass( 'open' );
-			siblings.find( sectionContent ).show().slideUp( 150 );
+			siblings.find( '.accordion-section-content' ).show().slideUp( 150 );
 			content.toggle( false ).slideToggle( 150 );
 			section.toggleClass( 'open' );
 		}
@@ -125,7 +123,7 @@
 		} else {
 			// Close all open sections in any accordion level.
 			siblings.removeClass( 'open' );
-			siblings.find( sectionContent ).show().slideUp( 0 );
+			siblings.find( '.accordion-section-content' ).show().slideUp( 0 );
 			content.show( 0, function() {
 				position = content.offset().top;
 				scroll = container.scrollTop();
diff --git src/wp-admin/js/customize-controls.js src/wp-admin/js/customize-controls.js
index fad223e..fbfe90e 100644
--- src/wp-admin/js/customize-controls.js
+++ src/wp-admin/js/customize-controls.js
@@ -1,6 +1,6 @@
 /* globals _wpCustomizeHeader, _wpMediaViewsL10n */
 (function( exports, $ ){
-	var api = wp.customize;
+	var bubbleChildValueChanges, Container, api = wp.customize;
 
 	/**
 	 * @constructor
@@ -31,6 +31,476 @@
 	});
 
 	/**
+	 * Watch all changes to Value properties, and bubble changes to parent Values instance
+	 *
+	 * @param {wp.customize.Class} instance
+	 * @param {Array} properties  The names of the Value instances to watch.
+	 */
+	bubbleChildValueChanges = function ( instance, properties ) {
+		$.each( properties, function ( i, key ) {
+			instance[ key ].bind( function ( to, from ) {
+				if ( instance.parent && to !== from ) {
+					instance.parent.trigger( 'change', instance );
+				}
+			} );
+		} );
+	};
+
+	/**
+	 * Base class for Panel and Section
+	 *
+	 * @constructor
+	 * @augments wp.customize.Class
+	 */
+	Container = api.Class.extend({
+		slideSpeed: 150,
+
+		initialize: function ( id, options ) {
+			var self = this;
+			self.id = id;
+			self.params = {};
+			$.extend( self, options || {} );
+			self.container = $( self.params.content );
+
+			self.priority = new api.Value();
+			self.active = new api.Value();
+			self.expanded = new api.Value();
+
+			self.active.bind( function ( active ) {
+				if ( active && self.isContextuallyActive() ) {
+					self.onActivate();
+					// @todo Trigger 'activated' event?
+				} else {
+					self.onDeactivate();
+					// @todo Trigger 'deactivated' event?
+				}
+			});
+			self.expanded.bind( function ( expanded ) {
+				if ( expanded ) {
+					self.onExpand();
+					// @todo Trigger 'expanded' event?
+				} else {
+					self.onCollapse();
+					// @todo Trigger 'collapsed' event?
+				}
+			});
+
+			self.attachEvents();
+
+			bubbleChildValueChanges( self, [ 'priority', 'active' ] );
+
+			self.priority.set( self.params.priority || 100 );
+			self.active.set( true ); // @todo pass from params, eventually from active_callback when defining panel/section
+			self.expanded.set( false ); // @todo True if deeplinking?
+		},
+
+		/**
+		 * Get the child models associated with this parent, sorting them by their priority Value.
+		 *
+		 * @param {String} parentType
+		 * @param {String} childType
+		 * @returns {Array}
+		 */
+		_children: function ( parentType, childType ) {
+			var parent = this,
+				children = [];
+			api[ childType ].each( function ( child ) {
+				if ( child[ parentType ].get() === parent.id ) {
+					children.push( child );
+				}
+			} );
+			children.sort( function ( a, b ) {
+				return a.priority() - b.priority();
+			} );
+			return children;
+		},
+
+		/**
+		 * Change to the section's active state.
+		 *
+		 * @param {Boolean} active
+		 */
+		toggle: function ( active ) {
+			// @todo
+			if ( active ) {
+				this.deactivate();
+			} else {
+				this.activate();
+			}
+		},
+
+		isContextuallyActive: function () {
+			throw new Error( 'Must override with subclass.' );
+		},
+
+		/**
+		 *
+		 */
+		activate: function () {
+			this.active.set( true );
+			// @todo If it was already active, then re-trigger the events
+		},
+
+		/**
+		 *
+		 */
+		onActivate: function () {
+			// @todo Prevent this from proceeding if there are no active controls; as soon as a control becomes active, then this would automatically show
+			this.container.stop( true, true ).slideDown(); // @todo Trigger 'activated' event when complete?
+		},
+
+		/**
+		 *
+		 */
+		deactivate: function () {
+			this.active.set( false );
+			// @todo If it was already inactive, then re-trigger the events
+		},
+
+		/**
+		 *
+		 */
+		onDeactivate: function () {
+			this.onCollapse();
+			this.container.stop( true, true ).slideUp(); // @todo Trigger 'deactivated' event when complete?
+		},
+
+		/**
+		 *
+		 */
+		expand: function () {
+			this.expanded.set( true );
+		},
+
+		/**
+		 * Show the accordion section contents and collapse all other sections at the same time
+		 */
+		onExpand: function () {
+			throw new Error( 'onCollapse method must be overridden' );
+		},
+
+		/**
+		 *
+		 */
+		collapse: function () {
+			this.expanded( false );
+		},
+
+		/**
+		 *
+		 */
+		onCollapse: function () {
+			throw new Error( 'onCollapse method must be overridden' );
+		},
+
+		/**
+		 *
+		 */
+		focus: function () {
+			throw new Error( 'focus method must be overridden' );
+		}
+	});
+
+	/**
+	 * @constructor
+	 * @augments wp.customize.Class
+	 */
+	api.Section = Container.extend({
+
+		/**
+		 * @param {String} id
+		 * @param {Array} options
+		 */
+		initialize: function ( id, options ) {
+			var section = this;
+			Container.prototype.initialize.call( section, id, options );
+
+			section.panel = new api.Value();
+			section.panel.bind( function ( id ) {
+				$( section.container ).toggleClass( 'control-subsection', !! id );
+			});
+			section.panel.set( section.params.panel || '' );
+			bubbleChildValueChanges( section, [ 'panel' ] );
+		},
+
+		/**
+		 *
+		 */
+		embed: function ( readyCallback ) {
+			var panel_id,
+				section = this;
+
+			panel_id = this.panel.get();
+			if ( ! panel_id ) {
+				$( '#customize-theme-controls > ul' ).append( section.container );
+				readyCallback();
+			} else {
+				api.panel( panel_id, function ( panel ) {
+					panel.embed();
+					panel.container.find( 'ul:first' ).append( section.container );
+					readyCallback();
+				} );
+			}
+		},
+
+		/**
+		 * Add behaviors for the accordion section
+		 */
+		attachEvents: function () {
+			var section = this;
+
+			// Expand/Collapse accordion sections on click.
+			section.container.find( '.accordion-section-title' ).on( 'click keydown', function( e ) {
+				if ( e.type === 'keydown' && 13 !== e.which ) { // "return" key
+					return;
+				}
+				e.preventDefault(); // Keep this AFTER the key filter above
+
+				if ( section.expanded() ) {
+					section.collapse();
+				} else {
+					section.expand();
+				}
+			});
+		},
+
+		/**
+		 * Return whether this section has any active controls.
+		 *
+		 * @returns {boolean}
+		 */
+		isContextuallyActive: function () {
+			var section = this,
+				controls = section.controls(),
+				activeCount = 0;
+			_( controls ).each( function ( control ) {
+				if ( control.active() ) {
+					activeCount += 1;
+				}
+			} );
+			return ( activeCount !== 0 );
+		},
+
+		/**
+		 * Get the controls that are associated with this section, sorted by their priority Value.
+		 *
+		 * @returns {Array}
+		 */
+		controls: function () {
+			return this._children( 'section', 'control' );
+		},
+
+		/**
+		 * Show the accordion section contents and collapse all other sections at the same time
+		 */
+		onExpand: function () {
+			var section = this,
+				content = section.container.find( '.accordion-section-content' );
+
+			if ( section.panel() ) {
+				api.panel( section.panel() ).expand();
+			}
+
+			api.section.each( function ( otherSection ) {
+				if ( otherSection !== section ) {
+					otherSection.collapse();
+				}
+			});
+
+			content.stop().slideDown( section.slideSpeed );
+			section.container.addClass( 'open' );
+		},
+
+		/**
+		 *
+		 */
+		onCollapse: function () {
+			var section = this,
+				content = section.container.find( '.accordion-section-content' );
+
+			section.container.removeClass( 'open' );
+			content.slideUp( section.slideSpeed );
+		},
+
+		/**
+		 * Bring the containing panel into view and then expand this section and bring it into view
+		 *
+		 * @todo This is an alias for expand(); do we need it?
+		 */
+		focus: function () {
+			var section = this;
+			// @todo What if it is not active? Return false?
+			section.expand();
+		}
+	});
+
+	/**
+	 * @constructor
+	 * @augments wp.customize.Class
+	 */
+	api.Panel = Container.extend({
+		initialize: function ( id, options ) {
+			var panel = this;
+			Container.prototype.initialize.call( panel, id, options );
+		},
+
+		/**
+		 *
+		 */
+		embed: function ( readyCallback ) {
+			$( '#customize-theme-controls > ul' ).append( this.container );
+			if ( readyCallback ) {
+				readyCallback();
+			}
+		},
+
+		/**
+		 *
+		 */
+		attachEvents: function () {
+			var meta, panel = this;
+
+			// Expand/Collapse accordion sections on click.
+			panel.container.find( '.accordion-section-title' ).on( 'click keydown', function( e ) {
+				if ( e.type === 'keydown' && 13 !== e.which ) { // "return" key
+					return;
+				}
+				e.preventDefault(); // Keep this AFTER the key filter above
+
+				if ( ! panel.expanded() ) {
+					panel.expand();
+				}
+			});
+
+			meta = panel.container.find( '.panel-meta:first' );
+
+			meta.find( '> .accordion-section-title' ).on( 'click keydown', function( e ) {
+				if ( e.type === 'keydown' && 13 !== e.which ) { // "return" key
+					return;
+				}
+				e.preventDefault(); // Keep this AFTER the key filter above
+
+				if ( meta.hasClass( 'cannot-expand' ) ) {
+					return;
+				}
+
+				var content = meta.find( '.accordion-section-content:first' );
+				if ( meta.hasClass( 'open' ) ) {
+					meta.toggleClass( 'open' );
+					content.slideUp( 150 );
+				} else {
+					content.slideDown( 150 );
+					meta.toggleClass( 'open' );
+				}
+			});
+
+		},
+
+		/**
+		 * Get the sections that are associated with this panel, sorted by their priority Value.
+		 *
+		 * @returns {Array}
+		 */
+		sections: function () {
+			return this._children( 'panel', 'section' );
+		},
+
+		/**
+		 * Return whether this section has any active sections.
+		 *
+		 * @returns {boolean}
+		 */
+		isContextuallyActive: function () {
+			var panel = this,
+				sections = panel.sections(),
+				activeCount = 0;
+			_( sections ).each( function ( section ) {
+				if ( section.active() && section.isContextuallyActive() ) {
+					activeCount += 1;
+				}
+			} );
+			return ( activeCount !== 0 );
+		},
+
+		/**
+		 *
+		 */
+		onExpand: function () {
+
+			var position, scroll,
+				panel = this,
+				section = panel.container.closest( '.accordion-section' ),
+				overlay = section.closest( '.wp-full-overlay' ),
+				container = section.closest( '.accordion-container' ),
+				topPanel = overlay.find( '#customize-theme-controls > ul > .accordion-section > .accordion-section-title' ).add( '#customize-info > .accordion-section-title' ),
+				backBtn = overlay.find( '.control-panel-back' ),
+				content = section.find( '.control-panel-content' );
+
+			// Collapse any sibling sections/panels
+			api.section.each( function ( section ) {
+				if ( ! section.panel() ) {
+					section.collapse(); // @todo If any sections are open, then the position calculation below will fire too early
+				}
+			});
+			api.panel.each( function ( otherPanel ) {
+				if ( panel !== otherPanel ) {
+					otherPanel.collapse(); // @todo the position calculation below probably will fire too early
+				}
+			});
+
+			content.show( 0, function() {
+				position = content.offset().top;
+				scroll = container.scrollTop();
+				content.css( 'margin-top', ( 45 - position - scroll ) );
+				section.addClass( 'current-panel' );
+				overlay.addClass( 'in-sub-panel' );
+				container.scrollTop( 0 );
+			} );
+			topPanel.attr( 'tabindex', '-1' );
+			backBtn.attr( 'tabindex', '0' );
+			backBtn.focus();
+		},
+
+		/**
+		 *
+		 */
+		onCollapse: function () {
+
+			var panel = this,
+				section = panel.container.closest( '.accordion-section' ),
+				overlay = section.closest( '.wp-full-overlay' ),
+				container = section.closest( '.accordion-container' ),
+				siblings = container.find( '.open' ),
+				topPanel = overlay.find( '#customize-theme-controls > ul > .accordion-section > .accordion-section-title' ).add( '#customize-info > .accordion-section-title' ),
+				backBtn = overlay.find( '.control-panel-back' ),
+				panelTitle = section.find( '.accordion-section-title' ).first(),
+				content = section.find( '.control-panel-content' );
+
+			siblings.removeClass( 'open' );
+			section.removeClass( 'current-panel' );
+			overlay.removeClass( 'in-sub-panel' );
+			content.delay( 180 ).hide( 0, function() {
+				content.css( 'margin-top', 'inherit' ); // Reset
+			} );
+			topPanel.attr( 'tabindex', '0' );
+			backBtn.attr( 'tabindex', '-1' );
+			panelTitle.focus();
+			container.scrollTop( 0 );
+		},
+
+		/**
+		 * Bring the containing panel into view and then expand this section and bring it into view
+		 */
+		focus: function () {
+			var panel = this;
+			// @todo What if it is not active? Return false?
+			panel.expand();
+		}
+
+		// @todo Need to first exit out of the Panel
+	});
+
+	/**
 	 * @constructor
 	 * @augments wp.customize.Class
 	 */
@@ -44,7 +514,10 @@
 
 			this.id = id;
 			this.selector = '#customize-control-' + id.replace( /\]/g, '' ).replace( /\[/g, '-' );
-			this.container = $( this.selector );
+			this.container = this.params.content ? $( this.params.content ) : $( this.selector );
+
+			this.section = new api.Value( this.params.section );
+			this.priority = new api.Value( this.params.priority || 10 );
 			this.active = new api.Value( this.params.active );
 
 			settings = $.map( this.params.settings, function( value ) {
@@ -60,7 +533,9 @@
 				}
 
 				control.setting = control.settings['default'] || null;
-				control.ready();
+				control.embed( function () {
+					control.ready();
+				} );
 			}) );
 
 			control.elements = [];
@@ -74,8 +549,9 @@
 
 				if ( node.is(':radio') ) {
 					name = node.prop('name');
-					if ( radios[ name ] )
+					if ( radios[ name ] ) {
 						return;
+					}
 
 					radios[ name ] = true;
 					node = nodes.filter( '[name="' + name + '"]' );
@@ -93,6 +569,29 @@
 				control.toggle( active );
 			} );
 			control.toggle( control.active() );
+
+			bubbleChildValueChanges( control, [ 'section', 'priority', 'active' ] );
+		},
+
+		/**
+		 * @param {Function} [readyCallback] Callback to fire when the embedding is done.
+		 */
+		embed: function ( readyCallback ) {
+			var section_id,
+				control = this;
+
+			section_id = control.section.get();
+			if ( ! section_id ) {
+				throw new Error( 'A control must have an associated section.' );
+			}
+
+			// Defer until the associated section is available
+			api.section( section_id, function ( section ) {
+				section.embed( function () {
+					section.container.find( 'ul:first' ).append( control.container );
+					readyCallback();
+				} );
+			} );
 		},
 
 		/**
@@ -101,9 +600,14 @@
 		ready: function() {},
 
 		/**
-		 * Callback for change to the control's active state.
-		 *
-		 * Override function for custom behavior for the control being active/inactive.
+		 * Bring the containing section and panel into view and then this control into view, focusing on the first input
+		 */
+		focus: function () {
+			throw new Error( 'Not implemented yet' );
+		},
+
+		/**
+		 * Change to the control's active state.
 		 *
 		 * @param {Boolean} active
 		 */
@@ -575,6 +1079,8 @@
 
 	// Create the collection of Control objects.
 	api.control = new api.Values({ defaultConstructor: api.Control });
+	api.section = new api.Values({ defaultConstructor: api.Section });
+	api.panel = new api.Values({ defaultConstructor: api.Panel });
 
 	/**
 	 * @constructor
@@ -979,6 +1485,8 @@
 		image:  api.ImageControl,
 		header: api.HeaderControl
 	};
+	api.panelConstructor = {};
+	api.sectionConstructor = {};
 
 	$( function() {
 		api.settings = window._wpCustomizeSettings;
@@ -1009,6 +1517,29 @@
 			}
 		});
 
+		// Expand/Collapse the main customizer customize info
+		$( '#customize-info' ).find( '> .accordion-section-title' ).on( 'click keydown', function( e ) {
+			if ( e.type === 'keydown' && 13 !== e.which ) { // "return" key
+				return;
+			}
+			e.preventDefault(); // Keep this AFTER the key filter above
+
+			var section = $( this ).parent(),
+				content = section.find( '.accordion-section-content:first' );
+
+			if ( section.hasClass( 'cannot-expand' ) ) {
+				return;
+			}
+
+			if ( section.hasClass( 'open' ) ) {
+				section.toggleClass( 'open' );
+				content.slideUp( 150 );
+			} else {
+				content.slideDown( 150 );
+				section.toggleClass( 'open' );
+			}
+		});
+
 		// Initialize Previewer
 		api.previewer = new api.Previewer({
 			container:   '#customize-preview',
@@ -1102,6 +1633,7 @@
 			$.extend( this.nonce, nonce );
 		});
 
+		// Create Settings
 		$.each( api.settings.settings, function( id, data ) {
 			api.create( id, id, data.value, {
 				transport: data.transport,
@@ -1109,16 +1641,103 @@
 			} );
 		});
 
+		// Create Panels
+		$.each( api.settings.panels, function ( id, data ) {
+			var constructor = api.panelConstructor[ data.type ] || api.Panel,
+				panel;
+
+			panel = new constructor( id, {
+				params: data
+			} );
+			api.panel.add( id, panel );
+		});
+
+		// Create Sections
+		$.each( api.settings.sections, function ( id, data ) {
+			var constructor = api.sectionConstructor[ data.type ] || api.Section,
+				section;
+
+			section = new constructor( id, {
+				params: data
+			} );
+			api.section.add( id, section );
+		});
+
+		// Create Controls
+		// @todo factor this out
 		$.each( api.settings.controls, function( id, data ) {
 			var constructor = api.controlConstructor[ data.type ] || api.Control,
 				control;
 
-			control = api.control.add( id, new constructor( id, {
+			control = new constructor( id, {
 				params: data,
 				previewer: api.previewer
-			} ) );
+			} );
+			api.control.add( id, control );
 		});
 
+		/**
+		 * Sort panels, sections, controls by priorities. Hide empty sections and panels.
+		 */
+		api.reflowPaneContents = _.bind( function () {
+
+			var appendContainer, activeElement, rootNodes = [];
+
+			if ( document.activeElement ) {
+				activeElement = $( document.activeElement );
+			}
+
+			api.panel.each( function ( panel ) {
+				var sections = panel.sections();
+				rootNodes.push( panel );
+				appendContainer = panel.container.find( 'ul:first' );
+				_.chain( sections ).reverse().each( function ( section ) {
+					appendContainer.append( section.container );
+				} );
+			} );
+
+			api.section.each( function ( section ) {
+				var controls = section.controls();
+				if ( ! section.panel() ) {
+					rootNodes.push( section );
+				}
+				appendContainer = section.container.find( 'ul:first' );
+				_.chain( controls ).reverse().each( function ( control ) {
+					appendContainer.append( control.container );
+				} );
+			} );
+
+			// Sort the root elements
+			rootNodes.sort( function ( a, b ) {
+				return a.priority() - b.priority();
+			} );
+			appendContainer = $( '#customize-theme-controls > ul' );
+			_.chain( rootNodes ).each( function ( rootNode ) {
+				appendContainer.append( rootNode.container );
+			} );
+
+			// Now re-trigger the active Value callbacks to that the panels and sections can decide whether they can be rendered
+			api.panel.each( function ( panel ) {
+				var value = panel.active();
+				panel.active.callbacks.fireWith( panel.active, [ value, value ] );
+			} );
+			api.section.each( function ( section ) {
+				var value = section.active();
+				section.active.callbacks.fireWith( section.active, [ value, value ] );
+			} );
+
+			if ( activeElement ) {
+				activeElement.focus();
+			}
+		}, api );
+		api.reflowPaneContents = _.debounce( api.reflowPaneContents, 100 );
+		$( [ api.panel, api.section, api.control ] ).each( function ( i, values ) {
+			values.bind( 'add', api.reflowPaneContents );
+			values.bind( 'change', api.reflowPaneContents );
+			values.bind( 'remove', api.reflowPaneContents );
+		} );
+		api.bind( 'ready', api.reflowPaneContents );
+
 		// Check if preview url is valid and load the preview frame.
 		if ( api.previewer.previewUrl() ) {
 			api.previewer.refresh();
@@ -1183,6 +1802,18 @@
 			event.preventDefault();
 		});
 
+		// Go back to the top-level Customizer accordion.
+		$( '#customize-header-actions' ).on( 'click keydown', '.control-panel-back', function( e ) {
+			if ( e.type === 'keydown' && 13 !== e.which ) { // "return" key
+				return;
+			}
+
+			e.preventDefault(); // Keep this AFTER the key filter above
+			api.panel.each( function ( panel ) {
+				panel.collapse();
+			});
+		});
+
 		closeBtn.keydown( function( event ) {
 			if ( 9 === event.which ) // tab
 				return;
diff --git src/wp-includes/class-wp-customize-control.php src/wp-includes/class-wp-customize-control.php
index 7937d2d..d38d22f 100644
--- src/wp-includes/class-wp-customize-control.php
+++ src/wp-includes/class-wp-customize-control.php
@@ -74,6 +74,7 @@ class WP_Customize_Control {
 	public $input_attrs = array();
 
 	/**
+	 * @deprecated It is better to just call the json() method
 	 * @access public
 	 * @var array
 	 */
@@ -219,6 +220,20 @@ class WP_Customize_Control {
 
 		$this->json['type'] = $this->type;
 		$this->json['active'] = $this->active();
+		$this->json['section'] = $this->section;
+		$this->json['content'] = $this->get_content();
+	}
+
+	/**
+	 * Get the data to export to the client via JSON.
+	 *
+	 * @since 4.1.0
+	 *
+	 * @return array
+	 */
+	public function json() {
+		$this->to_json();
+		return $this->json;
 	}
 
 	/**
@@ -242,6 +257,21 @@ class WP_Customize_Control {
 	}
 
 	/**
+	 * Get the control's content for insertion into the Customizer pane.
+	 *
+	 * @since 4.1.0
+	 *
+	 * @return string
+	 */
+	public final function get_content() {
+		ob_start();
+		$this->maybe_render();
+		$template = trim( ob_get_contents() );
+		ob_end_clean();
+		return $template;
+	}
+
+	/**
 	 * Check capabilities and render the control.
 	 *
 	 * @since 3.4.0
diff --git src/wp-includes/class-wp-customize-manager.php src/wp-includes/class-wp-customize-manager.php
index febd8bc..5041f82 100644
--- src/wp-includes/class-wp-customize-manager.php
+++ src/wp-includes/class-wp-customize-manager.php
@@ -878,11 +878,11 @@ final class WP_Customize_Manager {
 
 			if ( ! $section->panel ) {
 				// Top-level section.
-				$sections[] = $section;
+				$sections[ $section->id ] = $section;
 			} else {
 				// This section belongs to a panel.
 				if ( isset( $this->panels [ $section->panel ] ) ) {
-					$this->panels[ $section->panel ]->sections[] = $section;
+					$this->panels[ $section->panel ]->sections[ $section->id ] = $section;
 				}
 			}
 		}
@@ -899,8 +899,8 @@ final class WP_Customize_Manager {
 				continue;
 			}
 
-			usort( $panel->sections, array( $this, '_cmp_priority' ) );
-			$panels[] = $panel;
+			uasort( $panel->sections, array( $this, '_cmp_priority' ) );
+			$panels[ $panel->id ] = $panel;
 		}
 		$this->panels = $panels;
 
diff --git src/wp-includes/class-wp-customize-panel.php src/wp-includes/class-wp-customize-panel.php
index f289cb7..9ba0295 100644
--- src/wp-includes/class-wp-customize-panel.php
+++ src/wp-includes/class-wp-customize-panel.php
@@ -83,6 +83,13 @@ class WP_Customize_Panel {
 	public $sections;
 
 	/**
+	 * @since 4.1.0
+	 * @access public
+	 * @var string
+	 */
+	public $type;
+
+	/**
 	 * Constructor.
 	 *
 	 * Any supplied $args override class property defaults.
@@ -110,6 +117,19 @@ class WP_Customize_Panel {
 	}
 
 	/**
+	 * Gather the parameters passed to client JavaScript via JSON.
+	 *
+	 * @since 4.1.0
+	 *
+	 * @return array The array to be exported to the client as JSON
+	 */
+	public function json() {
+		$array = wp_array_slice_assoc( (array) $this, array( 'title', 'description', 'priority', 'type' ) );
+		$array['content'] = $this->get_content();
+		return $array;
+	}
+
+	/**
 	 * Checks required user capabilities and whether the theme has the
 	 * feature support required by the panel.
 	 *
@@ -130,6 +150,21 @@ class WP_Customize_Panel {
 	}
 
 	/**
+	 * Get the panel's content template for insertion into the Customizer pane.
+	 *
+	 * @since 4.1.0
+	 *
+	 * @return string
+	 */
+	public final function get_content() {
+		ob_start();
+		$this->maybe_render();
+		$template = trim( ob_get_contents() );
+		ob_end_clean();
+		return $template;
+	}
+
+	/**
 	 * Check capabilities and render the panel.
 	 *
 	 * @since 4.0.0
@@ -189,7 +224,7 @@ class WP_Customize_Panel {
 	 */
 	protected function render_content() {
 		?>
-		<li class="accordion-section control-section<?php if ( empty( $this->description ) ) echo ' cannot-expand'; ?>">
+		<li class="panel-meta accordion-section control-section<?php if ( empty( $this->description ) ) { echo ' cannot-expand'; } ?>">
 			<div class="accordion-section-title" tabindex="0">
 				<span class="preview-notice"><?php
 					/* translators: %s is the site/panel title in the Customizer */
@@ -203,8 +238,5 @@ class WP_Customize_Panel {
 			<?php endif; ?>
 		</li>
 		<?php
-		foreach ( $this->sections as $section ) {
-			$section->maybe_render();
-		}
 	}
 }
diff --git src/wp-includes/class-wp-customize-section.php src/wp-includes/class-wp-customize-section.php
index d740ddb..a905f32 100644
--- src/wp-includes/class-wp-customize-section.php
+++ src/wp-includes/class-wp-customize-section.php
@@ -92,6 +92,13 @@ class WP_Customize_Section {
 	public $controls;
 
 	/**
+	 * @since 4.1.0
+	 * @access public
+	 * @var string
+	 */
+	public $type;
+
+	/**
 	 * Constructor.
 	 *
 	 * Any supplied $args override class property defaults.
@@ -118,6 +125,19 @@ class WP_Customize_Section {
 	}
 
 	/**
+	 * Gather the parameters passed to client JavaScript via JSON.
+	 *
+	 * @since 4.1.0
+	 *
+	 * @return array The array to be exported to the client as JSON
+	 */
+	public function json() {
+		$array = wp_array_slice_assoc( (array) $this, array( 'title', 'description', 'priority', 'panel', 'type' ) );
+		$array['content'] = $this->get_content();
+		return $array;
+	}
+
+	/**
 	 * Checks required user capabilities and whether the theme has the
 	 * feature support required by the section.
 	 *
@@ -136,6 +156,21 @@ class WP_Customize_Section {
 	}
 
 	/**
+	 * Get the section's content template for insertion into the Customizer pane.
+	 *
+	 * @since 4.1.0
+	 *
+	 * @return string
+	 */
+	public final function get_content() {
+		ob_start();
+		$this->maybe_render();
+		$template = trim( ob_get_contents() );
+		ob_end_clean();
+		return $template;
+	}
+
+	/**
 	 * Check capabilities and render the section.
 	 *
 	 * @since 3.4.0
@@ -172,9 +207,6 @@ class WP_Customize_Section {
 	 */
 	protected function render() {
 		$classes = 'control-section accordion-section';
-		if ( $this->panel ) {
-			$classes .= ' control-subsection';
-		}
 		?>
 		<li id="accordion-section-<?php echo esc_attr( $this->id ); ?>" class="<?php echo esc_attr( $classes ); ?>">
 			<h3 class="accordion-section-title" tabindex="0">
@@ -183,12 +215,10 @@ class WP_Customize_Section {
 			</h3>
 			<ul class="accordion-section-content">
 				<?php if ( ! empty( $this->description ) ) : ?>
-				<li><p class="description customize-section-description"><?php echo $this->description; ?></p></li>
+					<li class="customize-section-description-container">
+						<p class="description customize-section-description"><?php echo $this->description; ?></p>
+					</li>
 				<?php endif; ?>
-				<?php
-				foreach ( $this->controls as $control )
-					$control->maybe_render();
-				?>
 			</ul>
 		</li>
 		<?php
diff --git src/wp-includes/js/customize-base.js src/wp-includes/js/customize-base.js
index d2488dd..6ea2822 100644
--- src/wp-includes/js/customize-base.js
+++ src/wp-includes/js/customize-base.js
@@ -184,8 +184,9 @@ window.wp = window.wp || {};
 			to = this.validate( to );
 
 			// Bail if the sanitized value is null or unchanged.
-			if ( null === to || _.isEqual( from, to ) )
+			if ( null === to || _.isEqual( from, to ) ) {
 				return this;
+			}
 
 			this._value = to;
 			this._dirty = true;
