Why WP_Screeen doesn’t show all options with admin_body_class

But parent_base is set to null.

Yes, because by the time admin_body_class is fired, WordPress hasn’t yet set the parent_base value.

And it’s only set after the <body> tag is actually outputted. More specifically, it’s set only after the <div id="wpbody" role="main"> as you can see here in wp-admin/admin-header.php.

Any idea how to add class to submenu pages if it is under the specific top-level menu.

You can use the admin_body_class hook, but instead of the $screen->parent_base, use $screen->id which is the hook name returned by add_menu_page() as well as add_submenu_page().

And to get that hook name, you should or would want to use get_plugin_page_hook().

So for example:

  • If I added my top-level and sub menus like so:

    // Top-level menu: "Test menu"
    add_menu_page( 'Test', 'Test menu', 'manage_options', 'test', 'some_function' );
    
    // Submenu: "Test submenu"
    add_submenu_page( 'test', 'Submenu Page', 'Test submenu', 'manage_options',
        'test-submenu', 'some_function_2' );
    
  • Then I can add the custom CSS class like so:

    add_filter( 'admin_body_class', 'my_admin_body_class' );
    function my_admin_body_class( $classes ) {
        $screen = get_current_screen();
    
        if ( get_plugin_page_hook( 'test', '' ) === $screen->id ) {
            $classes .= ' foo-class';
        }
        elseif ( get_plugin_page_hook( 'test-submenu', 'test' ) === $screen->id ) {
            $classes .= ' bar-class';
        }
    
        return $classes; // *always return this
    }
    

    Note that I emphasized the return $classes; because in your tw_admin_body_class() function, the $classes is only returned if your conditional returns a true.

And actually, the plugin page hook is added by default to the <body> tag of the menu/admin page as well as the top-level menu item (<li> tag), e.g. <body class="... toplevel_page_test ..."> on my “Test” admin page, so you could probably just make use of that class?