Custom Launcher: your own button instead of the default bubble
The default round bubble rarely fits the rest of the page. With the widget API, any element becomes the launcher: a button in the header, a gear in the bottom-right corner, a menu item.
The Admin Center prerequisite
Before any code comes into play: in the Admin Center, under Channels → Messaging → Web Widget, set the launcher style to “Custom Launcher”. That keeps the default bubble from ever showing up. The setting is account-wide. On pages using embedded mode it doesn’t matter (there’s no bubble there anyway); on floating pages your own button takes over.
Open, close, mirror the state
meinKnopf.addEventListener('click', () =>
zE('messenger', istOffen ? 'close' : 'open')
);
zE('messenger:on', 'open', () => setzeZustand(true));
zE('messenger:on', 'close', () => setzeZustand(false));
The events fire even when someone closes the widget through its own UI. So the button stays in sync (don’t forget aria-expanded).
The unread counter
zE('messenger:on', 'unreadMessages', (anzahl) => {
badge.textContent = String(anzahl);
badge.hidden = anzahl === 0;
});
When the team replies while the widget is closed, the badge keeps count. A small shake animation makes it noticeable, behind prefers-reduced-motion: no-preference.
close-on-load: the underrated detail
The widget remembers its state across page changes. If it was open on the last page, it starts open on the next one, right past your own launcher. That’s why the official pattern closes it immediately after load:
zE('messenger', 'close');
From there, only your button decides when the chat shows up.