You add an AdMob banner to your Capacitor app, run it on a device, and the Ionic tab bar is gone, hidden under the ad. In this guide, we'll look at why the banner covers your content and fix it two ways: by letting the plugin shrink the web view, or by reserving the exact banner height in CSS. We'll also cover the Ionic CSS for tab bars, content and floating action buttons, and what changes if you come from @capacitor-community/admob.
Prerequisites
- A Capacitor 8 app, ideally with Ionic Framework.
- Consent gathered and the Google Mobile Ads SDK initialized. The AdMob GDPR consent guide walks through that part.
- The Capacitor AdMob plugin. For setup, see the Installation section of the docs.
Full transparency: we build this plugin at Capawesome, and it is part of Capawesome Insiders, a paid subscription. If you need a free option, @capacitor-community/admob is a maintained, MIT-licensed alternative, and the last section compares the two.
Why the banner overlaps
The Google Mobile Ads SDK renders the banner as a native view on top of the Capacitor web view. Your HTML is laid out as if the banner did not exist, so the ion-tab-bar slides under a bottom banner, a floating action button ends up behind it, or the last list row never scrolls into view.
This is an old problem. Issue #2 "Ad overlapping tabs" on the community plugin dates back to July 2020, and edge-to-edge layouts made it worse, as #390 from November 2025 shows for Android 15 and 16.
Hardcoding padding-bottom: 50px doesn't work either, because the default adaptive banner has no fixed height. Google's iOS banner guide caps anchored adaptive banners at 20% of the screen height with a 150px maximum, and the Android guide describes a 50 to 150 dp range. The plugin defaults to BannerSize.AdaptiveBanner and leaves the height to the SDK, so the only reliable number is the one the SDK reports after it picks a size.
Resize the web view
The fix with zero CSS is mode: 'resize'. Pass it to showBanner(...) and the plugin shrinks the web view so the banner never overlaps your web content. Your page still lays itself out at full height; the page is a bit shorter.
It works for top banners as well as bottom ones. Here the banner sits above an Ionic header, and we keep the returned id for later calls:
import { Admob, BannerSize } from '@capawesome-team/capacitor-admob';
let topBannerId: string | undefined;
const showTopBanner = async () => {
const { id } = await Admob.showBanner({
adUnitId: 'ca-app-pub-3940256099942544/6300978111',
size: BannerSize.AdaptiveBanner,
position: 'top',
mode: 'resize',
});
topBannerId = id;
};
That ad unit is Google's Android test banner. On iOS, use ca-app-pub-3940256099942544/2934735716 during development.
Resize mode covers most apps with one anchored banner. Reach for something else when:
- the banner belongs inside scrolling content (use a
frame;ShowBannerOptionsignorespositiononce a frame is set), - your layout should continue behind the banner, like a full-screen map or game canvas,
- a changing viewport height makes your CSS or
resizelisteners jump visibly.
Pad in overlay mode
Overlay is the default mode. The web view stays full size, and the banner sits on top of it, anchored to the top or bottom edge and safe-area aware, so it stays clear of the home indicator and gesture bar. Keeping content out from under it is your CSS's job.
The bannerSizeChanged event tells you how much space to reserve. Its payload carries the banner id plus width and height in CSS pixels, so the value goes straight into a CSS custom property with no unit conversion. Register the listener once and before you call showBanner(...), otherwise the first size event can arrive before anything is listening. We keep the handle so a second call to showBottomBanner() doesn't add a second handler:
import type { PluginListenerHandle } from '@capacitor/core';
import { Admob, BannerSize } from '@capawesome-team/capacitor-admob';
const BANNER_ID = 'bottom-banner';
let bannerHeight = 0;
let sizeListener: PluginListenerHandle | undefined;
const setBannerInset = (height: number) => {
document.documentElement.style.setProperty('--admob-banner-height', `${height}px`);
};
const addBannerSizeListener = async () => {
if (sizeListener) {
return;
}
sizeListener = await Admob.addListener('bannerSizeChanged', ({ id, height }) => {
if (id !== BANNER_ID) {
return;
}
bannerHeight = height;
setBannerInset(height);
});
};
const showBottomBanner = async () => {
await addBannerSizeListener();
await Admob.showBanner({
id: BANNER_ID,
adUnitId: 'ca-app-pub-3940256099942544/6300978111',
size: BannerSize.AdaptiveBanner,
position: 'bottom',
mode: 'overlay',
});
};
Why pass our own id? The listener exists before showBanner(...) resolves, so it can't compare against a returned identifier yet, but it can compare against a constant. The check also stops a second banner, say an inline one, from overwriting the bottom inset with its own height.
The property lives on document.documentElement (the :root element), so any component can read it with var(--admob-banner-height, 0px). The 0px fallback keeps the layout correct on the web and before the first event.
Ionic layout specifics
Which element gets the banner height depends on what sits at the bottom of your page. With tabs, move the ion-tab-bar up:
ion-tab-bar {
margin-bottom: var(--admob-banner-height, 0px);
}
Don't add env(safe-area-inset-bottom) or --ion-safe-area-bottom to that margin. Ionic's tab bar and footer already pad themselves for the bottom inset, and the overlay banner already sits above it, so adding it again makes the tab bar grow by the height of the gesture bar. The same goes for ion-footer.
Pages without a tab bar or footer need the space inside ion-content. Its --padding-bottom extends the scrollable area so the last item can scroll above the banner. A bottom ion-fab is positioned against ion-content itself, not its scroll padding, so it needs its own margin:
ion-content {
--padding-bottom: var(--admob-banner-height, 0px);
}
ion-fab[vertical="bottom"] {
margin-bottom: var(--admob-banner-height, 0px);
}
If some pages run without ads, scope these rules with a class on the page component. On a tabbed page you can skip the content and FAB rules entirely, because the raised tab bar already shortens the area above it.
Hide, resume, remove
Hiding a banner doesn't reset the space you reserved for it, so the CSS variable has to follow visibility. hideBanner(...) keeps the ad loaded but invisible, and resumeBanner(...) shows it again. Reusing BANNER_ID, bannerHeight and setBannerInset from above, one function handles both:
import { Admob } from '@capawesome-team/capacitor-admob';
const setBannerVisible = async (visible: boolean) => {
if (visible) {
await Admob.resumeBanner({ id: BANNER_ID });
setBannerInset(bannerHeight);
} else {
await Admob.hideBanner({ id: BANNER_ID });
setBannerInset(0);
}
};
We restore the last known height on resume instead of waiting for an event, since the README doesn't say that resuming fires bannerSizeChanged. When a user buys your ad-free tier, call removeBanner(...), set the inset to 0, and call remove() on sizeListener before clearing it.
Collapsible banners need no extra code. With collapsible: true, the banner expands and collapses, firing bannerSizeChanged each time, and the listener keeps the tab bar or padding in sync in both directions.
Community plugin comparison
@capacitor-community/admob gives the same core advice: the banner is drawn above the WebView, and you pad your content from its bannerAdSizeChanged event. It's free, while the Capacitor AdMob plugin requires Insiders and is still marked experimental in its README. Here's how they differ:
@capacitor-community/admob |
Capacitor AdMob plugin | |
|---|---|---|
| Avoiding overlap | Pad content from the size event, or offset with margin
|
mode: 'resize', or pad content from the size event |
| Offset option |
margin in dp (Android) or points (iOS) |
None |
| Position values |
TOP_CENTER, CENTER, BOTTOM_CENTER
|
'top', 'bottom', or a frame
|
| Size event |
bannerAdSizeChanged with { width, height }
|
bannerSizeChanged with { id, width, height }
|
| Units | dp (Android) and points (iOS) | CSS pixels |
| Banners at once | One | Several, each with its own id
|
| Android SDK | Legacy Google Mobile Ads SDK 25.4.x | Next-Gen Google Mobile Ads SDK |
| License | MIT | Capawesome Insiders (paid) |
The unit gap is smaller than it looks. The Android WebView sets devicePixelRatio to the screen density factor, so one CSS pixel covers about one dp, and on iOS one CSS pixel equals one point at default zoom.
If you migrate, drop the margin workaround. Setting margin to the tab bar height is a fixed dp value that ignores edge-to-edge insets (see #390). Use mode: 'resize' or the ion-tab-bar rule instead.
The SDK row matters long term: Google's deprecation schedule deprecates the legacy Android SDK versions 24 and 25 on June 30, 2027, and sunsets them on June 30, 2028, after which ads are "at risk of not serving".
Conclusion
Start with mode: 'resize' for a single top or bottom banner. Switch to overlay plus bannerSizeChanged when content has to continue behind the banner or the viewport height must stay constant, and use a frame when the banner belongs inside your content. Before shipping, test on one device with gesture navigation and one with a notch, since the inset is where most remaining overlap comes from. The edge-to-edge and safe areas guide covers that CSS.
The full post has the FAQ (including whether the reported height includes the safe-area inset) and the inline placement details, and if you have questions, drop them in the comments.
Top comments (0)