Skip to content

Instantly share code, notes, and snippets.

@davidwebca
Last active October 16, 2024 12:25
Show Gist options
  • Save davidwebca/a7b278bbb0c0ce1d1ec5620126e863bb to your computer and use it in GitHub Desktop.
Save davidwebca/a7b278bbb0c0ce1d1ec5620126e863bb to your computer and use it in GitHub Desktop.
Allow adding custom classes to WordPress menu ul, li, a and at different depths. Perfect for TailwindCSS and AlpineJS usage.
<?php
/**
* WordPress filters to allow custom arguments to wp_nav_menu to,
* in turn, allow custom classes to every element of a menu.
*
* You can apply a class only to certain depth of your menu as well.
*
* The filters use the depth argument given by WordPress
* which is an index, thus starts with level 0 (zero).
*
* Custom Arguments supported :
* link_atts or link_atts_$depth -> Add any attribute to <a> elements
* a_class or a_class_$depth -> Add classes to <a> elements
* li_class or li_class_$depth -> Add classes to <li> elements
* submenu_class or submenu_class_$depth -> Add classes to submenu <ul> elements
*
* Ex.: add a "text-black" class to all links and "text-blue" class only to 3rd level links
* wp_nav_menu([
* 'theme_location' => 'primary_navigation',
* 'a_class' => 'text-black',
* 'a_class_2'
* ...
* ])
*
* Ex.: More complete example with some TailwindCSS classes and AlpineJS sugar
* wp_nav_menu([
* 'theme_location' => 'primary_navigation',
* 'menu_class' => 'relative w-full z-10 pl-0 list-none flex',
* 'link_atts_0' => [
* ":class" => "{ 'active': tab === 'foo' }",
* "@click" => "tab = 'foo'"
* ],
* 'li_class' => 'w-full',
* 'li_class_0' => 'mb-12',
* 'a_class' => 'text-sm xl:text-xl text-white border-b hover:border-white',
* 'a_class_0' => 'text-3xl xl:text-5xl relative dash-left js-stagger a-mask after:bg-primary',
* 'li_class_1' => 'js-stagger a-mask after:bg-primary hidden lg:block',
* 'a_class_1' => 'flex h-full items-center uppercase py-2 relative border-white border-opacity-40 hover:border-opacity-100',
* 'submenu_class' => 'list-none pl-0 grid grid-cols-1 lg:grid-cols-2 lg:gap-x-12 xl:gap-x-24 xxl:gap-x-32',
* 'container'=>false
* ])
*
* @author davidwebca
* @link https://gist.github.com/davidwebca/a7b278bbb0c0ce1d1ec5620126e863bb
*/
/**
* Add custom attributes or classes to links in wp_nav_menu
*/
add_filter( 'nav_menu_link_attributes', function ( $atts, $item, $args, $depth ) {
if (property_exists($args, 'link_atts')) {
$atts = array_merge($atts, $args->link_atts);
}
if (property_exists($args, "link_atts_$depth")) {
$atts = array_merge($atts, $args->{"link_atts_$depth"});
}
if(empty($atts['class'])) {
$atts['class'] = '';
}
$classes = explode(' ', $atts['class']);
/**
* Fix for tailwindcss classes that include ":" (colon)
* Enter triple underscore hover___text-primary instaed of hover:text-primary
*
* Some filters provided so that you can customize your own replacements,
* passed directly to preg_replace so supports array replacements as well.
*
* WordPress trac following the issue of escaping CSS classes:
* @link https://core.trac.wordpress.org/ticket/33924
*/
$patterns = apply_filters( 'nav_menu_css_class_unescape_patterns', '/___/');
$replacements = apply_filters( 'nav_menu_css_class_unescape_replacements', ':' );
$classes = array_map(function($cssclass) use( $patterns, $replacements) {
return preg_replace($patterns, $replacements, $cssclass);
}, $classes);
if (property_exists($args, 'a_class')) {
$arr_classes = explode(' ', $args->a_class);
$classes = array_merge($classes, $arr_classes);
}
if (property_exists($args, "a_class_$depth")) {
$arr_classes = explode(' ', $args->{"a_class_$depth"});
$classes = array_merge($classes, $arr_classes);
}
$atts['class'] = implode(' ', $classes);
return $atts;
}, 1, 4 );
/**
* Add custom classes to lis in wp_nav_menu
*/
add_filter( 'nav_menu_css_class', function ( $classes, $item, $args, $depth ) {
if (property_exists($args, 'li_class')) {
$arr_classes = explode(' ', $args->li_class);
$classes = array_merge($classes, $arr_classes);
}
if (property_exists($args, "li_class_$depth")) {
$arr_classes = explode(' ', $args->{"li_class_$depth"});
$classes = array_merge($classes, $arr_classes);
}
return $classes;
}, 1, 4 );
/**
* Add custom classes to ul.sub-menu in wp_nav_menu
*/
add_filter('nav_menu_submenu_css_class', function( $classes, $args, $depth ) {
if (property_exists($args, 'submenu_class')) {
$arr_classes = explode(' ', $args->submenu_class);
$classes = array_merge($classes, $arr_classes);
}
if (property_exists($args, "submenu_class_$depth")) {
$arr_classes = explode(' ', $args->{"submenu_class_$depth"});
$classes = array_merge($classes, $arr_classes);
}
return $classes;
}, 1, 3);
/**
* Apply our new custom attributes to widgetized wp_nav_menus
*/
add_filter('widget_nav_menu_args', function($nav_menu_args, $nav_menu, $args, $instance) {
// if($args['id'] == 'sidebar-footer') {
// $nav_menu_args['menu_class'] = 'mt-5 font-alt text-sm font-normal leading-7 opacity-60';
// $nav_menu_args['a_class'] = 'text-gray-300 opacity-60 hover:opacity-100';
// }
return $nav_menu_args;
}, 10, 4);
@Vince-ALIEN
Copy link

HI David,

It's really a great job, and it' really usefull. I hop in the future put walker's in the trash...

I'm near to have the best menu of the worrld, but I am encountering a problem : https://github.com/Vince-ALIEN/dev/blob/main/the-header-content.php.

I use Tailwind css and Alpine js.

It's a offcanvas menu on desktop and mobile, the burger open menu w-1/2 and li_atts_0 push it to w-full.

First, i would liketo close the submenu when i click on other li_atts_0 to open them, and then i want to keep the menu full width.

Does your response to @jansolo76 it's the way to realise this ?

Do you have just an idea ? It'would be great...

Encore merci.

Vincent.

@davidwebca
Copy link
Author

davidwebca commented Nov 24, 2023

Hey Vince! This gist is now a full composer package : https://github.com/davidwebca/wordpress-menu-classes

You can look at the updated file there if you want to use it standalone : https://github.com/davidwebca/wordpress-menu-classes/blob/main/src/WordPressMenuClasses.php

Especially since there were updates to WordPress core that I contributed that allows to add classes and attributes to the links directly (instead of only being able to add them to the LI elements).

What I'm looking at in your linked code seems to be fine unless I don't understand completely what you want to achieve. One thing to note is that if you want to only keep one submenu opened, you need to store an ID of some sort.

Here's a quick demo of how I do it and you can also check out the newest Alpine plugin Anchor which solves a part of the headache for anchoring submenus :

--
Edit: The demo is HTML only but gives you an idea of what would be the attributes to pass to which element in wp_nav_menu

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment