`hook_preprocess_menu` is missing a lot of variables that other preprocessors have so I finally solved my problem using `hook_preprocess_block` (which I think would not have been possible in earlier versions of drupal) **New solution** since people have been reporting that `$vars['configuration']['region']` is empty function THEMENAME_preprocess_block(&$vars){ if($vars['base_plugin_id'] == "system_menu_block"){ if($vars['derivative_plugin_id'] == "main"){ $block = \Drupal\block\Entity\Block::load($vars["elements"]["#id"]); if( $block->getRegion() == "navbar") $vars['content']['#attributes']['class'][] = 'in-navbar'; $vars['content']['#attributes']['class'][] = 'nav'; } elseif($vars['configuration']['region'] == "footer"){ $vars['content']['#attributes']['class'][] = 'in-footer'; } } } } **NOTE** if using in a recursive function/twig MACRO (which probably you are) you should `removeClass` all the classes you don't want in inner `UL` tags in a way similar to this {{ menus.menu_links(item.below, attributes.removeClass('nav','in-navbar'), menu_level + 1) }} **old solution** function THEMENAME_preprocess_block(&$vars){ if($vars['base_plugin_id'] == "system_menu_block"){ if($vars['derivative_plugin_id'] == "main"){ if($vars['configuration']['region'] == "navbar"){ $vars['content']['#attributes']['class'][] = 'in-navbar'; $vars['content']['#attributes']['class'][] = 'nav'; } elseif($vars['configuration']['region'] == "footer"){ $vars['content']['#attributes']['class'][] = 'in-footer'; } } } }