Docs / SDK reference / Drop-in element
The <mindset-agent> element
The full reference for the drop-in chat element: attributes, methods, events, theming.
The drop-in half of the SDK. One custom element that renders a complete chat interface on your page, carrying its own styles, in its own shadow root. Your page loads one script and writes one tag.
Loading it
<script src="https://YOUR-MINDSET-HOST/sdk/mindset-agent.js"></script>
<mindset-agent agent="support-bot"></mindset-agent>
<script>
document.querySelector("mindset-agent").configure({
getSession: () => myBackend.fetchAgentSession(),
});
</script>
The script registers the mindset-agent tag itself.
Configuring it
configure() is what makes the element live. Until you call it, the element renders nothing and send() won't work.
element.configure({
getSession: () => myBackend.fetchAgentSession(),
});
getSession is a function that returns the session credential, not the credential itself. Your backend creates that credential server-to-server; the element decodes it, runs on it, and renews it without your involvement.
Passing a static string won't work, because a string can't be renewed. An organization API key is dropped rather than sent.
The credential carries everything else the element needs, including your organization and Environment, so there's nothing further to configure.
Attributes and properties
Every attribute mirrors a property in both directions, so declarative and imperative configuration can't disagree. React 19 sets unknown props as DOM properties, which is why each one is a real accessor pair.
| Property | Attribute | What it is |
|---|---|---|
agent |
agent |
Which agent to run. Its handle, or its UUID. Copy either from the agent's Embed tab |
conversationId |
conversation-id |
Which conversation to resume. See below |
Note the attribute name is conversation-id in markup and conversationId as a property. HTML lowercases attribute names when it parses, so the two forms are unavoidable.
Resuming a conversation
By default, every time the element mounts your user gets a fresh conversation. If you want someone to come back to what they were doing, this is the section that matters.
It's a round trip, and you don't need to know an ID to start using one.
First visit. Leave conversation-id off. The element starts a fresh conversation. On the first completed turn, once there's something worth coming back to, it tells you the ID two ways: it fires a mindset:conversation event, and it writes the ID onto its own conversation-id attribute.
Store it against whoever is looking, in your own database, keyed to that user.
Next visit. Set conversation-id to the value you stored. The agent picks up with the history it had.
element.addEventListener("mindset:conversation", (e) => {
saveForCurrentUser(e.detail.conversationId);
});
Rules worth knowing
Prefer the event over reading the attribute back. If your stack morphs the DOM towards a template, it will strip an attribute the template doesn't declare, and the element deliberately doesn't fight your renderer to put it back. Store what the event gave you.
Clearing the attribute does nothing. That's on purpose. An unrelated DOM update must not cost a user their conversation.
Writing the same ID back is safe. A framework re-render doing exactly that is a no-op.
Writing a different ID is a reconfiguration. The running conversation stops, loudly, rather than switching silently.
One thing to get right
A conversation ID is per user. Resolve it per request, from what your backend knows about who is asking.
Templating one into static server-rendered HTML serves one user's conversation to everybody who loads the page, and no check inside the element can tell that apart from a host legitimately setting the attribute from code.
The ID is a correlation key, not a credential. It already travels in every request the element makes, and holding it grants nothing without the session credential. That's why it's safe to express as an attribute at all.
Methods
| Method | What it does |
|---|---|
configure() |
Set this instance up. Calling it again tears down the previous conversation and starts one for the new config, so a host that reconfigures on identity change can't end up with two live conversations on one element |
send() |
Send a user turn. Before configure(), this reports an error rather than silently dropping the message |
stop() |
Stop the turn in flight. Safe to call when nothing is running |
Each of these maps onto a documented headless command. The element is a thin shell over the same contract, with no private channel to the runtime.
Events
DOM CustomEvents, namespaced so they can't collide with your own, dispatched so a listener on an ancestor outside the shadow root still sees them.
| Event | detail |
|---|---|
mindset:runtime-event |
A RuntimeEvent. The full runtime vocabulary, surfaced directly. See the events reference |
mindset:error |
Carries code, message and an optional cause. Configuration and transport failures you should surface |
mindset:conversation |
Carries the conversationId. Fires once: at mount if you supplied the ID, or on the first completed turn if the element started fresh |
mindset:conversation carries the ID and nothing else, deliberately. It crosses the shadow boundary into your document, where every script on the page can read it.
These are additive only. A new event type is a compatible change; renaming or reshaping one is not. Write consumers that switch on the type and ignore anything they don't recognize.
Error codes
mindset:error detail is always structured, never prose alone. Branch on code and treat an unknown code as a generic failure.
| code | Raised when |
|---|---|
missing-agent |
configure() ran with no agent, from either the attribute or the config |
invalid-auth |
configure() got no getSession function |
not-configured |
send() was called with no live conversation, either before configure() or after a real disconnect |
stale-configuration |
An attribute changed after configure(), so the live conversation no longer matches what the element reports. Call configure() again |
turn-failed |
The turn itself failed: transport, credentials, or the run |
turn-failed is worth handling specifically. It's the class of failure you can't diagnose any other way, because the SDK fetches its envelope before the first runtime event, so an expired credential produces no mindset:runtime-event at all.
Theming
Theming is CSS custom properties that pierce the shadow boundary. There's no ::part() API in this version, so don't plan around one.
The element ships a default set of colors on its own shadow host, so an embed on a page with no theme renders in real colors with no cooperation from you. Add class="dark" to the tag for the dark set.
Override any channel from your own stylesheet. A rule on the host element wins over the element's internal defaults.
mindset-agent {
--ch-accent: 124 58 237; /* space-separated r g b */
--ch-page: 255 255 255;
}
Values are space-separated RGB components, not hex and not rgb().
The full vocabulary is 62 channels. The list may change as the interface develops, so treat it as the current set rather than a fixed contract.
--ch-page --ch-panel --ch-card
--ch-surface --ch-input --ch-card-hover
--ch-selected --ch-overlay --ch-text-heading
--ch-text-primary --ch-text-secondary --ch-text-muted
--ch-logo --ch-edge --ch-edge-subtle
--ch-edge-strong --ch-btn-primary --ch-btn-primary-hover
--ch-btn-secondary --ch-btn-secondary-hover --ch-accent
--ch-accent-hover --ch-on-accent --ch-agent
--ch-agent-hover --ch-node-agent --ch-node-connection
--ch-node-tool --ch-node-knowledge --ch-node-widget
--ch-node-function --ch-chip --ch-chip-text
--ch-chip-border --ch-status-warning-bg --ch-status-warning-text
--ch-status-warning-border --ch-status-success-bg --ch-status-success-text
--ch-status-success-border --ch-status-info-bg --ch-status-info-text
--ch-status-info-border --ch-status-danger-bg --ch-status-danger-text
--ch-status-danger-border --ch-status-neutral-bg --ch-status-neutral-text
--ch-status-neutral-border --ch-phase-specify-bg --ch-phase-specify-text
--ch-phase-specify-border --ch-phase-specify-container --ch-phase-build-bg
--ch-phase-build-text --ch-phase-build-border --ch-phase-build-container
--ch-phase-verify-bg --ch-phase-verify-text --ch-phase-verify-border
--ch-phase-verify-container --ch-phase-draft-container
Isolation runs both ways and it's structural, not conventional. The interface lives in a shadow root and the compiled stylesheet is adopted into that root rather than added to your document, so neither side's CSS can reach the other.