'Ask Leo to..' Buttons
Buttons in your app that pre-fill Leo's chat with a ready-made command — one click instead of typing the request out.
Your app runs inside the Leo workspace, side by side with the chat. That means pages in your app can talk to Leo's chat interface directly: a button on one of your pages can drop a pre-written command straight into Leo's text field, ready to send. We call these "Ask Leo to.." buttons.
The short version: put the command text in a
data-command attribute on a button, and a five-line script sends it
to Leo with window.parent.postMessage. Leo's chat input fills in,
the user reviews or edits it, and hits send. You can add one just by asking Leo.
Why you'd want one
Some actions in your app are really requests for Leo — things the AI agent handles better than a hand-built form. A classic example: a "Ask Leo to Reset" button next to each account on a sign-in page. Clicking it pre-fills Leo's chat with:
Ask Leo to reset the password for jane@example.com to [new_password] so I can sign into the application.
The user swaps [new_password] for the password they want, hits send,
and Leo performs the reset — no admin panel, no database console, no support
ticket. The same pattern works for anything you'd otherwise explain in chat:
"Ask Leo to add a row to this table," "Ask Leo to fix this page," "Ask Leo to
export this month's report."
The button doesn't act on its own — it just writes the message for the user. They still see it, can edit it, and choose to send. That keeps a human in the loop while removing the "what do I even type?" hurdle.
The pattern
1. The button
Give each button a shared class and put the full command in a
data-command attribute. Use square-bracket placeholders like
[new_password] for anything the user should fill in before sending:
<button class="ask-leo-btn"
data-command="Ask Leo to reset the password for jane@example.com to [new_password] so I can sign into the application.">
Ask Leo to Reset
</button>
2. The script
One small script wires up every button on the page. It posts a message to the parent window — the Leo workspace your app is embedded in:
<script>
(function() {
'use strict';
document.querySelectorAll('.ask-leo-btn').forEach(function(btn) {
btn.addEventListener('click', function(e) {
e.stopPropagation();
var command = this.dataset.command;
if (window.parent !== window) {
window.parent.postMessage({
source: 'launchpad',
type: 'prefill-chat',
command: command
}, '*');
}
});
});
})();
</script>
That's the whole integration. The pieces that matter:
-
source: 'launchpad'andtype: 'prefill-chat'— this exact pair is what Leo's chat interface listens for. Any other values are ignored. -
command— the text that lands in Leo's chat input, verbatim. -
window.parent !== window— the guard that makes the button a no-op when the page is opened outside the Leo workspace (for example, on your live public URL). Visitors on the standalone site can click it and nothing happens. -
'*'as the target origin — the chat interface runs on a different origin than your app's pages, so the message is addressed to any parent. That's fine here because the message only pre-fills text; see the caveat below about what not to put in it.
3. What Leo's side does
You don't build the receiving end — it ships with every LlamaPress workspace.
When the message arrives, Leo's chat interface puts the command into the text
field, focuses it, and updates the send button, so the user just reviews and
sends. Optionally, you can add auto_send: true to the message and
Leo will send the command immediately instead of waiting:
window.parent.postMessage({
source: 'launchpad',
type: 'prefill-chat',
command: command,
auto_send: true // skips review — use sparingly
}, '*');
Prefer pre-fill over auto-send. The strength of this pattern is
that the user sees the command before it runs and can adjust it. Reserve
auto_send for commands with no placeholders and no surprises.
Good habits
- Write the command as a complete, self-contained request. Leo reads it with no other context, so include the specifics — the email address, the page, the record — right in the text, and end with the goal ("…so I can sign into the application").
-
Use
[bracketed_placeholders]for anything the user must supply, and mention it in the UI ("replace [new_password] with the password you want"). -
Never put secrets in
data-command. The attribute is visible in the page source to anyone who can load the page — that's why the reset example has the user type the new password into the chat rather than baking one into the button. -
Design for the standalone site too. Outside the Leo workspace
the buttons silently do nothing; if a page is public-facing, consider hiding
them or showing a hint when
window.parent === window.
Adding one to your app
You don't need to write any of this by hand — it's Leo's own interface, so Leo knows the pattern. Just describe the button you want:
"Add an 'Ask Leo to Reset' button next to each user on the sign-in page. When clicked, it should pre-fill your chat with a command to reset that user's password, using the launchpad prefill-chat postMessage pattern."
If you're implementing it by hand (or want the full recipe Leo follows — the Stimulus variant, the message contract, and the gotchas), the canonical implementation guide lives in the cookbook:
<%= render "wiki/cookbook_card", slug: "...", note: "optional override of the summary line" %> Title/summary are read from the guide's YAML frontmatter at render time, so the card never drifts from the guide. Unknown/invalid slug → renders nothing (a broken reference must not 500 a wiki article). %>For more on the tools built into the chat interface itself, see Leo Tools & Features, and for everyday fixes see Common Errors and Fixes.
Want help wiring an "Ask Leo to.." button into your app? Email support@llamapress.ai and we'll walk through it with you.