Index: src/wp-admin/customize.php
===================================================================
--- src/wp-admin/customize.php	(revision 29720)
+++ src/wp-admin/customize.php	(working copy)
@@ -180,6 +180,9 @@
 	<div id="customize-preview" class="wp-full-overlay-main"></div>
 	<?php
 
+	// Render control templates.
+	$wp_customize->render_control_templates();
+
 	/**
 	 * Print Customizer control scripts in the footer.
 	 *
Index: src/wp-admin/js/customize-controls.js
===================================================================
--- src/wp-admin/js/customize-controls.js	(revision 29720)
+++ src/wp-admin/js/customize-controls.js	(working copy)
@@ -60,7 +60,10 @@
 				}
 
 				control.setting = control.settings['default'] || null;
-				control.ready();
+				control.renderContent( function() {
+					// Don't call ready() until the content has rendered.
+					control.ready();
+				} );
 			}) );
 
 			control.elements = [];
@@ -149,6 +152,24 @@
 
 			this.setting.bind( update );
 			update( this.setting() );
+		},
+
+		/**
+		 * Render the control from its JS template, if it exists.
+		 *
+		 * The control's container must alreasy exist in the DOM.
+		 */
+		renderContent: function( callback ) {
+			var template,
+			    selector = 'customize-control-' + this.params.type + '-content',
+			    callback = callback || function(){};
+			if ( 0 !== $( '#tmpl-' + selector ).length ) {
+				template = wp.template( selector );
+				if ( template && this.container ) {
+					this.container.append( template( this.params ) );
+				}
+			}
+			callback();
 		}
 	});
 
Index: src/wp-includes/class-wp-customize-control.php
===================================================================
--- src/wp-includes/class-wp-customize-control.php	(revision 29720)
+++ src/wp-includes/class-wp-customize-control.php	(working copy)
@@ -217,8 +217,10 @@
 			$this->json['settings'][ $key ] = $setting->id;
 		}
 
-		$this->json['type'] = $this->type;
-		$this->json['active'] = $this->active();
+		$this->json['type']        = $this->type;
+		$this->json['label']       = $this->label;
+		$this->json['description'] = $this->description;
+		$this->json['active']      = $this->active();
 	}
 
 	/**
@@ -336,6 +338,8 @@
 	 * Supports basic input types `text`, `checkbox`, `textarea`, `radio`, `select` and `dropdown-pages`.
 	 * Additional input types such as `email`, `url`, `number`, `hidden` and `date` are supported implicitly.
 	 *
+	 * Control content can alternately be rendered in JS. See {@see WP_Customize_Control::print_template()}.
+	 *
 	 * @since 3.4.0
 	 */
 	protected function render_content() {
@@ -443,6 +447,36 @@
 				break;
 		}
 	}
+
+	/**
+	 * Render the control's JS template.
+	 *
+	 * This function is only run for control types that have been registered with {@see WP_Customize_Manager::register_control_type()}.
+	 *
+	 * In the future, this will also print the template for the control's container element and be overridable.
+	 *
+	 * @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
+	}
+
+	/**
+	 * An Underscore (JS) template for this control's content (but not its container).
+	 *
+	 * Class variables for this control class are available in the `data` JS object;
+	 * export custom variables by overriding {@see WP_Customize_Control::to_json()}.
+	 *
+	 * @see WP_Customize_Control::print_template()
+	 *
+	 * @since 4.1.0
+	 */
+	protected function content_template() {}
 }
 
 /**
@@ -499,33 +533,40 @@
 	public function to_json() {
 		parent::to_json();
 		$this->json['statuses'] = $this->statuses;
+		$this->json['defaultValue'] = $this->setting->default;
 	}
 
 	/**
-	 * Render the control's content.
+	 * Don't render the control content from PHP, as it's rendered via JS on load.
 	 *
-	 * @since 3.4.0
+	 * @since 4.1.0
 	 */
-	public function render_content() {
-		$this_default = $this->setting->default;
-		$default_attr = '';
-		if ( $this_default ) {
-			if ( false === strpos( $this_default, '#' ) )
-				$this_default = '#' . $this_default;
-			$default_attr = ' data-default-color="' . esc_attr( $this_default ) . '"';
-		}
-		// The input's value gets set by JS. Don't fill it.
+	public function render_content() {}
+
+	/**
+	 * Render a JS template for the control's content.
+	 *
+	 * @since 4.1.0
+	 */
+	public function content_template() {
 		?>
+		<# var defaultValue = '';
+		if ( data.defaultValue ) {
+			if ( '#' !== data.defaultValue.substring( 0, 1 ) ) {
+				defaultValue = '#' + data.defaultValue;
+			}
+			defaultValue = ' data-default-color=' + defaultValue; // Quotes added automatically.
+		} #>
 		<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; ?>
+			<# if ( data.label ) { #>
+				<span class="customize-control-title">{{ data.label }}</span>
+			<# } #>
+			<# if ( data.description ) { #>
+				<span class="description customize-control-description">{{ data.description }}</span>
+			<# } #>
 
 			<div class="customize-control-content">
-				<input class="color-picker-hex" type="text" maxlength="7" placeholder="<?php esc_attr_e( 'Hex Value' ); ?>"<?php echo $default_attr; ?> />
+				<input class="color-picker-hex" type="text" maxlength="7" placeholder="<?php esc_attr_e( 'Hex Value' ); ?>" {{ defaultValue }} />
 			</div>
 		</label>
 		<?php
Index: src/wp-includes/class-wp-customize-manager.php
===================================================================
--- src/wp-includes/class-wp-customize-manager.php	(revision 29720)
+++ src/wp-includes/class-wp-customize-manager.php	(working copy)
@@ -54,6 +54,13 @@
 	protected $customized;
 
 	/**
+	 * Controls that may be rendered from JS templates.
+	 *
+	 * @since 4.1.0
+	 */
+	protected $registered_control_types = array();
+
+	/**
 	 * $_POST values for Customize Settings.
 	 *
 	 * @var array
@@ -822,6 +829,32 @@
 	}
 
 	/**
+	 * Register a customize control type.
+	 *
+	 * Registered types are eligible to be rendered 
+	 * via JS and created dynamically.
+	 *
+	 * @since 4.1.0
+	 *
+	 * @param string $control Custom control subclass of {@see WP_Customize_Control}.
+	 */
+	public function register_control_type( $control ) {
+		$this->registered_control_types[] = $control;
+	}
+
+	/**
+	 * Render JS templates for all registered control types.
+	 *
+	 * @since 4.1.0
+	 */
+	public function render_control_templates() {
+		foreach( $this->registered_control_types as $control_type ) {
+			$control = new $control_type( $this, 'temp', array() );
+			$control->print_template();
+		}
+	}
+
+	/**
 	 * Helper function to compare two objects by priority.
 	 *
 	 * @since 3.4.0
@@ -927,6 +960,9 @@
 	 */
 	public function register_controls() {
 
+		/* Control Types (custom control classes) */
+		$this->register_control_type( 'WP_Customize_Color_Control' );
+
 		/* Site Title & Tagline */
 
 		$this->add_section( 'title_tagline', array(
