> For the complete documentation index, see [llms.txt](https://alhena.gitbook.io/docs/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://alhena.gitbook.io/docs/developer-reference/website-sdk/examples/custom-launcher-nudge.md).

# Custom Launcher & Nudge UI

This example shows how to hide Alhena's default launcher button and nudge UI and render **your own** in their place — while still being driven by the same standard [nudge](/docs/features/chat-widget/nudges.md) logic (page matching, timer/scroll triggers, dismissal rules) configured in your dashboard.

This is useful when you want the chat entry point and proactive prompts to match your site's design system exactly, while keeping the targeting and behavior managed in Alhena.

## What You'll Learn

* Hide the built-in launcher and nudge with the [`hideDefaultUI`](/docs/developer-reference/website-sdk/javascript-api.md#configuration-options-reference) config option
* Build a custom launcher button that opens/closes the widget with [`toggle()`](/docs/developer-reference/website-sdk/javascript-api.md#toggle-options)
* Render a custom nudge from the [`nudge:loaded`](/docs/developer-reference/website-sdk/events.md#nudge-loaded), [`nudge:triggered`](/docs/developer-reference/website-sdk/events.md#nudge-triggered), and [`nudge:hidden`](/docs/developer-reference/website-sdk/events.md#nudge-hidden) events
* Run the standard nudge click behavior from your own UI with [`clickNudge()`](/docs/developer-reference/website-sdk/javascript-api.md#clicknudge)

## How It Works

| Piece                 | Role                                                                                                                                        |
| --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- |
| `hideDefaultUI: true` | Hides Alhena's default launcher button **and** nudge UI. The nudge events still fire.                                                       |
| `nudge:loaded`        | Fires when a nudge is matched for the current page — use it to populate (but keep hidden) your custom nudge.                                |
| `nudge:triggered`     | Fires when the standard show logic activates (timer elapsed / scroll threshold met, widget closed, not dismissed) — reveal your nudge here. |
| `nudge:hidden`        | Fires when the nudge should come down (dismissed, chat opened, FAQ widget used, page no longer matches) — hide your nudge here.             |
| `clickNudge()`        | Runs the same analytics + open-conversation behavior as the built-in nudge, so your custom nudge behaves identically.                       |

{% hint style="info" %}
Register your event handlers inside a [`widget:loaded`](/docs/developer-reference/website-sdk/events.md#widget-loaded) handler (or after the SDK `<script>` tag) so the SDK is ready before you subscribe.
{% endhint %}

## Code Example

```html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Custom Launcher + Nudge</title>
    <style>
        body {
            font-family: system-ui, sans-serif;
            margin: 0;
            padding: 40px;
            min-height: 200vh; /* tall page so scroll-triggered nudges can fire */
            background: #f4f5f7;
        }

        /* ---- Custom launcher button (replaces the default Alhena launcher) ---- */
        #my-launcher {
            position: fixed;
            bottom: 24px;
            right: 24px;
            width: 60px;
            height: 60px;
            border-radius: 50%;
            border: none;
            cursor: pointer;
            background: #6c2bd9;
            color: #fff;
            font-size: 26px;
            box-shadow: 0 6px 18px rgba(0, 0, 0, 0.25);
            z-index: 999999;
            display: flex;
            align-items: center;
            justify-content: center;
        }

        /* ---- Custom nudge UI (built from nudge:loaded / nudge:triggered) ---- */
        #my-nudge {
            position: fixed;
            bottom: 96px;
            right: 24px;
            max-width: 300px;
            background: #fff;
            border-radius: 14px;
            padding: 16px;
            box-shadow: 0 10px 32px rgba(24, 39, 75, 0.18);
            z-index: 999999;
            display: none; /* hidden until nudge:triggered */
            font-size: 15px;
            color: #1f2937;
        }
        #my-nudge.visible { display: block; }
        #my-nudge .nudge-text { cursor: pointer; line-height: 1.4; }
        #my-nudge .nudge-btn {
            margin-top: 12px;
            background: #6c2bd9;
            color: #fff;
            border: none;
            border-radius: 999px;
            padding: 8px 16px;
            cursor: pointer;
            font-weight: 600;
        }
        #my-nudge .nudge-close {
            position: absolute;
            top: -10px;
            right: -10px;
            width: 26px;
            height: 26px;
            border-radius: 50%;
            border: none;
            background: #fff;
            box-shadow: 0 2px 6px rgba(0, 0, 0, 0.2);
            cursor: pointer;
        }
    </style>
</head>
<body>
    <h1>Custom Launcher + Custom Nudge</h1>
    <p>Scroll down / wait for the nudge trigger. Watch the console for event logs.</p>

    <!-- Your own launcher button -->
    <button id="my-launcher" aria-label="Open chat">💬</button>

    <!-- Your own nudge container (populated from SDK events) -->
    <div id="my-nudge" role="dialog" aria-label="Suggestion">
        <button class="nudge-close" aria-label="Dismiss">×</button>
        <div class="nudge-text"></div>
        <button class="nudge-btn" style="display:none"></button>
    </div>

    <!-- ============ 1. SDK config: hide the default launcher + nudge ============ -->
    <script>
        document.gleenConfig = {
            company: 'your-company-key',
            apiBaseUrl: 'https://app.alhena.ai',
            hideDefaultUI: true, // <-- hides default launcher button AND default nudge
        };
    </script>

    <!-- ============ 2. Load the SDK ============ -->
    <script src="https://app.alhena.ai/sdk/gleenWidget.js"></script>

    <!-- ============ 3. Wire up the custom UI to the JS APIs ============ -->
    <script>
        window.gleenWidget.on('widget:loaded', function () {
            const g = window.gleenWidget;

            const launcher = document.getElementById('my-launcher');
            const nudgeEl = document.getElementById('my-nudge');
            const nudgeTextEl = nudgeEl.querySelector('.nudge-text');
            const nudgeBtnEl = nudgeEl.querySelector('.nudge-btn');
            const nudgeCloseEl = nudgeEl.querySelector('.nudge-close');

            function hideNudge() {
                nudgeEl.classList.remove('visible');
            }

            // --- Custom launcher toggles the chat window ---
            launcher.addEventListener('click', function () {
                g.toggle();
                hideNudge(); // opening chat should dismiss the nudge
            });

            // Keep the launcher icon in sync with widget state (optional nicety)
            g.on('widget:opened', function () { launcher.textContent = '✕'; });
            g.on('widget:closed', function () { launcher.textContent = '💬'; });

            // --- nudge:loaded: a nudge was matched for this page; prepare (but hide) UI ---
            g.on('nudge:loaded', function (nudge) {
                console.log('[nudge:loaded]', nudge);

                // nudge.nudge_texts is an array; show the first
                nudgeTextEl.textContent = nudge.nudge_texts[0] || '';

                if (nudge.nudge_button_text) {
                    nudgeBtnEl.textContent = nudge.nudge_button_text;
                    nudgeBtnEl.style.display = '';
                } else {
                    nudgeBtnEl.style.display = 'none';
                }
            });

            // --- nudge:triggered: standard logic says show it now (timer/scroll/etc.) ---
            g.on('nudge:triggered', function (nudge) {
                console.log('[nudge:triggered]', nudge);
                nudgeEl.classList.add('visible');
            });

            // --- nudge:hidden: standard logic says take it down (dismissed, chat opened, ...) ---
            g.on('nudge:hidden', function (nudge) {
                console.log('[nudge:hidden]', nudge);
                hideNudge();
            });

            // --- Clicking your custom nudge runs the SAME logic as the built-in nudge ---
            function fireNudgeClick() {
                g.clickNudge();
                hideNudge();
            }
            nudgeTextEl.addEventListener('click', fireNudgeClick);
            nudgeBtnEl.addEventListener('click', fireNudgeClick);

            // --- Dismiss your custom nudge ---
            nudgeCloseEl.addEventListener('click', hideNudge);
        });
    </script>
</body>
</html>
```

## Walkthrough

1. **Hide the default UI.** Setting `hideDefaultUI: true` in `document.gleenConfig` suppresses both Alhena's floating launcher button and its built-in nudge. The widget itself still loads, and all nudge events still fire.
2. **Build your launcher.** A plain `<button>` calls [`window.gleenWidget.toggle()`](/docs/developer-reference/website-sdk/javascript-api.md#toggle-options) to open and close the chat. Listening to [`widget:opened`](/docs/developer-reference/website-sdk/events.md#widget-opened) / [`widget:closed`](/docs/developer-reference/website-sdk/events.md#widget-closed) lets you swap the icon to reflect state.
3. **Prepare your nudge on `nudge:loaded`.** When a nudge matches the current page, the [`nudge:loaded`](/docs/developer-reference/website-sdk/events.md#nudge-loaded) event delivers its text and button label. Populate your element but keep it hidden.
4. **Reveal on `nudge:triggered`.** When the standard trigger condition is met, [`nudge:triggered`](/docs/developer-reference/website-sdk/events.md#nudge-triggered) fires — show your element.
5. **Hide on `nudge:hidden`.** [`nudge:hidden`](/docs/developer-reference/website-sdk/events.md#nudge-hidden) fires whenever the nudge should come down — the visitor dismissed it, opened the chat, used the Product FAQ widget, or navigated to a page with no nudge. Hide your element.
6. **Click through with `clickNudge()`.** When the visitor clicks your nudge, call [`window.gleenWidget.clickNudge()`](/docs/developer-reference/website-sdk/javascript-api.md#clicknudge) so it fires the same analytics and opens the conversation exactly like the built-in nudge.

## Related

* [JavaScript API](/docs/developer-reference/website-sdk/javascript-api.md) - `toggle()`, `clickNudge()`, and the `hideDefaultUI` config option
* [Events Reference](/docs/developer-reference/website-sdk/events.md) - `nudge:loaded`, `nudge:triggered`, and `nudge:hidden`
* [AI Nudges](/docs/features/chat-widget/nudges.md) - Configuring nudges in the dashboard
