Pet recipes

Turn your pet into a helpful companion: action feedback, onboarding, and visitor chat.

A companion that responds to your website

Your pet can celebrate a completed task, explain a next step, or speak a reply from your own chat service. The exported widget provides the character, animations and speech bubbles. Your website decides when to trigger them and supplies any forms, application logic or backend services.

Build a pet, or choose one from the gallery, then install the widget. These examples use the default global name PetWidget; substitute your configured global name if you changed it. Use a spritesheet with the named moves mapped in the builder. Single-image pets can speak, but do not gain new poses from an animation command.

Choose a role

Experience Trigger after Pet response Integration
Shopping companion An item is successfully added Wave and confirm the addition Existing cart success handler
Form guide A step is completed or validation fails Celebrate progress or suggest a correction Your form validation logic
Onboarding buddy A setup milestone is saved Explain the next step Application success callback
Learning partner An answer is checked Jump for success; offer a hint for a retry Quiz or lesson logic
Search helper Search finishes with no results Suggest a broader query Search result handler
Upload companion Upload begins, succeeds or fails Wait, celebrate, or explain a retry Upload lifecycle callbacks
Reading companion A visitor explicitly asks for help Explain a term or summarize a section Local rules or your backend
Visitor chat A visitor submits a question Think, then show a short reply Accessible chat form + your backend

Start with one useful moment. Avoid commentary on every click: the pet should support the visitor’s task.

React to a successful action

Call the pet from the same callback that confirms your operation succeeded. A button click alone does not mean a purchase, save or booking completed.

// Run after your cart API confirms the item was added.
function onCartAddSuccess() {
  window.PetWidget.trigger('cart.item-added', {
    label: 'Added to your cart',
    message: 'Ready when you are. Your item is in the cart.',
    task: { state: 'waving', duration: 2200 }
  });
}

trigger(name, detail) sends a named action into the pet. task controls its animation; message supplies a local reply. This example makes no network request through the pet.

For framework components or separate scripts, the browser event interface gives you the same behavior:

window.dispatchEvent(new CustomEvent('embedyourpet:trigger', {
  detail: {
    name: 'lesson.completed',
    message: 'One more skill in your toolkit!',
    task: { state: 'jumping', duration: 1800 }
  }
}));

The widget does not automatically observe arbitrary page actions. Add this call to your existing handler. Emit only after the exported widget has mounted; early calls are not queued.

Guide a multi-step flow

Use a short message alongside the normal interface, keeping validation errors and status text visible in the form itself.

function onProfileSaved() {
  window.PetWidget.say({
    title: 'Profile saved',
    body: 'Next, choose your first project.',
    state: 'waving',
    duration: 4000
  });
}

function onUploadStarted() {
  window.PetWidget.runTask({ state: 'waiting', duration: 10000 });
}

function onUploadFinished() {
  window.PetWidget.say({ body: 'Upload complete.', state: 'jumping' });
}

function onUploadFailed() {
  window.PetWidget.say({
    body: 'The upload did not finish. Try again using the upload button.',
    state: 'failed'
  });
}

Task durations are visual timers, not a connection to actual upload progress. Your success and failure handlers must update the pet. Keep the real progress indicator in your application.

Let visitors chat with the pet

Chat is an integration recipe, not a built-in hosted chat product. You supply the input, transcript and server; the pet displays the response. You can use a deterministic FAQ service or an AI responder behind the same endpoint.

Place your exported widget script and this integration near the end of the body. Replace /your-pet.js with the downloaded filename. The form remains usable with a keyboard, and its transcript preserves replies after a speech bubble disappears.

<form id="pet-chat">
  <label for="pet-question">Ask our pet a question</label>
  <input id="pet-question" name="question" maxlength="500" required>
  <button type="submit">Ask</button>
</form>
<p id="pet-chat-status" role="status"></p>
<div id="pet-chat-transcript" role="log" aria-label="Pet conversation"
     aria-live="polite" aria-relevant="additions"></div>
<script src="/your-pet.js"></script>
<script>
document.addEventListener('DOMContentLoaded', function () {
  const form = document.querySelector('#pet-chat');
  const input = document.querySelector('#pet-question');
  const button = form.querySelector('button');
  const status = document.querySelector('#pet-chat-status');
  const transcript = document.querySelector('#pet-chat-transcript');
  const pet = window.PetWidget;
  let busy = false;

  function addLine(speaker, text) {
    const line = document.createElement('p');
    line.textContent = speaker + ': ' + text;
    transcript.appendChild(line);
  }

  pet.setResponder(async function (action) {
    if (action.name !== 'chat.question') return 'How can I help?';
    try {
      const response = await fetch('/api/pet-reply', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ question: action.context.question }),
        signal: AbortSignal.timeout(12000)
      });
      if (!response.ok) throw new Error('Reply unavailable');
      const result = await response.json();
      if (typeof result.body !== 'string' || !result.body.trim()) {
        throw new Error('Invalid reply');
      }
      const body = result.body.slice(0, 600);
      addLine('Pet', body);
      return { body, state: 'waving', duration: 7000 };
    } catch (error) {
      const body = 'I could not reply right now. Please try again.';
      addLine('Pet', body);
      return { body, state: 'failed' };
    } finally {
      busy = false;
      button.disabled = false;
      status.textContent = '';
    }
  });

  form.addEventListener('submit', function (event) {
    event.preventDefault();
    const question = input.value.trim();
    if (!question || busy) return;
    busy = true;
    button.disabled = true;
    status.textContent = 'Waiting for a reply…';
    addLine('You', question);
    input.value = '';
    // No task, state, preset or message: allow the responder to handle it.
    pet.trigger('chat.question', { context: { question } });
  });
});
</script>

Your server should return JSON like this:

{ "body": "You can download a pet from its gallery page." }

The example handles one question at a time and uses a 12-second timeout. It does not implement conversational memory. To add follow-up conversations, keep a bounded history on your server and identify the visitor through your own session, rather than trusting a browser-provided account ID.

Backend checklist: validate question length, limit request rates and cost, keep provider credentials on the server, apply your framework’s CSRF protection where required, and return a short plain-text answer. Explain the chat provider and retention policy before collecting questions. Only send the context needed to answer. If an AI generates replies, make that clear and provide a human contact option.

Treat replies as information. A pet should not place orders, change an account or authorize access solely because a visitor asked it to. Your normal application permissions and confirmation flow still handle those actions.

Reuse local replies before adding AI

Most product moments need predictable copy. Define presets before loading the exported script:

<script>
window.PetWidgetOptions = {
  presets: {
    setupDone: {
      title: 'All set',
      body: 'Your workspace is ready. Open your first project.',
      state: 'waving',
      duration: 5000
    }
  }
};
</script>
<script src="/your-pet.js"></script>

Then call window.PetWidget.trigger('setup.completed', { preset: 'setupDone' }) from your setup success handler. Use a responder for open-ended questions, and local messages for known outcomes.

Routing detail: a trigger with task, preset, message, state, duration or reply is handled locally and bypasses the responder. For chat, send the event name and context only. An asynchronous responder automatically shows the thinking bubble while its promise is pending.

Make it helpful and accessible

Keep the pet clear of navigation, consent controls and form buttons. Keep essential errors, success messages and instructions in the page; a temporary speech bubble must not be their only location. Offer a way to dismiss or mute optional commentary in your own interface. Check the widget on mobile, with a keyboard and with reduced motion enabled. Animation feedback should never require a visitor to see a particular pose to understand the result.

Before launch, test success, failure, slow replies, repeated submissions, unavailable endpoints and pets missing an animation row. The chat example uses plain text, not generated HTML, and keeps a readable transcript independent of the pet bubble.

For method signatures, available states, reply fields and lifecycle events, see the Message API reference.