Which launcher and display mode should I use for the CMD+K widget?

The launcher is what your visitor clicks; the display mode is how the chat surface appears once it opens. Both are picked in the dashboard and can be overridden per page with data-cmdk-* attributes on the script tag. This page documents every launcher (bubble, bar, pill, avatar, tab, embed, custom), every display mode (overlay, inline, fullpage), the full attribute reference, and the window.CommandK JavaScript API.

Launcher vs display mode

Two independent settings control how the widget shows up on your page:

  • Launcher (data-cmdk-launcher) — the element the loader injects for visitors to click: a corner bubble, an avatar, a side tab, a bottom pill, an in-page command bar, or nothing at all (custom).
  • Display mode (data-cmdk-display) — how the chat surface appears once opened: a floating overlay, a docked inline side panel, or a full-page takeover.

Any launcher can be combined with any display mode, with one shortcut: data-cmdk-launcher="embed" always uses the fullpage display. Every value below can be set in the dashboard (Install tab) and updates live on every installed page; setting the attribute on the script tag overrides the dashboard for that page only.

Resolution order

For each setting the loader uses, in order: the data-cmdk-* attribute on the script tag → the widget's dashboard config → the built-in default listed in the attribute reference.

Launchers

bubble (default)

A 56 px round button fixed in the bottom corner set by data-cmdk-position, filled with your accent color. Click toggles the chat.

html
<script async
  src="https://commandplusk.com/embed/widget.js"
  data-cmdk-widget="<WIDGET_ID>"
  data-cmdk-launcher="bubble"
  data-cmdk-position="right">
</script>

avatar

Same footprint and position as the bubble, but rendered as the assistant's "face": your widget icon (the square icon/favicon set in the dashboard, falling back to the logo) inside an accent-colored ring with a soft pulse and a green online dot. With no icon or logo set it shows a gradient sparkle instead. Good when the assistant has a name and personality.

html
<script async
  src="https://commandplusk.com/embed/widget.js"
  data-cmdk-widget="<WIDGET_ID>"
  data-cmdk-launcher="avatar">
</script>

tab

A slim vertical "Ask AI" tab glued to the middle of the left or right viewport edge (feedback-drawer style). Uses data-cmdk-position for the side. Least intrusive of the floating launchers — nothing covers your page's bottom corners.

html
<script async
  src="https://commandplusk.com/embed/widget.js"
  data-cmdk-widget="<WIDGET_ID>"
  data-cmdk-launcher="tab"
  data-cmdk-position="left">
</script>

pill

A translucent pill fixed at the bottom center showing your placeholder text and a ⌘K hint. Clicking it or pressing ⌘K/Ctrl+K toggles the chat. data-cmdk-position has no effect on the pill itself (it is always centered) but still decides which side the overlay/inline panel opens on.

html
<script async
  src="https://commandplusk.com/embed/widget.js"
  data-cmdk-widget="<WIDGET_ID>"
  data-cmdk-launcher="pill"
  data-cmdk-placeholder="Ask anything about your account…">
</script>

bar (in-page command palette)

Instead of a floating button, the loader mounts a ⌘K-style search bar inside an element you own — a page header, a docs sidebar, an empty state. data-cmdk-target is required: it's the CSS selector of the mount point and can't come from the dashboard because it's page-specific. If the selector matches nothing when the script runs, the loader warns in the console and falls back to the bubble.

Focusing the bar opens a dropdown of your configured starter prompts (Overview → Suggested prompts); typing filters them and offers to ask the free-text query. ↑/↓ navigate, ↵ runs the highlighted item or the typed text, Esc closes, and ⌘K/Ctrl+K focuses the bar from anywhere. Selecting an item opens the assistant and sends the message. Re-running the snippet into the same mount (SPA re-render, duplicated tag) replaces the existing bar rather than stacking a second one.

html
<div id="cmdk-mount" style="max-width: 560px"></div>

<script async
  src="https://commandplusk.com/embed/widget.js"
  data-cmdk-widget="<WIDGET_ID>"
  data-cmdk-launcher="bar"
  data-cmdk-target="#cmdk-mount"
  data-cmdk-placeholder="Search or ask…">
</script>

Mount point must exist first

Place the mount element above the script tag, or make sure your framework has rendered it before the loader executes. A target that appears later is not picked up.

embed (full-page assistant)

Shorthand for "bubble launcher + ⌘K/Ctrl+K hotkey + fullpage display". The visitor gets a corner bubble; opening it takes over the whole viewport with the full-page assistant surface (personalized welcome, conversation-history sidebar). Any data-cmdk-display value is ignored for this launcher.

html
<script async
  src="https://commandplusk.com/embed/widget.js"
  data-cmdk-widget="<WIDGET_ID>"
  data-cmdk-launcher="embed">
</script>

custom (your own button)

Nothing is injected into your page. Open, close, and toggle the chat from any element you own through window.CommandK (see the JavaScript API) or the cmdk:host:* postMessage API. Works with every display mode — pair it with fullpage for a full-page assistant behind your own "Ask AI" menu item.

html
<!-- Queue calls made before the async loader lands (see JavaScript API below) -->
<script>
  window.CommandK = window.CommandK || {
    q: [], open: function () { this.q.push(["open", arguments]); },
  };
</script>

<script async
  src="https://commandplusk.com/embed/widget.js"
  data-cmdk-widget="<WIDGET_ID>"
  data-cmdk-launcher="custom"
  data-cmdk-display="inline">
</script>

<button onclick="window.CommandK.open()">Ask AI</button>

Display modes

overlay (default)

When to use: marketing sites, docs, and most product pages — the classic support-chat feel. A floating 380 × 600 px panel (capped at 80% of the viewport height) appears above your content, anchored to the bottom corner on the data-cmdk-position side, just above the launcher. The chat iframe is served from /embed/w/<WIDGET_ID>.

html
<script async
  src="https://commandplusk.com/embed/widget.js"
  data-cmdk-widget="<WIDGET_ID>"
  data-cmdk-display="overlay">
</script>

inline (docked side panel)

When to use: in-app copilots where visitors keep working while chatting — Grafana or Linear style. The chat becomes a full-height panel docked to the left or right edge (data-cmdk-position), sliding in when opened. If the viewport is wider than the panel width plus 320 px, the page's <body> margin is animated so your content reflows next to the panel; on narrower viewports the panel slides over the content instead.

html
<script async
  src="https://commandplusk.com/embed/widget.js"
  data-cmdk-widget="<WIDGET_ID>"
  data-cmdk-display="inline"
  data-cmdk-panel-width="380">
</script>

Visitors can resize the inline panel

On desktop viewports (640 px and wider) the loader adds a drag handle on the panel's inner edge. Visitors drag it — or focus it and press ←/→ (20 px per step, 40 px with Shift) — to resize between 320 and 720 px. The chosen width is saved in localStorage under cmdk:width:<WIDGET_ID> and reused on later visits to the same origin, taking precedence over data-cmdk-panel-width and the dashboard value. The handle is hidden on mobile viewports.

fullpage (viewport takeover)

When to use: a dedicated "AI assistant" destination — Mixpanel/Linear style — with a personalized welcome, conversation-history sidebar, and selectable visual styles. When opened, the iframe covers the entire viewport; it is served from /embed/full/<WIDGET_ID> rather than /embed/w/. Open it from any launcher, the ⌘K hotkey (embed/pill/bar launchers), or your own button via window.CommandK.open(); the visitor closes it to return to your app.

html
<script async
  src="https://commandplusk.com/embed/widget.js"
  data-cmdk-widget="<WIDGET_ID>"
  data-cmdk-display="fullpage">
</script>

<!-- equivalent shorthand (also adds the ⌘K hotkey):
<script async src="https://commandplusk.com/embed/widget.js"
  data-cmdk-widget="<WIDGET_ID>" data-cmdk-launcher="embed"></script> -->

Collapse and expand

The full-page surface has header buttons to collapse itself into a floating 420 × 680 px window in the bottom corner and expand back. Under the hood the iframe posts cmdk:collapse / cmdk:expand messages to the loader, which swaps the iframe between full-viewport and floating styles and acknowledges with cmdk:set_collapsed. These are internal loader ↔ iframe messages — your page doesn't need to send or handle them; they are listed here so the traffic isn't a surprise in your console.

Docked / native embed (inside your own layout)

When to use: an "AI assistant" page or panel that is part of your product's own layout — your navigation, your header, your typography — rather than a takeover on top of it. Combine custom + fullpage (or inline) with two attributes: data-cmdk-chrome="minimal" strips the surface's window controls and its logo/name header, and data-cmdk-font="inherit" makes it use your page font. Then move the iframe (iframe[data-cmdk-el="iframe"]) into your content area and size it like any other element; the loader still handles the token handshake, theme, and prefill.

html
<script async
  src="https://commandplusk.com/embed/widget.js"
  data-cmdk-widget="<WIDGET_ID>"
  data-cmdk-token="<END_USER_JWT>"
  data-cmdk-launcher="custom"
  data-cmdk-display="fullpage"
  data-cmdk-chrome="minimal"
  data-cmdk-font="inherit"
  data-cmdk-theme="light">
</script>

<section id="assistant-page">
  <header><h1>Assistant</h1></header>   <!-- your header; the iframe shows no second one -->
  <div id="assistant-mount" style="height: calc(100vh - 64px)"></div>
</section>

<script>
  // Dock the iframe once the loader has created it, then open it.
  const mount = document.getElementById("assistant-mount");
  const timer = setInterval(() => {
    const frame = document.querySelector('iframe[data-cmdk-el="iframe"]');
    if (!frame || !window.CommandK) return;
    clearInterval(timer);
    mount.appendChild(frame);
    frame.style.cssText = "position:static;display:block;width:100%;height:100%;border:0;opacity:1";
    window.CommandK.open();
  }, 50);
</script>

What minimal hides — and what it keeps

Hidden: the collapse-to-window and close buttons, the history-sidebar logo + assistant name, and the assistant name in the top bar (the name renders at most once, in your header). Kept: the sidebar toggle, new conversation, conversation history, mode tabs, and the composer. "Powered by Command+K" still follows the widget's branding setting (hidden when whitelabeling is on). With no valid data-cmdk-token on a signed-in-only widget, the surface shows a themed "Sign in to continue" card with a Retry button instead of an error; append ?debug=1 to the iframe URL to see the operator detail.

Attribute reference

Everything the loader reads from the <script> tag. "Dashboard setting" means the value configured on the widget's Install tab, fetched live on every page load.

AttributeValuesDefaultNotes
data-cmdk-widgetwidget UUID— (required)Your widget ID from the dashboard Install tab. The loader exits with a console error without it.
data-cmdk-tokenHS256 JWTnone (anonymous)Signed-in mode. Values that look like placeholders (contain < or >, or don't have exactly two dots) are ignored and the widget starts anonymous.
data-cmdk-modeassistant | onboarding | learning | investigationsfirst mode configured on the widgetWhich assistant mode the chat starts in. Must be one of the modes enabled on the widget.
data-cmdk-embed-keypartner embed keynonePartner embeds only. Bypasses the widget's origin allowlist so a third-party product can host the widget on origins you can't predict. Values containing < or > are treated as placeholders and ignored.
data-cmdk-themelight | dark | autodashboard settingauto follows the visitor's OS preference. Any other value is ignored.
data-cmdk-accenthex color, e.g. #6366f1dashboard setting, else #3B82F6Launcher and brand color.
data-cmdk-positionright | leftdashboard setting, else rightWhich side the bubble, avatar, and tab launchers sit on, and which edge the overlay and inline panels anchor to. The pill is always bottom-center.
data-cmdk-launcherbubble | bar | pill | avatar | tab | embed | customdashboard setting, else bubbleSee Launchers below. embed forces the fullpage display mode.
data-cmdk-placeholdertextdashboard setting, else “Ask anything…”Input placeholder for the bar launcher and the label text on the pill.
data-cmdk-displayoverlay | inline | fullpagedashboard setting, else overlaySee Display modes below. Ignored when the launcher is embed (always fullpage).
data-cmdk-panel-widthinteger px, 320–720dashboard setting, else 420Inline mode only. Values below 320 fall back to 420; values above 720 are clamped to 720. A width the visitor saved by resizing overrides this.
data-cmdk-targetCSS selector, e.g. #cmdk-mountnoneRequired for the bar launcher — where the palette is mounted. Missing or unmatched → falls back to bubble.
data-cmdk-localeen | trnavigator.languageLanguage of the guided-tour chrome (Next, Skip, Done…). Assistant replies follow the visitor's language regardless.
data-cmdk-chromewindowed | minimalwindowedminimal hides the surface's own window controls (collapse to window, close) and its logo/assistant-name header (history-sidebar header and top bar on fullpage; logo + name on overlay/inline) — for iframes you dock inside your own layout, where your header is the only header. New-conversation, history, and mode controls stay. Any other value is treated as windowed.
data-cmdk-fontinherit | CSS font-family stackdashboard font, else Inter/systeminherit reads your page's <body> font-family at load time and forwards it; any other value is used as the family stack verbatim. Only letters, digits, spaces, commas, quotes, and hyphens are kept (max 200 characters) — no url() or other CSS. Overrides the font chosen in the dashboard for that page. The font must already be loaded on your page; the widget doesn't fetch it.

JavaScript API (window.CommandK)

Put this in your <head>, before the loader tag. It makes every call safe from the first paint: anything called while the loader is still downloading is queued and replayed as soon as it lands.

html
<script>
  window.CommandK = window.CommandK || {
    q: [],
    open:     function () { this.q.push(["open", arguments]); },
    close:    function () { this.q.push(["close", arguments]); },
    toggle:   function () { this.q.push(["toggle", arguments]); },
    setTheme: function () { this.q.push(["setTheme", arguments]); },
    isOpen:   function () { return false; },
  };
</script>

The loader exposes the same controls for every launcher and display mode — they're the only way in for custom, and handy everywhere else (open the chat from a help menu, prefill a question from an error state, match your app's theme toggle).

js
// Open the chat. With text, the composer is prefilled; submit defaults to true
// (send immediately) — pass false to let the visitor edit first.
window.CommandK.open();
window.CommandK.open("How do I invite my team?");
window.CommandK.open("How do I invite my team?", false);

window.CommandK.close();
window.CommandK.toggle();
window.CommandK.isOpen();          // → boolean

// 'light' | 'dark' | 'auto' — other values are ignored.
window.CommandK.setTheme("dark");
  • The script is loaded async, so window.CommandK only exists after the loader has run. A visitor who clicks your own button in that first moment gets nothing — with window.CommandK && window.CommandK.open() the click is silently swallowed and they have to click again. Install the queueing stub below in your page head and that never happens: calls made before the loader arrives are replayed the moment it does.
  • The same controls are mirrored as cmdk:host:open, cmdk:host:close, cmdk:host:toggle, and cmdk:host:set_theme postMessages — see postMessage host API.
  • Guided-tour helpers (fireTourEvent, snoozeWalkthroughs, resetWalkthroughs) live on the same object — see Guided tours & onboarding.

Next steps

FAQ

Which settings win — the dashboard or the script attributes?
Script attributes win. The loader resolves each setting as: data-cmdk-* attribute on the script tag → the widget's dashboard config → a hardcoded default. Leave attributes off and everything updates live from the dashboard; set one and it overrides the dashboard for that page only.
Can I use a full-page assistant without any floating button?
Yes. Use data-cmdk-launcher="custom" together with data-cmdk-display="fullpage". Nothing is injected into your page; open the full-page surface from your own button with window.CommandK.open().
Why does my bar launcher show up as a bubble?
The bar needs a mount point. If data-cmdk-target is missing or the selector matches nothing when the script runs, the loader logs a warning and falls back to the bubble launcher. Make sure the target element exists before the script executes.
The inline panel doesn't push my page content aside.
Inline only reflows the page when the viewport is wider than the panel width plus 320 px. On narrower viewports the panel slides over the content instead, so the page stays usable on tablets and phones.
Does the visitor's resized inline width persist?
Yes. When a visitor drags the inline panel edge (or resizes it with the keyboard), the width is stored in localStorage under cmdk:width:<widgetId> for that origin and reused on the next visit. A stored width overrides both data-cmdk-panel-width and the dashboard value, within the 320–720 px range.
Can I embed the assistant inside my own app layout without its header and close buttons?
Yes. Add data-cmdk-chrome="minimal" (with launcher="custom" and display="fullpage" or "inline") and move the iframe into your content area. The surface then hides its collapse/close buttons and its own logo/name header, so your app's header is the only header. Add data-cmdk-font="inherit" to make it use your page font.
Which launchers register the ⌘K / Ctrl+K shortcut?
pill, bar, and embed. The bubble, avatar, tab, and custom launchers do not bind a hotkey; wire one yourself with window.CommandK.toggle() if you want it.