diff --git src/wp-admin/js/customize-controls.js src/wp-admin/js/customize-controls.js
index b483aea335..4348fee7ec 100644
--- src/wp-admin/js/customize-controls.js
+++ src/wp-admin/js/customize-controls.js
@@ -216,6 +216,7 @@
 			setting.id = id;
 			setting.transport = setting.transport || 'refresh';
 			setting._dirty = options.dirty || false;
+			setting['default'] = options['default'];
 			setting.notifications = new api.Values({ defaultConstructor: api.Notification });
 
 			// Whenever the setting's value changes, refresh the preview.
@@ -2089,8 +2090,7 @@
 		defaultActiveArguments: { duration: 'fast', completeCallback: $.noop },
 
 		initialize: function( id, options ) {
-			var control = this,
-				nodes, radios, settings;
+			var control = this, settings, linkNodes;
 
 			control.params = {};
 			$.extend( control, options || {} );
@@ -2112,30 +2112,39 @@
 
 			control.elements = [];
 
-			nodes  = control.container.find('[data-customize-setting-link]');
-			radios = {};
+			linkNodes = function() {
+				var nodes, radios;
+				nodes  = control.container.find( '[data-customize-setting-link]' );
+				radios = {};
 
-			nodes.each( function() {
-				var node = $( this ),
-					name;
+				nodes.each( function() {
+					var node = $( this ),
+						name;
 
-				if ( node.is( ':radio' ) ) {
-					name = node.prop( 'name' );
-					if ( radios[ name ] ) {
+					if ( node.data( 'customizeSettingLinkAdded' ) ) {
 						return;
 					}
 
-					radios[ name ] = true;
-					node = nodes.filter( '[name="' + name + '"]' );
-				}
+					if ( node.is( ':radio' ) ) {
+						name = node.prop( 'name' );
+						if ( radios[ name ] ) {
+							return;
+						}
+
+						radios[ name ] = true;
+						node = nodes.filter( '[name="' + name + '"]' );
+					}
 
-				api( node.data( 'customizeSettingLink' ), function( setting ) {
-					var element = new api.Element( node );
-					control.elements.push( element );
-					element.sync( setting );
-					element.set( setting() );
+					node.data( 'customizeSettingLinkAdded', true );
+					api( node.data( 'customizeSettingLink' ), function( setting ) {
+						var element = new api.Element( node );
+						control.elements.push( element );
+						element.sync( setting );
+						element.set( setting() );
+					});
 				});
-			});
+			};
+			linkNodes(); // Call early for back-compat in case no content template is being used.
 
 			control.active.bind( function ( active ) {
 				var args = control.activeArgumentsQueue.shift();
@@ -2198,6 +2207,8 @@
 
 			// After the control is embedded on the page, invoke the "ready" method.
 			control.deferred.embedded.done( function () {
+				linkNodes(); // Link nodes that were added via content template.
+
 				control.setupNotifications();
 				control.ready();
 			});
@@ -2515,12 +2526,19 @@
 			var template,
 				control = this;
 
+			// If there's already content, bail.
+			if ( control.container.is( ':empty' ) ) {
+				return;
+			}
 			// Replace the container element's content with the control.
 			if ( 0 !== $( '#tmpl-' + control.templateSelector ).length ) {
 				template = wp.template( control.templateSelector );
-				if ( template && control.container ) {
-					control.container.html( template( control.params ) );
-				}
+			} else {
+				template = wp.template( 'customize-control-default-content' );
+			}
+
+			if ( template && control.container ) {
+				control.container.html( template( control.params ) );
 			}
 		},
 
@@ -4909,11 +4927,11 @@
 			var constructor = api.settingConstructor[ data.type ] || api.Setting,
 				setting;
 
-			setting = new constructor( id, data.value, {
-				transport: data.transport,
-				previewer: api.previewer,
-				dirty: !! data.dirty
-			} );
+			setting = new constructor( id, data.value, _.extend(
+				{},
+				data,
+				{ previewer: api.previewer }
+			) );
 			api.add( id, setting );
 		});
 
@@ -5648,22 +5666,25 @@
 
 		// Juggle the two controls that use header_textcolor
 		api.control( 'display_header_text', function( control ) {
-			var last = '';
+			control.deferred.embedded.done( function() { // @todo How to guarantee that control.elements will be populated?
+				var last = '';
 
-			control.elements[0].unsync( api( 'header_textcolor' ) );
+				control.elements[0].unsync( api( 'header_textcolor' ) );
 
-			control.element = new api.Element( control.container.find('input') );
-			control.element.set( 'blank' !== control.setting() );
+				control.element = new api.Element( control.container.find( 'input' ) );
+				control.element.set( 'blank' !== control.setting() );
 
-			control.element.bind( function( to ) {
-				if ( ! to )
-					last = api( 'header_textcolor' ).get();
+				control.element.bind( function( to ) {
+					if ( ! to ) {
+						last = api( 'header_textcolor' ).get();
+					}
 
-				control.setting.set( to ? last : 'blank' );
-			});
+					control.setting.set( to ? last : 'blank' );
+				});
 
-			control.setting.bind( function( to ) {
-				control.element.set( 'blank' !== to );
+				control.setting.bind( function( to ) {
+					control.element.set( 'blank' !== to );
+				});
 			});
 		});
 
diff --git src/wp-includes/class-wp-customize-control.php src/wp-includes/class-wp-customize-control.php
index b9c83c7575..8f7b4ae505 100644
--- src/wp-includes/class-wp-customize-control.php
+++ src/wp-includes/class-wp-customize-control.php
@@ -316,6 +316,10 @@ class WP_Customize_Control {
 		$this->json['label'] = $this->label;
 		$this->json['description'] = $this->description;
 		$this->json['instanceNumber'] = $this->instance_number;
+		$this->json['value'] = $this->value();
+		$this->json['link'] = $this->get_link();
+		$this->json['choices'] = $this->choices;
+		$this->json['inputAttrs'] = $this->get_input_attrs();
 
 		if ( 'dropdown-pages' === $this->type ) {
 			$this->json['allow_addition'] = $this->allow_addition;
@@ -459,9 +463,21 @@ class WP_Customize_Control {
 	 * @since 4.0.0
 	 */
 	public function input_attrs() {
-		foreach ( $this->input_attrs as $attr => $value ) {
-			echo $attr . '="' . esc_attr( $value ) . '" ';
+		echo $this->get_input_attrs();
+	}
+
+	/**
+	 * Return html for the custom attributes for the control's input element.
+	 *
+	 * @since 4.3.0
+	 * @return string Input attributes html.
+	 */
+	public function get_input_attrs() {
+		$attrs = '';
+		foreach( $this->input_attrs as $attr => $value ) {
+			$attrs .= $attr . '="' . esc_attr( $value ) . '" ';
 		}
+		return $attrs;
 	}
 
 	/**
@@ -475,77 +491,10 @@ class WP_Customize_Control {
 	 * Control content can alternately be rendered in JS. See WP_Customize_Control::print_template().
 	 *
 	 * @since 3.4.0
+	 * @since 4.3.0 Most core control types are rendered with a content template instead.
 	 */
 	protected function render_content() {
 		switch( $this->type ) {
-			case 'checkbox':
-				?>
-				<label>
-					<input type="checkbox" value="<?php echo esc_attr( $this->value() ); ?>" <?php $this->link(); checked( $this->value() ); ?> />
-					<?php echo esc_html( $this->label ); ?>
-					<?php if ( ! empty( $this->description ) ) : ?>
-						<span class="description customize-control-description"><?php echo $this->description; ?></span>
-					<?php endif; ?>
-				</label>
-				<?php
-				break;
-			case 'radio':
-				if ( empty( $this->choices ) )
-					return;
-
-				$name = '_customize-radio-' . $this->id;
-
-				if ( ! empty( $this->label ) ) : ?>
-					<span class="customize-control-title"><?php echo esc_html( $this->label ); ?></span>
-				<?php endif;
-				if ( ! empty( $this->description ) ) : ?>
-					<span class="description customize-control-description"><?php echo $this->description ; ?></span>
-				<?php endif;
-
-				foreach ( $this->choices as $value => $label ) :
-					?>
-					<label>
-						<input type="radio" value="<?php echo esc_attr( $value ); ?>" name="<?php echo esc_attr( $name ); ?>" <?php $this->link(); checked( $this->value(), $value ); ?> />
-						<?php echo esc_html( $label ); ?><br/>
-					</label>
-					<?php
-				endforeach;
-				break;
-			case 'select':
-				if ( empty( $this->choices ) )
-					return;
-
-				?>
-				<label>
-					<?php if ( ! empty( $this->label ) ) : ?>
-						<span class="customize-control-title"><?php echo esc_html( $this->label ); ?></span>
-					<?php endif;
-					if ( ! empty( $this->description ) ) : ?>
-						<span class="description customize-control-description"><?php echo $this->description; ?></span>
-					<?php endif; ?>
-
-					<select <?php $this->link(); ?>>
-						<?php
-						foreach ( $this->choices as $value => $label )
-							echo '<option value="' . esc_attr( $value ) . '"' . selected( $this->value(), $value, false ) . '>' . $label . '</option>';
-						?>
-					</select>
-				</label>
-				<?php
-				break;
-			case 'textarea':
-				?>
-				<label>
-					<?php if ( ! empty( $this->label ) ) : ?>
-						<span class="customize-control-title"><?php echo esc_html( $this->label ); ?></span>
-					<?php endif;
-					if ( ! empty( $this->description ) ) : ?>
-						<span class="description customize-control-description"><?php echo $this->description; ?></span>
-					<?php endif; ?>
-					<textarea rows="5" <?php $this->input_attrs(); ?> <?php $this->link(); ?>><?php echo esc_textarea( $this->value() ); ?></textarea>
-				</label>
-				<?php
-				break;
 			case 'dropdown-pages':
 				?>
 				<label>
@@ -609,19 +558,6 @@ class WP_Customize_Control {
 					</div>
 				<?php endif;
 				break;
-			default:
-				?>
-				<label>
-					<?php if ( ! empty( $this->label ) ) : ?>
-						<span class="customize-control-title"><?php echo esc_html( $this->label ); ?></span>
-					<?php endif;
-					if ( ! empty( $this->description ) ) : ?>
-						<span class="description customize-control-description"><?php echo $this->description; ?></span>
-					<?php endif; ?>
-					<input type="<?php echo esc_attr( $this->type ); ?>" <?php $this->input_attrs(); ?> value="<?php echo esc_attr( $this->value() ); ?>" <?php $this->link(); ?> />
-				</label>
-				<?php
-				break;
 		}
 	}
 
@@ -632,7 +568,7 @@ class WP_Customize_Control {
 	 * WP_Customize_Manager::register_control_type().
 	 *
 	 * In the future, this will also print the template for the control's container
-	 * element and be override-able.
+	 * element and be override-able. @link https://core.trac.wordpress.org/ticket/30741
 	 *
 	 * @since 4.1.0
 	 */
@@ -653,9 +589,88 @@ class WP_Customize_Control {
 	 * @see WP_Customize_Control::print_template()
 	 *
 	 * @since 4.1.0
+	 * @since 4.3.0 Core base control class uses JS templates by default.
 	 */
-	protected function content_template() {}
+	protected function content_template() {
+	?>
+		<# switch ( data.type ) {
+			case 'checkbox': #>
+				<label>
+					<input type="checkbox" value="{{ data.value }}" {{{ data.link }}} <# if ( data.value ) { #> checked="checked" <# } #> />
+					{{ data.label }}
+					<# if ( data.description ) { #>
+						<span class="description customize-control-description">{{ data.description }}</span>
+					<# } #>
+				</label>
+				<#
+				break;
+			case 'radio':
+				if ( ! data.choices ) {
+					return;
+				}
+
+				var name = '_customize-radio-' + data.id;
 
+				if ( data.label ) { #>
+					<span class="customize-control-title">{{ data.label }}</span>
+				<# } if ( data.description ) { #>
+					<span class="description customize-control-description">{{{ data.description }}}</span>
+				<# }
+
+				for ( key in data.choices ) { #>
+					<label>
+						<input type="radio" value="{{ key }}" name="{{ name }}" {{{ data.link }}} <# if ( data.choices[key] === data.value ) { #> checked="checked" <# } #> />
+						{{ data.label }}<br/>
+					</label>
+				<# }
+				break;
+			case 'select':
+				if ( ! data.choices ) {
+					return;
+				}
+				#>
+				<label>
+					<# if ( data.label ) { #>
+						<span class="customize-control-title">{{ data.label }}</span>
+					<# } if ( data.description ) { #>
+						<span class="description customize-control-description">{{{ data.description }}}</span>
+					<# } #>
+
+					<select {{{ data.link }}}>
+						<# for ( key in data.choices ) { #>
+							<option value="{{ key }}" <# if ( data.choices[key] === data.value ) { #>selected="selected" <# } #>>{{ data.label }}</option>
+						<# } #>
+					</select>
+				</label>
+				<#
+				break;
+			case 'textarea':
+				#>
+				<label>
+					<# if ( data.label ) { #>
+						<span class="customize-control-title">{{ data.label }}</span>
+					<# } if ( data.description ) { #>
+						<span class="description customize-control-description">{{{ data.description }}}</span>
+					<# } #>
+
+					<textarea rows="5" {{{ data.inputAttrs }}} {{{ data.link }}}>{{ data.value }}</textarea>
+				</label>
+				<#
+				break;
+			default:
+				#>
+				<label>
+					<# if ( data.label ) { #>
+						<span class="customize-control-title">{{ data.label }}</span>
+					<# } if ( data.description ) { #>
+						<span class="description customize-control-description">{{{ data.description }}}</span>
+					<# } #>
+
+					<input type="{{ data.type }}" {{{ data.inputAttrs }}} value="{{ data.value }}" {{{ data.link }}} />
+				</label>
+		<# } #>
+	<?php
+	}
 }
 
 /**
diff --git src/wp-includes/class-wp-customize-manager.php src/wp-includes/class-wp-customize-manager.php
index 5e66a1aa75..74b15ec969 100644
--- src/wp-includes/class-wp-customize-manager.php
+++ src/wp-includes/class-wp-customize-manager.php
@@ -3200,6 +3200,15 @@ final class WP_Customize_Manager {
 			$control->print_template();
 		}
 
+		// Base control, for built-in & fallback types.
+		$control = new WP_Customize_Control( $this, 'temp', array( 'type' => 'default' ) );
+		$control->print_template();
+
+		$header_image_control = $this->get_control( 'header_image' );
+		if ( $header_image_control instanceof WP_Customize_Header_Image_Control ) {
+			$header_image_control->print_header_image_template();
+		}
+
 		?>
 		<script type="text/html" id="tmpl-customize-notification">
 			<li class="notice notice-{{ data.type || 'info' }} {{ data.alt ? 'notice-alt' : '' }} {{ data.dismissible ? 'is-dismissible' : '' }}" data-code="{{ data.code }}" data-type="{{ data.type }}">
diff --git src/wp-includes/class-wp-customize-setting.php src/wp-includes/class-wp-customize-setting.php
index 601990f532..a5bddab000 100644
--- src/wp-includes/class-wp-customize-setting.php
+++ src/wp-includes/class-wp-customize-setting.php
@@ -805,6 +805,7 @@ class WP_Customize_Setting {
 			'transport' => $this->transport,
 			'dirty'     => $this->dirty,
 			'type'      => $this->type,
+			'default'   => $this->default,
 		);
 	}
 
diff --git src/wp-includes/customize/class-wp-customize-header-image-control.php src/wp-includes/customize/class-wp-customize-header-image-control.php
index 8bf93e7df1..eab94c5d8d 100644
--- src/wp-includes/customize/class-wp-customize-header-image-control.php
+++ src/wp-includes/customize/class-wp-customize-header-image-control.php
@@ -84,6 +84,7 @@ class WP_Customize_Header_Image_Control extends WP_Customize_Image_Control {
 	}
 
 	/**
+	 * @todo This should go in the content_template() method.
 	 */
 	public function print_header_image_template() {
 		?>
@@ -157,7 +158,6 @@ class WP_Customize_Header_Image_Control extends WP_Customize_Image_Control {
 	/**
 	 */
 	public function render_content() {
-		$this->print_header_image_template();
 		$visibility = $this->get_current_image_src() ? '' : ' style="display:none" ';
 		$width = absint( get_theme_support( 'custom-header', 'width' ) );
 		$height = absint( get_theme_support( 'custom-header', 'height' ) );
