BuddyBoss App Mobile View: How It Works and Why It Breaks Your Templates
If you’ve ever opened a WordPress page inside the BuddyBoss mobile app view and seen a blank screen, a stripped-out layout, or mysteriously hidden headings, you’ve met fallback_support(). This post explains what that script does, why it’s badly designed, and the minimal fix that actually works.
TL;DR: The BuddyBoss app mobile view hides your entire page to strip out site chrome, then tries to reveal only the post content, and its selector list is broad enough to leave your real headings and layout hidden. If you just want the fix, skip to The Working Fix. If you want to know why the obvious approaches fail first, read straight through. Verified still relevant as of BuddyBoss App v2.14.2+.
What It’s Trying to Do
When the BuddyBoss app loads a web page in its in-app webview, it wants to strip out site chrome (header, nav, footer) and show only the post content. Clean, app-like, no clutter. Reasonable goal.
The way it does it is not reasonable.
How the Script Works
The logic lives in plugins/buddyboss-app/include/NativeAppPage.php, method fallback_support(), hooked to wp_head at priority 999.
It triggers when any of the following are true:
- The request carries
?mobile-view-content=1in the URL ?mobile-page-mode=1is set- The app sends a
pagemode: templateHTTP header or User-Agent string - A
bbapp_page_modecookie is set totemplate
You can test the BuddyBoss mobile view in any browser by appending ?mobile-view-content=1 to a URL.
Step 1: Nuke the page with CSS
The script injects a <style> block that hides the entire page:
body { display: none; }
.bbapp_hide_all { display: none !important; }
Then it dumps a direct display: none !important rule for every selector in get_mobile_view_hide_selectors(), a list of ~60 selectors covering headers, footers, sidebars, and layout containers.
Step 2: Nuke the DOM with JS
On document.ready, jQuery:
- Adds
.bbapp_hide_allto every match in that same selector list - Adds
.bbapp_hide_allto every single element in<body> - Tries to find
.bbapp_main_content_area, or auto-guesses from selectors likemain,#main,.entry-content,article - Removes
.bbapp_hide_allfrom that content island and walks up the ancestor chain
Step 3: Reveal, unwrap, and clean up
$ele.find('.bbapp_hide_all').each(function() {
jQuery(this).removeClass('bbapp_hide_all');
});
// Show parent elements up to HTML.
while ($ele.parent().prop('nodeName') !== jQuery('html').prop('nodeName')) {
jQuery($ele.parent()).removeClass('bbapp_hide_all');
$ele = $ele.parent();
}
jQuery('body').show();
jQuery('.bbapp_main_content_area').contents().unwrap();
That last line, .contents().unwrap(), destroys the wrapper div, keeping only its children. Any class you put on .bbapp_main_content_area is gone after the script runs.
Why This Design Is a Problem
Two hides, only one is reversible
The script hides elements in two different ways:
- Direct CSS rule:
header, .page-header, #content … { display: none !important; } - Class:
.bbapp_hide_all { display: none !important; }
The reveal pass only removes the .bbapp_hide_all class. It never touches the CSS rules directly. So any element that matches a hide-list selector and needs to be visible stays hidden. Removing the class doesn’t help.
Selectors too broad to be safe
The selector list is written for generic WordPress themes and uses bare element names and class names that collide with actual content markup:
| Selector | Intended target | Also matches |
|---|---|---|
header | <header id="masthead"> | <header class="page-header">, <header class="entry-header"> |
.page-header | Theme page title area | Archive title wrapper in content |
#content, .container | Layout wrappers | The layout wrappers get_header() opens around your content |
#main, .main | Main landmark | Your <main id="main" class="site-main"> |
.widget-area | Sidebar | Any sidebar you actually want visible |
These elements get display: none !important injected as a direct CSS rule. No amount of class manipulation can undo that.
The archive “fix” doesn’t fix archives
The script has special archive handling that restores .page-title and article. But if your archive title is <header class="page-header"><h1 class="page-title">...</h1></header>, the parent <header> has a direct display: none rule on it. .page-title gets revealed, but its parent is still hidden. Result: content is in the DOM but invisible.
The wrapper gets deleted
Developers are told to wrap content in .bbapp_main_content_area. The script uses that as a target for the reveal pass, then deletes it:
jQuery('.bbapp_main_content_area').contents().unwrap();
Any class, data attribute, or event binding on that wrapper is gone. Every integration that needs to survive the script (LearnDash, MEC, Jobs) has to hook an element inside the wrapper or listen for the bbapp_mobile_view_hiding_complete event after the fact.
bbapp_main_content_parent is dead code
The reveal adds this class to ancestors while walking up the tree. There is no CSS rule for it anywhere in the plugin. It does nothing.
FOUC is baked in
FOUC: flash of unstyled content.
The whole page is hidden in the BuddyBoss app mobile view via body { display: none; } before jQuery runs. A slow script, a race condition, or a JS error results in a completely blank screen. There’s no graceful degradation.
The Working Fix
You need three things.
1. Wrap your layout in .bbapp_main_content_area
This gives the script a target island. Put it around #primary and your sidebar, not just the post content. Also, stash the bbapp_show_content_area class to put on #primary:
$bbapp_in_app = ( function_exists( 'bbapp_is_loaded_from_inapp_browser' ) && bbapp_is_loaded_from_inapp_browser() )
|| isset( $_GET['mobile-view-content'] );
$add_class = '';
if ( $bbapp_in_app ) {
echo '<div class="bbapp_main_content_area">';
$add_class = 'bbapp_show_content_area';
}
?>
<div id="primary" class="content-area <?php echo esc_attr( $add_class ); ?>">
<!-- your content -->
</div>
<?php get_sidebar(); ?>
<?php if ( $bbapp_in_app ) { echo '</div>'; } ?>
2. Put bbapp_show_content_area on #primary, not on the wrapper
The wrapper gets deleted at the end of the script. #primary survives. The class needs to live on something that survives.
3. Add one CSS rule
.bbapp_show_content_area .bbapp_hide_all,
.bbapp_show_content_area header {
display: revert !important;
}
This beats both hides inside #primary on specificity. Against the class-based hide (.bbapp_hide_all, specificity 0,1,0), the override .bbapp_show_content_area .bbapp_hide_all wins at 0,2,0. Against the direct CSS hide (header, specificity 0,0,1), the override .bbapp_show_content_area header wins at 0,1,1. Both the hides and both overrides use !important, so within that layer specificity is the tiebreaker.
Use revert, not initial. display: initial computes to inline for all elements, which collapses block elements and breaks layout. revert restores each element’s browser-default display value.
Site chrome (#masthead, footer, nav) sits outside #primary, so it never matches the selector and stays hidden as intended.
What a Sane Version Would Look Like
Our fix routes around the script. Here’s what would have made the fix unnecessary in the first place:
- Hide specific site-chrome elements by their actual IDs:
#masthead,.site-footer,#wpadminbar - Never use bare
header/footerelement selectors - One hide mechanism, not direct CSS and a class
- Don’t unwrap the wrapper you told developers to use
- Let themes opt in with a body class (
in-bbappis already added) or data attribute rather than guessing from 60 generic selectors - Keep the content visible by default and hide the chrome, not the reverse
Templates Where This Is Applied
This pattern came out of our own theme build against the plugin. Apply the wrapper-plus-#primary approach to any template where the theme’s header, sidebar, or layout markup collides with the hide list, and use the wrapper alone where the layout has no broad-selector collisions.
| Template type | Approach |
|---|---|
Archive templates (archive-*.php) | Wrapper + bbapp_show_content_area on #primary + CSS rule |
Taxonomy templates (taxonomy-*.php) | Same |
Single-post templates (single-*.php) with theme header markup or a sidebar | Same |
| Content-part templates with a clean, self-contained layout | Wrapper only (no broad-selector collisions) |
| Custom post type singles (for example, events) with self-contained content | Wrapper only |
Approaches That Don’t Work
| Approach | Why it fails |
|---|---|
.bbapp_main_content_area wrapper class alone | The wrapper is deleted at the end of the script |
Putting bbapp_show_content_area on the wrapper | Same, the wrapper is gone before CSS applies |
bbapp_disable_default_inapp_browser_template() | Hooks bbapp_perform_pagemode_action filter, which is never checked anywhere in the plugin. Dead code. |
bbapp_is_lms_page filter | Skips some CSS on LMS pages only; the hide-all JS still runs |
bbapp_mobile_view_hide_selectors filter | Removes selectors from both the PHP CSS block and the JS list. In theory, it is correct, but in practice brittle because the direct-CSS and JS-class passes don’t fail the same way |
| Piling on CSS/JS overrides | Too many moving parts. You end up fighting the script rather than rooting around in it. |
Debugging Checklist
- Append
?mobile-view-content=1to the URL in a desktop browser - Does
<body>have thein-bbappclass? If not, the script didn’t trigger - Is
.bbapp_main_content_areapresent in the HTML source (not the live DOM, since the script deletes it from the DOM) - Does
#primaryhavebbapp_show_content_areain the live DOM? - Is
.bbapp_show_content_area .bbapp_hide_allpresent in the computed styles on a hidden element? - Check the browser console for errors inside
<script id="bbapp-mobile-view-fallback">
Plugin Update: v2.14.2+
After the upgrade, the script is significantly better, but our fix remains unchanged.
What they fixed:
- Removed
body,#content,.content,#main,.main,.site-content,#site-content,.container,.wrapfrom the hide list. This eliminates the ancestor collision problem on most themes without a wrapper. - Added a content boundary pass that finds
#primary/#content/.content-areafrom the content island, unhides everything inside it, then re-hides hide-list matches within it. More targeted than the previous all-or-nothing ancestor walk. - Added the
bbapp_mobile_view_visible_selectorsfilter, the official way to declare content outsidethe_content()that needs to stay visible. The script unhides matching elements, their descendants, and their parent chain. - Added
bbapp_mobile_view_popup_selectorsand a MutationObserver to keep dynamically injected modals/overlays visible.
What’s still broken:
- Bare
header,footer, and.page-headerare still in the hide list. The content boundary pass unhides them, then immediately re-hides them in the same pass ($contentBoundary.find( allSelectorsToHide ).addClass('bbapp_hide_all')). And the direct CSS rule still applies on top of that. A<header class="page-header">archive title and<header class="entry-header">post titles are still hit. .bbapp_main_content_areais still unwrapped at the end.bbapp_show_content_areamust still live on#primary, not the wrapper.
The bbapp_mobile_view_visible_selectors filter is the right tool for future content that just needs class cleanup (block theme content sections, plugin meta areas outside the_content()). It doesn’t help with the direct CSS rule problem. Use it alongside the CSS override, not instead of it.
Based on reverse-engineering NativeAppPage::fallback_support() in the BuddyBoss App plugin, and on our own theme build against it. Original analysis against v2.12 to v2.13; update notes for v2.14.2+.
Read Other Technical Snippets
[email protected]
Have a BuddyBoss problem that you need help with?