Index: src/wp-admin/customize.php
===================================================================
--- src/wp-admin/customize.php	(revision 31255)
+++ src/wp-admin/customize.php	(working copy)
@@ -184,7 +184,9 @@
 	<div id="customize-preview" class="wp-full-overlay-main"></div>
 	<?php
 
-	// Render control templates.
+	// Render Panel, Section, and Control templates.
+	$wp_customize->render_panel_templates();
+	$wp_customize->render_section_templates();
 	$wp_customize->render_control_templates();
 
 	/**
Index: src/wp-admin/js/customize-controls.js
===================================================================
--- src/wp-admin/js/customize-controls.js	(revision 31255)
+++ src/wp-admin/js/customize-controls.js	(working copy)
@@ -149,6 +149,7 @@
 	Container = api.Class.extend({
 		defaultActiveArguments: { duration: 'fast', completeCallback: $.noop },
 		defaultExpandedArguments: { duration: 'fast', completeCallback: $.noop },
+		containerType: 'container',
 
 		/**
 		 * @since 4.1.0
@@ -161,7 +162,11 @@
 			container.id = id;
 			container.params = {};
 			$.extend( container, options || {} );
+			container.templateSelector = 'customize-' + container.containerType + '-' + container.params.type;
 			container.container = $( container.params.content );
+			if ( 0 === container.container.length ) {
+				container.container = $( container.getContainer() );
+			}
 
 			container.deferred = {
 				embedded: new $.Deferred()
@@ -348,7 +353,26 @@
 		 * Bring the container into view and then expand this and bring it into view
 		 * @param {Object} [params]
 		 */
-		focus: focus
+		focus: focus,
+
+		/**
+		 * Return the container html, generated from its JS template, if it exists.
+		 *
+		 * @since 4.2.0
+		 */
+		getContainer: function () {
+			var template,
+				container = this;
+
+			if ( 0 !== $( '#tmpl-' + container.templateSelector ).length ) {
+				template = wp.template( container.templateSelector );
+				if ( template && container.container ) {
+					return template( container.params );
+				}
+			}
+
+			return '<li></li>';
+		}
 	});
 
 	/**
@@ -358,6 +382,7 @@
 	 * @augments wp.customize.Class
 	 */
 	api.Section = Container.extend({
+		containerType: 'section',
 
 		/**
 		 * @since 4.1.0
@@ -527,6 +552,8 @@
 	 * @augments wp.customize.Class
 	 */
 	api.Panel = Container.extend({
+		containerType: 'panel',
+
 		/**
 		 * @since 4.1.0
 		 *
@@ -553,6 +580,7 @@
 
 			if ( ! panel.container.parent().is( parentContainer ) ) {
 				parentContainer.append( panel.container );
+				panel.renderContent();
 			}
 			panel.deferred.embedded.resolve();
 		},
@@ -575,14 +603,13 @@
 				}
 			});
 
-			meta = panel.container.find( '.panel-meta:first' );
-
-			meta.find( '> .accordion-section-title' ).on( 'click keydown', function( event ) {
+			panel.container.on( 'click keydown', '.panel-meta > .accordion-section-title', function( event ) {
 				if ( api.utils.isKeydownButNotEnterEvent( event ) ) {
 					return;
 				}
 				event.preventDefault(); // Keep this AFTER the key filter above
 
+				meta = panel.container.find( '.panel-meta' );
 				if ( meta.hasClass( 'cannot-expand' ) ) {
 					return;
 				}
@@ -704,6 +731,26 @@
 				panelTitle.focus();
 				container.scrollTop( 0 );
 			}
+		},
+
+		/**
+		 * Render the panel from its JS template, if it exists.
+		 *
+		 * The panel's container must already exist in the DOM.
+		 *
+		 * @since 4.2.0
+		 */
+		renderContent: function () {
+			var template,
+				panel = this;
+
+			// Add the content to the container.
+			if ( 0 !== $( '#tmpl-' + panel.templateSelector + '-content' ).length ) {
+				template = wp.template( panel.templateSelector + '-content' );
+				if ( template && panel.container ) {
+					panel.container.find( '.accordion-sub-container' ).html( template( panel.params ) );
+				}
+			}
 		}
 	});
 
Index: src/wp-includes/class-wp-customize-control.php
===================================================================
--- src/wp-includes/class-wp-customize-control.php	(revision 31255)
+++ src/wp-includes/class-wp-customize-control.php	(working copy)
@@ -259,6 +259,10 @@
 	 * @return array Array of parameters passed to the JavaScript.
 	 */
 	public function json() {
+		if ( ! $this->check_capabilities() ) {
+			return;
+		}
+
 		$this->to_json();
 		return $this->json;
 	}
@@ -515,11 +519,11 @@
 	 * @since 4.1.0
 	 */
 	final public function print_template() {
-	        ?>
-	        <script type="text/html" id="tmpl-customize-control-<?php echo $this->type; ?>-content">
-	                <?php $this->content_template(); ?>
-	        </script>
-	        <?php
+		?>
+		<script type="text/html" id="tmpl-customize-control-<?php echo $this->type; ?>-content">
+			<?php $this->content_template(); ?>
+		</script>
+		<?php
 	}
 
 	/**
Index: src/wp-includes/class-wp-customize-manager.php
===================================================================
--- src/wp-includes/class-wp-customize-manager.php	(revision 31255)
+++ src/wp-includes/class-wp-customize-manager.php	(working copy)
@@ -54,8 +54,26 @@
 	protected $customized;
 
 	/**
-	 * Controls that may be rendered from JS templates.
+	 * Panel types that may be rendered from JS templates.
 	 *
+	 * @since 4.2.0
+	 * @access protected
+	 * @var array
+	 */
+	protected $registered_panel_types = array();
+
+	/**
+	 * Section types that may be rendered from JS templates.
+	 *
+	 * @since 4.2.0
+	 * @access protected
+	 * @var array
+	 */
+	protected $registered_section_types = array();
+
+	/**
+	 * Control types that may be rendered from JS templates.
+	 *
 	 * @since 4.1.0
 	 * @access protected
 	 * @var array
@@ -774,6 +792,34 @@
 	}
 
 	/**
+	 * Register a customize panel type.
+	 *
+	 * Registered types are eligible to be rendered via JS and created dynamically.
+	 *
+	 * @since 4.2.0
+	 * @access public
+	 *
+	 * @param string $panel Name of a custom panel which is a subclass of
+	 *                        {@see WP_Customize_Panel}.
+	 */
+	public function register_panel_type( $panel ) {
+		$this->registered_panel_types[] = $panel;
+	}
+
+	/**
+	 * Render JS templates for all registered panel types.
+	 *
+	 * @since 4.2.0
+	 * @access public
+	 */
+	public function render_panel_templates() {
+		foreach ( $this->registered_panel_types as $panel_type ) {
+			$panel = new $panel_type( $this, 'temp', array() );
+			$panel->print_template();
+		}
+	}
+
+	/**
 	 * Add a customize section.
 	 *
 	 * @since 3.4.0
@@ -815,6 +861,34 @@
 	}
 
 	/**
+	 * Register a customize section type.
+	 *
+	 * Registered types are eligible to be rendered via JS and created dynamically.
+	 *
+	 * @since 4.2.0
+	 * @access public
+	 *
+	 * @param string $section Name of a custom section which is a subclass of
+	 *                        {@see WP_Customize_Section}.
+	 */
+	public function register_section_type( $section ) {
+		$this->registered_section_types[] = $section;
+	}
+
+	/**
+	 * Render JS templates for all registered section types.
+	 *
+	 * @since 4.2.0
+	 * @access public
+	 */
+	public function render_section_templates() {
+		foreach ( $this->registered_section_types as $section_type ) {
+			$section = new $section_type( $this, 'temp', array() );
+			$section->print_template();
+		}
+	}
+
+	/**
 	 * Add a customize control.
 	 *
 	 * @since 3.4.0
@@ -985,7 +1059,10 @@
 	 */
 	public function register_controls() {
 
-		/* Control Types (custom control classes) */
+		/* Panel, Section, and Control Types */
+		$this->register_panel_type( 'WP_Customize_Panel' );
+		$this->register_section_type( 'WP_Customize_Section' );
+		$this->register_section_type( 'WP_Customize_Sidebar_Section' );
 		$this->register_control_type( 'WP_Customize_Color_Control' );
 		$this->register_control_type( 'WP_Customize_Upload_Control' );
 		$this->register_control_type( 'WP_Customize_Image_Control' );
Index: src/wp-includes/class-wp-customize-panel.php
===================================================================
--- src/wp-includes/class-wp-customize-panel.php	(revision 31255)
+++ src/wp-includes/class-wp-customize-panel.php	(working copy)
@@ -204,7 +204,11 @@
 	 * @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' ) );
+		if ( ! $this->check_capabilities() ) {
+			return;
+		}
+
+		$array = wp_array_slice_assoc( (array) $this, array( 'id', 'title', 'description', 'priority', 'type' ) );
 		$array['content'] = $this->get_content();
 		$array['active'] = $this->active();
 		$array['instanceNumber'] = $this->instance_number;
@@ -279,46 +283,90 @@
 	}
 
 	/**
-	 * Render the panel container, and then its contents.
+	 * Render the panel container, and then its contents (via `this->render_content()`) in a subclass.
 	 *
+	 * Panel containers are now rendered in JS by default, see {@see WP_Customize_Panel::print_template()}.
+	 *
 	 * @since 4.0.0
 	 * @access protected
 	 */
-	protected function render() {
-		$classes = 'accordion-section control-section control-panel control-panel-' . $this->type;
+	protected function render() {}
+
+	/**
+	 * Render the panel UI in a subclass.
+	 *
+	 * Panel contents are now rendered in JS by default, see {@see WP_Customize_Panel::print_template()}.
+	 *
+	 * @since 4.1.0
+	 * @access protected
+	 */
+	protected function render_content() {}
+
+	/**
+	 * Render the panel's JS templates.
+	 *
+	 * This function is only run for panel types that have been registered with
+	 * {@see WP_Customize_Manager::register_panel_type()}.
+	 *
+	 * @since 4.2.0
+	 */
+	public function print_template() {
 		?>
-		<li id="accordion-panel-<?php echo esc_attr( $this->id ); ?>" class="<?php echo esc_attr( $classes ); ?>">
+		<script type="text/html" id="tmpl-customize-panel-<?php echo $this->type; ?>-content">
+			<?php $this->content_template(); ?>
+		</script>
+		<script type="text/html" id="tmpl-customize-panel-<?php echo $this->type; ?>">
+			<?php $this->render_template(); ?>
+		</script>
+        <?php
+	}
+
+	/**
+	 * An Underscore (JS) template for rendering this panel's container.
+	 *
+	 * Class variables for this panel class are available in the `data` JS object;
+	 * export custom variables by overriding {@see WP_Customize_Panel::json()}.
+	 *
+	 * @see WP_Customize_Panel::print_template()
+	 *
+	 * @since 4.2.0
+	 */
+	protected function render_template() {
+		?>
+		<li id="accordion-panel-{{ data.id }}" class="accordion-section control-section control-panel control-panel-{{ data.type }}">
 			<h3 class="accordion-section-title" tabindex="0">
-				<?php echo esc_html( $this->title ); ?>
+				{{ data.title }}
 				<span class="screen-reader-text"><?php _e( 'Press return or enter to open this panel' ); ?></span>
 			</h3>
-			<ul class="accordion-sub-container control-panel-content">
-				<?php $this->render_content(); ?>
-			</ul>
+			<ul class="accordion-sub-container control-panel-content"></ul>
 		</li>
 		<?php
 	}
 
 	/**
-	 * Render the sections that have been added to the panel.
+	 * An Underscore (JS) template for this panel's content (but not its container).
 	 *
-	 * @since 4.1.0
-	 * @access protected
+	 * Class variables for this panel class are available in the `data` JS object;
+	 * export custom variables by overriding {@see WP_Customize_Panel::json()}.
+	 *
+	 * @see WP_Customize_Panel::print_template()
+	 *
+	 * @since 4.2.0
 	 */
-	protected function render_content() {
+	protected function content_template() {
 		?>
-		<li class="panel-meta accordion-section control-section<?php if ( empty( $this->description ) ) { echo ' cannot-expand'; } ?>">
+		<li class="panel-meta accordion-section control-section<# if ( ! data.description ) { #> cannot-expand<# } #>">
 			<div class="accordion-section-title" tabindex="0">
 				<span class="preview-notice"><?php
 					/* translators: %s is the site/panel title in the Customizer */
-					echo sprintf( __( 'You are customizing %s' ), '<strong class="panel-title">' . esc_html( $this->title ) . '</strong>' );
+					echo sprintf( __( 'You are customizing %s' ), '<strong class="panel-title">{{ data.title }}</strong>' );
 				?></span>
 			</div>
-			<?php if ( ! empty( $this->description ) ) : ?>
+			<# if ( data.description ) { #>
 				<div class="accordion-section-content description">
-					<?php echo $this->description; ?>
+					{{{ data.description }}}
 				</div>
-			<?php endif; ?>
+			<# } #>
 		</li>
 		<?php
 	}
Index: src/wp-includes/class-wp-customize-section.php
===================================================================
--- src/wp-includes/class-wp-customize-section.php	(revision 31255)
+++ src/wp-includes/class-wp-customize-section.php	(working copy)
@@ -213,7 +213,11 @@
 	 * @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' ) );
+		if ( ! $this->check_capabilities() ) {
+			return;
+		}
+
+		$array = wp_array_slice_assoc( (array) $this, array( 'id', 'title', 'description', 'priority', 'panel', 'type' ) );
 		$array['content'] = $this->get_content();
 		$array['active'] = $this->active();
 		$array['instanceNumber'] = $this->instance_number;
@@ -241,7 +245,7 @@
 	}
 
 	/**
-	 * Get the section's content template for insertion into the Customizer pane.
+	 * Get the section's content for insertion into the Customizer pane.
 	 *
 	 * @since 4.1.0
 	 *
@@ -287,24 +291,53 @@
 	}
 
 	/**
-	 * Render the section, and the controls that have been added to it.
+	 * Render the section UI in a subclass.
 	 *
+	 * Sections are now rendered in JS by default, see {@see WP_Customize_Section::print_template()}.
+	 *
 	 * @since 3.4.0
 	 */
-	protected function render() {
-		$classes = 'accordion-section control-section control-section-' . $this->type;
+	protected function render() {}
+
+	/**
+	 * Render the section's JS template.
+	 *
+	 * This function is only run for section types that have been registered with
+	 * {@see WP_Customize_Manager::register_section_type()}.
+	 *
+	 * @since 4.2.0
+	 */
+	public function print_template() {
+        ?>
+		<script type="text/html" id="tmpl-customize-section-<?php echo $this->type; ?>">
+			<?php $this->render_template(); ?>
+		</script>
+        <?php
+	}
+
+	/**
+	 * An Underscore (JS) template for rendering this section.
+	 *
+	 * Class variables for this section class are available in the `data` JS object;
+	 * export custom variables by overriding {@see WP_Customize_Section::json()}.
+	 *
+	 * @see WP_Customize_Section::print_template()
+	 *
+	 * @since 4.2.0
+	 */
+	protected function render_template() {
 		?>
-		<li id="accordion-section-<?php echo esc_attr( $this->id ); ?>" class="<?php echo esc_attr( $classes ); ?>">
+		<li id="accordion-section-{{ data.id }}" class="accordion-section control-section control-section-{{ data.type }}">
 			<h3 class="accordion-section-title" tabindex="0">
-				<?php echo esc_html( $this->title ); ?>
+				{{ data.title }}
 				<span class="screen-reader-text"><?php _e( 'Press return or enter to expand' ); ?></span>
 			</h3>
 			<ul class="accordion-section-content">
-				<?php if ( ! empty( $this->description ) ) : ?>
+				<# if ( data.description ) { #>
 					<li class="customize-section-description-container">
-						<p class="description customize-section-description"><?php echo $this->description; ?></p>
+						<p class="description customize-section-description">{{{ data.description }}}</p>
 					</li>
-				<?php endif; ?>
+				<# } #>
 			</ul>
 		</li>
 		<?php
