diff --git a/includes/form.inc b/includes/form.inc
index 8c3b14ab4b2329035b4e3a5114d62f69e20c1f3c..1b163a056d14004a1e6a866dc0e96a3229c1c91b 100644
--- a/includes/form.inc
+++ b/includes/form.inc
@@ -762,6 +762,15 @@ function drupal_retrieve_form($form_id, &$form_state) {
   }
 
   $form = array();
+  // Assign a default CSS class name based on $form_id.
+  // This happens here and not in drupal_prepare_form() in order to allow the
+  // form constructor function to override or remove the default class.
+  $form['#attributes']['class'][] = drupal_html_class($form_id);
+  // Same for the base form ID, if any.
+  if (isset($form_state['build_info']['base_form_id'])) {
+    $form['#attributes']['class'][] = drupal_html_class($form_state['build_info']['base_form_id']);
+  }
+
   // We need to pass $form_state by reference in order for forms to modify it,
   // since call_user_func_array() requires that referenced variables are passed
   // explicitly.
diff --git a/modules/comment/comment.module b/modules/comment/comment.module
index f9af40a34df6c003239b170e675f79951e092cd8..1bdc57a7abedab12d6de97b542d8bfd06ad96128 100644
--- a/modules/comment/comment.module
+++ b/modules/comment/comment.module
@@ -1858,7 +1858,6 @@ function comment_form($form, &$form_state, $comment) {
 
   // Use #comment-form as unique jump target, regardless of node type.
   $form['#id'] = drupal_html_id('comment_form');
-  $form['#attributes']['class'][] = 'comment-form';
   $form['#theme'] = array('comment_form__node_' . $node->type, 'comment_form');
 
   $anonymous_contact = variable_get('comment_anonymous_' . $node->type, COMMENT_ANONYMOUS_MAYNOT_CONTACT);
diff --git a/modules/node/node.pages.inc b/modules/node/node.pages.inc
index d92e8b78628f5f992d1e74623b5c444b392f272e..c7b26e75cb210e9d4e329bb6df7ed18a3f69ea52 100644
--- a/modules/node/node.pages.inc
+++ b/modules/node/node.pages.inc
@@ -110,10 +110,9 @@ function node_form($form, &$form_state, $node) {
   // @todo D8: Remove. Modules can implement hook_form_BASE_FORM_ID_alter() now.
   $form['#node_edit_form'] = TRUE;
 
-  $form['#attributes']['class'][] = 'node-form';
-  if (!empty($node->type)) {
-    $form['#attributes']['class'][] = 'node-' . $node->type . '-form';
-  }
+  // Override the default CSS class name, since the user-defined node type name
+  // in 'TYPE-node-form' potentially clashes with third-party class names.
+  $form['#attributes']['class'][0] = drupal_html_class('node-' . $node->type . '-form');
 
   // Basic node information.
   // These elements are just values so they are not even sent to the client.
diff --git a/modules/search/search.module b/modules/search/search.module
index 5844f6ed341563c7a23191fdbd54486b518dd056..e731e4f2dc44d4e49bf635735c2e792bdb54c1e5 100644
--- a/modules/search/search.module
+++ b/modules/search/search.module
@@ -987,7 +987,6 @@ function search_form($form, &$form_state, $action = '', $keys = '', $module = NU
   $form['#action'] = url($action);
   // Record the $action for later use in redirecting.
   $form_state['action'] = $action;
-  $form['#attributes']['class'][] = 'search-form';
   $form['module'] = array('#type' => 'value', '#value' => $module);
   $form['basic'] = array('#type' => 'container', '#attributes' => array('class' => array('container-inline')));
   $form['basic']['keys'] = array(