diff --git a/src/js/_enqueues/admin/inline-edit-post.js b/src/js/_enqueues/admin/inline-edit-post.js
index e7d4496b88..9427f1a173 100644
--- a/src/js/_enqueues/admin/inline-edit-post.js
+++ b/src/js/_enqueues/admin/inline-edit-post.js
@@ -178,6 +178,8 @@ window.wp = window.wp || {};
 	 */
 	setBulk : function(){
 		var te = '', type = this.type, c = true;
+		var checkedPosts = $( 'tbody th.check-column input[type="checkbox"]:checked' );
+		var categories = {};
 		this.revert();
 
 		$( '#bulk-edit td' ).attr( 'colspan', $( 'th:visible, td:visible', '.widefat:first thead' ).length );
@@ -217,6 +219,42 @@ window.wp = window.wp || {};
 		// Populate the list of items to bulk edit.
 		$( '#bulk-titles' ).html( '<ul id="bulk-titles-list" role="list">' + te + '</ul>' );
 
+		// Gather up some statistics on which of these checked posts are in which categories.
+		checkedPosts.each( function() {
+			var id      = $( this ).val();
+			var checked = $( '#category_' + id ).text().split( ',' );
+
+			checked.map( function( cid ) {
+				categories[ cid ] || ( categories[ cid ] = 0 );
+				// Just record that this category is checked.
+				categories[ cid ]++;
+			} );
+		} );
+
+		// Compute initial states.
+		$( '.inline-edit-categories input[name="post_category[]"]' ).each( function() {
+			if ( categories[ $( this ).val() ] == checkedPosts.length ) {
+				// If the number of checked categories matches the number of selected posts, then all posts are in this category.
+				$( this ).prop( 'checked', true );
+			} else if ( categories[ $( this ).val() ] > 0 ) {
+				// If the number is less than the number of selected posts, then it's indeterminate.
+				$( this ).prop( 'indeterminate', true );
+				if ( ! $( this ).parent().find( 'input[name="indeterminate_post_category[]"]' ).length ) {
+					// Set indeterminate states for the backend.
+					$( this ).attr( 'label', wp.i18n.__( 'Some selected posts have this category' ) ).after( '<input type="hidden" name="indeterminate_post_category[]" value="' + $( this ).val() + '">' );
+				}
+			}
+		} );
+
+		$( '.inline-edit-categories input[name="post_category[]"]' ).on( 'change', function() {
+			// Remove the indeterminate flags as there was a specific state change.
+			$( this ).parent().find( 'input[name="indeterminate_post_category[]"]' ).remove();
+		} );
+
+		$( '.inline-edit-save button' ).on( 'click', function() {
+			$( '.inline-edit-categories input[name="post_category[]"]' ).prop( 'indeterminate', false );
+		} );
+
 		/**
 		 * Binds on click events to handle the list of items to bulk edit.
 		 *
diff --git a/src/wp-admin/css/list-tables.css b/src/wp-admin/css/list-tables.css
index 07cbc6229d..ad3c5522e6 100644
--- a/src/wp-admin/css/list-tables.css
+++ b/src/wp-admin/css/list-tables.css
@@ -1147,6 +1147,17 @@ ul.cat-checklist {
 	overflow-y: scroll;
 }
 
+ul.cat-checklist input[name="post_category[]"]:indeterminate::before {
+	content: '';
+	border-top: 2px solid grey;
+	width: 65%;
+	height: 2px;
+	position: absolute;
+	top: calc( 50% + 1px );
+	left: 50%;
+	transform: translate( -50%, -50% );
+}
+
 #bulk-titles .ntdelbutton,
 #bulk-titles .ntdeltitle,
 .inline-edit-row fieldset ul.cat-checklist label {
diff --git a/src/wp-admin/includes/post.php b/src/wp-admin/includes/post.php
index c93ac71bec..2e33e1fdb4 100644
--- a/src/wp-admin/includes/post.php
+++ b/src/wp-admin/includes/post.php
@@ -649,8 +649,21 @@ function bulk_edit_posts( $post_data = null ) {
 		}
 
 		if ( isset( $new_cats ) && in_array( 'category', $tax_names, true ) ) {
-			$cats                       = (array) wp_get_post_categories( $post_id );
-			$post_data['post_category'] = array_unique( array_merge( $cats, $new_cats ) );
+			$cats = (array) wp_get_post_categories( $post_id );
+
+			if (
+				isset( $post_data['indeterminate_post_category'] )
+				&& is_array( $post_data['indeterminate_post_category'] )
+			) {
+				$indeterminate_post_category = $post_data['indeterminate_post_category'];
+			} else {
+				$indeterminate_post_category = array();
+			}
+
+			$indeterminate_cats         = array_intersect( $cats, $indeterminate_post_category );
+			$determinate_cats           = array_diff( $new_cats, $indeterminate_post_category );
+			$post_data['post_category'] = array_unique( array_merge( $indeterminate_cats, $determinate_cats ) );
+
 			unset( $post_data['tax_input']['category'] );
 		}
 
