In Drupal 7, there a class e.g "path-node-17" added to the body tag so that we can target a particular node easily but this is missing in Drupal 8, I wonder how to get this node id class output in the tag in Drupal 8.

Comments

Jeff Burnz’s picture

theme name.theme :

use Drupal\Component\Utility\Html;

/**
 * Preprocess variables for html templates.
 * @param $variables
 */
function HOOK_preprocess_html(&$variables) {
  $variables['path_info']['args'] = FALSE;
  $path = \Drupal::request()->getPathInfo();
  $path_args = explode('/', $path);
  if (count($path_args) >= 3) {
    $variables['path_info']['args'] = Html::cleanCssIdentifier(ltrim($path, '/'));
  }
}

html.html.twig :

  {%
    set body_classes = [
      not root_path ? 'path-frontpage' : 'path-' ~ root_path|clean_class,
      path_info.args ? 'path-' ~ path_info.args,
    ]
  %}
  <body{{ attributes.addClass(body_classes) }}>

Note that this works for all paths, if you just want the node id thing, you can most likely lever \Drupal::request()->attributes->get('node');

simone960’s picture

Awesome ! It works well ! I think this should be included in core. Thanks !

serpforge’s picture

This solution not work for my theme (bootstrap subtheme), so I change
$path = \Drupal::request()->getPathInfo();
to
$path = \Drupal::service('path.current')->getPath();
Its work fine and i have "path-node-NID" body class.

tajdar’s picture

For bootstrap sub-theme it works fine.
Thanks serpforge

Suryakumar B’s picture

It works for me

Ralf Eisler’s picture

It works!

atul_shin’s picture

Thanks to all you who made efforts.

:)

artatac’s picture

Hi Jeff

Is there a way to expand this to also add tags (taxonomy) as css classes please?

AmolB’s picture

Worked for me.

Shashwat Purav’s picture

Worked perfectly. Thank you.

Thank You,
Shashwat Purav

chefigueroa’s picture

Thanks! Saved me a lot of time!

Plazik’s picture

in themename.theme:

/**
 * Implements hook_preprocess_HOOK().
 *
 */
function themename_preprocess_html(&$variables) {
  $path = \Drupal::service('path.current')->getPath();
  $path_args = explode('/', $path);
  if (isset($path_args[1]) && isset($path_args[2]) && ($path_args[1] == 'node') && (is_numeric($path_args[2]))) {
    $variables['attributes']['class'][] = 'page-node-' . $path_args[2];
  }
}
KyleNakhul’s picture

This works for me.

TolliSysDev’s picture

Thanks so much...

sergey-serov’s picture

Great, thank You!
Theme system became more complex of course.

atul_shin’s picture

Yes this piece of code is simple and works for me. Do not have to include additional library or class. Cool !!!

Mike Dodd’s picture

Works well, thank you

If Windows is the answer, it must have been a stupid question. -- Filip Van Raemdonck

pierrick419’s picture

Works perfectly, thanks!

Jeff Burnz’s picture

If you just want the node id in a body class you can do this in preprocess, this is probably the best way to get a simple unique class for the node page:

/**
 * Implements hook_preprocess_html().
 */
function themename_preprocess_html(&$variables) {
  if ($node = \Drupal::request()->attributes->get('node')) {
    $variables['attributes']['class'][] = 'page-node-' . $node->id();
  }
}

geraldito’s picture

Thanks, that works for me.

galactus86’s picture

I added your code to mythemename.theme but I don't get any body classes, do I need to still add something to the html.html.twig body classes?
Using Classy as my base theme so not sure if there is a difference.

Ideally I'd like to get the taxonomy term added as a body class too.

onejam’s picture

I was looking for a way to include term as body class as well and found this solution:

Place this in MY_THEME.theme:

<?php
function otterteck2_preprocess_html(&$variables) {
  $current_path = \Drupal::service('path.current')->getPath();
  $variables['current_path'] = \Drupal::service('path.alias_manager')->getAliasByPath($current_path);
}
?>

Then, place this in html.html.twig:

{%
  set body_classes = [
    logged_in ? 'user-logged-in',
    not root_path ? 'path-frontpage' : 'path-' ~ root_path|clean_class,
    node_type ? 'page-node-type-' ~ node_type|clean_class,
    db_offline ? 'db-offline',
    current_path ? 'context' ~ current_path|clean_class,
  ]
%}
<body{{ attributes.addClass(body_classes) }}>

and clear the cache.

Now the output of body classes should be something like this on term pages:

<body class="layout-one-sidebar layout-sidebar-first path-taxonomy context-category-technology">

-----------------------------------------------------------------
We build engaging websites and intuitive designs that will benefit your business.
Duvien

filnug’s picture

Worked out for me. Thanks!

duntuk’s picture

This doesn't include any taxonomy terms into the class, as your output example would suggest.

You're code is using the current_path (URL path) the same thing as the examples above.

Jeff Burnz’s picture

The term is used in the page title, so it's right there, even in the template to be used:

Preprocess html:

if (isset($variables['page']['#title'])) {
    $variables['attributes']['class'][] = 'page-title--' . Html::cleanCssIdentifier($variables['page']['#title']);
  }

Or just do it in twig:

page['#title'] ? 'page-title--' ~ page['#title']|clean_class

rroose’s picture

This did the trick for me, thanks!

thomas.frobieter’s picture

Thanks alot! Exactly what i've searched for.

bmango’s picture

Many thanks, works great!

illutek’s picture

Exactly what I needed

bkayne’s picture

I've attempted all of these fixes using Drupal 8.1.7 (just upgraded to 8.1.8 with no change) and Bootstrap with my own subtheme. The best I can get is the content type page-node-type-CONTENTYPE, but nothing has added the Node ID or the path. Are there are possible solutions out there?

Bob

onejam’s picture

The bootstrap theme renders classes for < body > tags (content types and path, as well as various others).

Only node ID is not available. However, if you care to follow Jeff example (a few comments above): https://www.drupal.org/node/2634364#comment-10871250 You will get the node ID as a css class appended to < body > tag.

Add that function to {MY_THEME_NAME}.theme file.

I run Drupal 8.1.8 with Bootstrap custom sub theme and it works fine.

-----------------------------------------------------------------
We build engaging websites and intuitive designs that will benefit your business.
Duvien

bkayne’s picture

Hm, no luck. The body tag renders like this after this change:

<body class="path-node page-node-type-participate has-glyphicons">

Jeff Burnz’s picture

bkayne’s picture

MYTHEME.theme

/**
 * @file
 * Bootstrap sub-theme.
 *
 * Place your custom PHP code in this file.
 */
 /**
  * Implements hook_preprocess_html().
  */
 function themename_preprocess_html(&$variables) {
   if ($node = \Drupal::request()->attributes->get('node')) {
     $variables['attributes']['class'][] = 'page-node-' . $node->id();
   }
 }

html.html.twig

{#
/**
 * @file
 * Default theme implementation to display the basic html structure of a single
 * Drupal page.
 *
 * Variables:
 * - $css: An array of CSS files for the current page.
 * - $language: (object) The language the site is being displayed in.
 *   $language->language contains its textual representation.
 *   $language->dir contains the language direction. It will either be 'ltr' or
 *   'rtl'.
 * - $rdf_namespaces: All the RDF namespace prefixes used in the HTML document.
 * - $grddl_profile: A GRDDL profile allowing agents to extract the RDF data.
 * - $head_title: A modified version of the page title, for use in the TITLE
 *   tag.
 * - $head_title_array: (array) An associative array containing the string parts
 *   that were used to generate the $head_title variable, already prepared to be
 *   output as TITLE tag. The key/value pairs may contain one or more of the
 *   following, depending on conditions:
 *   - title: The title of the current page, if any.
 *   - name: The name of the site.
 *   - slogan: The slogan of the site, if any, and if there is no title.
 * - $head: Markup for the HEAD section (including meta tags, keyword tags, and
 *   so on).
 * - $styles: Style tags necessary to import all CSS files for the page.
 * - $scripts: Script tags necessary to load the JavaScript files and settings
 *   for the page.
 * - $page_top: Initial markup from any modules that have altered the
 *   page. This variable should always be output first, before all other dynamic
 *   content.
 * - $page: The rendered page content.
 * - $page_bottom: Final closing markup from any modules that have altered the
 *   page. This variable should always be output last, after all other dynamic
 *   content.
 * - $classes String of classes that can be used to style contextually through
 *   CSS.
 *
 * @ingroup templates
 *
 * @see bootstrap_preprocess_html()
 * @see template_preprocess()
 * @see template_preprocess_html()
 * @see template_process()
 */
#}
{%
  set body_classes = [
    logged_in ? 'user-logged-in',
    not root_path ? 'path-frontpage' : 'path-' ~ root_path|clean_class,
    path_info.args ? 'path-' ~ path_info.args,
    node_type ? 'page-node-type-' ~ node_type|clean_class,
    db_offline ? 'db-offline',
    theme.settings.navbar_position ? 'navbar-is-' ~ theme.settings.navbar_position,
    theme.has_glyphicons ? 'has-glyphicons',
  ]
%}
<!DOCTYPE html>
<html {{ html_attributes }}>
  <head>
    <head-placeholder token="{{ placeholder_token|raw }}">
    <title>{{ head_title|safe_join(' | ') }}</title>
    <css-placeholder token="{{ placeholder_token|raw }}">
    <js-placeholder token="{{ placeholder_token|raw }}">
  </head>
  <body{{ attributes.addClass(body_classes) }}>
    <a href="#main-content" class="visually-hidden focusable skip-link">
      {{ 'Skip to main content'|t }}
    </a>
    {{ page_top }}
    {{ page }}
    {{ page_bottom }}
    <js-bottom-placeholder token="{{ placeholder_token|raw }}">
  </body>
</html>
Jeff Burnz’s picture

Did you change "themename" to your theme name?

"themename_preprocess_html" needs to be renamed to replace "themename" with your actual themes name.

Also you need to clear the Drupal cache.

bkayne’s picture

Oy...thank you.

Napche’s picture

For completeness, you can do this from e.g. the .module file too in a hook MODULENAME_preprocess_html

NanaD.Aoyagi’s picture

I try all the options in the comments but no one works for me, somebody could help me..

/**
 * @file
 * Bootstrap sub-theme.
 *
 * Place your custom PHP code in this file.
 */

 // varible for identifier front page
function mmar_preprocess_html(&$variables) {
   $variables['is_front'] = \Drupal::service('path.matcher')->isFrontPage();
   $variables['path_info']['args'] = FALSE;
   $path = \Drupal::service('path.current')->getPath();
   $path_args = explode('/', $path);
   if (count($path_args) >= 3) {
    $variables['path_info']['args'] = Html::cleanCssIdentifier(ltrim($path, '/'));
   }
}

//variable for get path
function mmar_preprocess(&$variables) {
  $variables['base_path_success'] = base_path() . $variables['directory'];
}

and..

{#
/**
 * @file
 * Default theme implementation to display the basic html structure of a single
 * Drupal page.
 *
 * Variables:
 * - $css: An array of CSS files for the current page.
 * - $language: (object) The language the site is being displayed in.
 *   $language->language contains its textual representation.
 *   $language->dir contains the language direction. It will either be 'ltr' or
 *   'rtl'.
 * - $rdf_namespaces: All the RDF namespace prefixes used in the HTML document.
 * - $grddl_profile: A GRDDL profile allowing agents to extract the RDF data.
 * - $head_title: A modified version of the page title, for use in the TITLE
 *   tag.
 * - $head_title_array: (array) An associative array containing the string parts
 *   that were used to generate the $head_title variable, already prepared to be
 *   output as TITLE tag. The key/value pairs may contain one or more of the
 *   following, depending on conditions:
 *   - title: The title of the current page, if any.
 *   - name: The name of the site.
 *   - slogan: The slogan of the site, if any, and if there is no title.
 * - $head: Markup for the HEAD section (including meta tags, keyword tags, and
 *   so on).
 * - $styles: Style tags necessary to import all CSS files for the page.
 * - $scripts: Script tags necessary to load the JavaScript files and settings
 *   for the page.
 * - $page_top: Initial markup from any modules that have altered the
 *   page. This variable should always be output first, before all other dynamic
 *   content.
 * - $page: The rendered page content.
 * - $page_bottom: Final closing markup from any modules that have altered the
 *   page. This variable should always be output last, after all other dynamic
 *   content.
 * - $classes String of classes that can be used to style contextually through
 *   CSS.
 *
 * @ingroup templates
 *
 * @see bootstrap_preprocess_html()
 * @see template_preprocess()
 * @see template_preprocess_html()
 * @see template_process()
 */
#}
{%
  set body_classes = [
    logged_in ? 'user-logged-in',
    not root_path ? 'path-frontpage' : 'path-' ~ root_path|clean_class,
    node_type ? 'page-node-type-' ~ node_type|clean_class,
    path_info.args ? 'path-' ~ path_info.args,
    db_offline ? 'db-offline',
    theme.settings.navbar_position ? 'navbar-is-' ~ theme.settings.navbar_position,
    theme.has_glyphicons ? 'has-glyphicons',
  ]
%}

<!DOCTYPE html>
<html {{ html_attributes }}>
  <head>
    <head-placeholder token="{{ placeholder_token|raw }}">
    <title>{{ head_title|safe_join(' | ') }}</title>
    <css-placeholder token="{{ placeholder_token|raw }}">
    <js-placeholder token="{{ placeholder_token|raw }}">
  </head>
  <body{{ attributes.addClass(body_classes)}}>
    <a href="#main-content" class="visually-hidden focusable skip-link">
      {{ 'Skip to main content'|t }}
    </a>
    {{ page_top }}
    {{ page }}
    {{ page_bottom }}
    <js-bottom-placeholder token="{{ placeholder_token|raw }}">
  </body>
</html>
tamerzg’s picture

Now there is a module for that, its called Body node ID class

--------------
zoubi.me

Ruslan Piskarov’s picture

Personally, I prefer the following.

use Drupal\node\NodeInterface;

/**
 * Implements hook_preprocess_html().
 */
function custom_theme_preprocess_html(&$variables) {
  $node = \Drupal::routeMatch()->getParameter('node');
  if ($node instanceof NodeInterface) {
    $variables['attributes']['class'][] = 'page-node-' . $node->getType();
    $variables['attributes']['class'][] = 'page-node-' . $node->id();
  }
  else {
    $variables['attributes']['class'][] = 'page-no-node';
  }
}

I hope it can help.

Visit my blog for more info: Make Drupal Easy.

aangel’s picture

This seems to be the cleanest, thanks. Providing a bit more specificity was useful in my case:

    $variables['attributes']['class'][] = 'page-type-' . $node->getType();
    $variables['attributes']['class'][] = 'page-nid-' . $node->id();