Components
Drop-in Web Components for chat, group chat, quizzes, microphone input and usage status
Web Components with shadow DOM, for plain HTML or any framework.
Registering the elements
| Import | Effect |
|---|---|
import "@neocortex-link/web/ui/auto" | Registers every <neocortex-*> element on import. |
import { defineNeocortexElements } from "@neocortex-link/web/ui" | Exports the element classes without registering them. Call defineNeocortexElements() when you are ready. |
In plain HTML, load the auto entry from a CDN:
<script type="module" src="https://cdn.jsdelivr.net/npm/@neocortex-link/web@0.2/dist/ui/auto.js"></script>defineNeocortexElements() is idempotent and a no-op outside the browser, so it is SSR-safe.
Elements
| Element | What it is |
|---|---|
<neocortex-chat> | A complete one-to-one chat: history, typing, mic input, spoken replies. |
<neocortex-group-chat> | Several characters in one conversation, each in their own colour. |
<neocortex-quiz> | A quiz host with questions, options, hints, progress and a leaderboard (Team plan). |
<neocortex-chat-panel> | The scrolling message list on its own. |
<neocortex-message> | One chat bubble. |
<neocortex-text-input> | A text box with a send button. Fires nx-send. |
<neocortex-mic-button> | A microphone button with a level ring and silence countdown. Fires nx-recorded. |
<neocortex-mic-select> | A microphone picker. |
<neocortex-thinking> | Typing dots. |
<neocortex-usage-badge> | Shows whether characters are available or out of credits. |
voice is on when present and off when absent or set to "false". history and mic are on unless set to "false". The other boolean attributes (push-to-talk, no-header, disabled, active) are on whenever present, whatever their value.
neocortex-chat
<neocortex-chat endpoint="/api/neocortex" character-id="your-character-id" voice></neocortex-chat>| Attribute | Description |
|---|---|
character-id | The character to talk to. Required. Changing it starts a new agent. |
endpoint | Your proxy route. When left out, the element uses the client set with setDefaultClient, or /api/neocortex. |
voice | Speak replies aloud. The header has a toggle for it. |
push-to-talk | The mic records only while held, instead of stopping after a pause. push-to-talk="false" turns it off. |
heading | Header title. When empty, the character's name is shown once it speaks. |
placeholder | Placeholder text of the input. |
language | Language for speech and transcription, for example en or tr. |
history="false" | Don't load the previous conversation on start. |
mic="false" | Hide the microphone button. |
no-header | Hide the header. |
theme | light or dark. Follows prefers-color-scheme when left out. |
Properties and methods
agentis theSmartAgentbehind the element. Register actions or listen to its events on it.clientgets or sets theNeocortexClientthe element uses. Setting it overridesendpointand starts a new agent.reset()stops the current reply, forgets the conversation and clears the panel. The header's reset button does the same.
const chat = document.querySelector("neocortex-chat");
chat.agent.registerAction("OPEN_DOOR", (action) => door.open(action.targetId));Changing character-id, endpoint or client creates a new agent; register handlers again. The conversation persists in localStorage.
neocortex-group-chat
<neocortex-group-chat endpoint="/api/neocortex" character-ids="aria,bram" voice></neocortex-group-chat>Takes the same attributes as <neocortex-chat>, with these instead of character-id:
| Attribute | Description |
|---|---|
character-ids | Comma-separated character ids. Changing it starts a new agent. |
max-turns | How many characters may speak in one turn. |
Each character's name and bubble accent get their own colour. The header has a Let them talk button that lets the characters carry on without the player. agent is a GroupDirector. See Group Chat for a walkthrough.
neocortex-quiz
<neocortex-quiz endpoint="/api/neocortex" character-id="host-id" question-set-id="set-id" voice></neocortex-quiz>| Attribute | Description |
|---|---|
character-id | The quiz host. Required. |
question-set-id | The question set to play. Required. |
endpoint | Your proxy route. |
voice | Speak the host's lines aloud. |
heading | Header title. Defaults to Quiz. |
question-count | How many questions to ask. |
theme | light or dark. |
The player answers by clicking an option, typing or speaking, and can ask for a hint or pass. The element reflects its phase in a state attribute: idle, playing or finished. agent is a QuizAgent.
See Quiz for a walkthrough.
neocortex-chat-panel
A scrolling message list with a thinking indicator at the end. Use it to build your own chat layout.
| Attribute | Description |
|---|---|
empty-text | Text shown while there are no messages. |
| Member | Description |
|---|---|
addMessage(message, { prepend }) | Adds a bubble and returns it. message is { sender, text, name?, emotion?, hue? }, where sender is player, character or notice, and hue is a colour angle for a speaker in a group. |
thinking | Set to true or false to show or hide the thinking dots. |
clear() | Removes every message. |
scrollToEnd() | Scrolls to the newest message. |
neocortex-message
One bubble. Text is always set as text, never parsed as HTML.
| Attribute | Description |
|---|---|
sender | player, character or notice. |
text | The message. Falls back to the element's text content. |
name | Speaker name, shown above character bubbles. |
emotion | Shown as the bubble's tooltip when it is not neutral. |
hue | A colour angle (0 to 360) for the name and an accent line. |
neocortex-text-input
| Attribute | Description |
|---|---|
placeholder | Defaults to Say something…. |
maxlength | Defaults to 500, the proxy's default message limit. |
disabled | Disables the box and the send button. |
Fires nx-send with { text } when the player submits. Call preventDefault() on it to keep the text in the box. It also has a value property and a focus() method.
neocortex-mic-button
Click to listen, and recording stops after a pause. With push-to-talk, hold it with the pointer, Space or Enter.
| Attribute | Description |
|---|---|
push-to-talk | Record only while held. |
disabled | Disables the button. |
threshold | Loudness from 0 to 1 that counts as speech. Defaults to 0.1. |
max-wait | Seconds of silence that end a recording. Defaults to 1.2. |
The element reflects its state in a state attribute: idle, listening or recording. It has a receiver property (the AudioReceiver behind it) and toggle() and stop() methods. The button is disabled in browsers that cannot record audio.
neocortex-mic-select
Lists microphones and switches the one a mic button records from. Device names appear once microphone permission has been granted, and the choice is remembered for the next visit.
<neocortex-mic-button id="mic"></neocortex-mic-button>
<neocortex-mic-select for="mic"></neocortex-mic-select>Point it at a mic button with for="<id>", or set its receiver property to an AudioReceiver. refresh() re-reads the device list.
neocortex-thinking
Three bouncing dots, hidden unless active is set.
| Attribute | Description |
|---|---|
active | Show the dots. Also available as the active property. |
label | Screen reader text. Defaults to Thinking…. |
neocortex-usage-badge
A status pill that shows whether characters are available, running low, or resting because credits or a per-player or per-character cap ran out.
| Attribute | Description |
|---|---|
endpoint | Your proxy route. |
character-id | Include this character's cap in the check. |
refresh | Seconds between checks. Defaults to 60. |
The element reflects the result in a status attribute: ok, low, empty, or unknown when the check failed. See UsageGate to gate your own UI.
Events
Events are CustomEvents that bubble out of shadow DOM, so you can listen on the element or any ancestor. The payload is in event.detail.
| Event | Fired by | detail |
|---|---|---|
nx-line | chat, group chat | { characterId, name, line, index }, when a line of a reply appears. line is { text, emotion, spokenText?, speechToken? }. |
nx-line | quiz | { line }, when a host line appears. Quiz lines also carry a role. |
nx-action | chat, group chat | { characterId, action, message }, where action is { name, targetId }. |
nx-emotion | chat, group chat | { characterId, emotion }, when a character's emotion changes. |
nx-response | chat, group chat | The whole reply { sessionId, messages }, as soon as it arrives. |
nx-reply-finished | chat, group chat | The same reply, once every line has been shown or spoken. |
nx-turn | quiz | Every quiz turn as the server sent it. |
nx-finished | quiz | The closing turn. |
nx-error | chat, group chat, quiz | { error }, a NeocortexError. |
nx-error | mic button | { error, reason? }. reason is "permission" when microphone access was blocked. |
nx-send | text input | { text } |
nx-recorded | mic button | { audio }, a 16 kHz mono WAV Blob. |
nx-usage | usage badge | The usage response { status, creditsRemaining, player?, character? }. |
Emotions are camelCase, for example happy or curious. By default nx-action fires as soon as the reply arrives, before it is played. Change that with the agent's actionTrigger.
The chat and quiz elements show a short notice in the conversation when something goes wrong. Call preventDefault() on nx-error to show your own instead. This covers microphone errors too.
chat.addEventListener("nx-error", (event) => {
event.preventDefault();
toast(event.detail.error.message);
});The built-in text input and mic button sit inside the chat and quiz elements, so their nx-send, nx-recorded and nx-error events bubble out of those elements too.
Theming
Every colour is a CSS custom property. Set them on the element or any ancestor:
neocortex-chat {
--nx-accent: #7c3aed;
--nx-character-bubble: #1e1b4b;
--nx-radius: 20px;
--nx-font: "Inter", sans-serif;
height: 600px;
}| Token | Light default | Dark default |
|---|---|---|
--nx-accent | #f59e0b | #f59e0b |
--nx-accent-text | #1f1f1f | #1f1f1f |
--nx-bg | #ffffff | #121212 |
--nx-surface | #f5f5f4 | #1c1c1c |
--nx-text | #1c1917 | #f5f5f4 |
--nx-muted | #78716c | #a8a29e |
--nx-border | #e7e5e4 | #2e2e2e |
--nx-player-bubble | --nx-accent | --nx-accent |
--nx-player-text | --nx-accent-text | --nx-accent-text |
--nx-character-bubble | #1f1f1f | #2a2a2a |
--nx-character-text | #fafaf9 | #fafaf9 |
--nx-danger | #dc2626 | #dc2626 |
--nx-radius | 14px | 14px |
--nx-font | system-ui, -apple-system, "Segoe UI", Roboto, sans-serif | same |
<neocortex-chat> is 32rem tall and <neocortex-quiz> is 36rem by default. Set height on the element to change it.
Light, dark and right-to-left
Elements follow the visitor's prefers-color-scheme. Set theme="light" or theme="dark" on an element to choose one; the chat, group chat, quiz and panel elements pass it on to everything inside them. To set exact colours, use the --nx-* tokens, which also reach every nested element.
The layout uses logical CSS properties, so the elements respect dir="rtl" on the element or the page. They also honour prefers-reduced-motion.
Parts
Internals are exposed with ::part():
neocortex-chat::part(header) { display: none; }
neocortex-chat::part(footer) { padding: 1rem; }
neocortex-chat::part(bubble) { font-size: 1.05rem; }
neocortex-chat::part(player) { --nx-player-bubble: #2563eb; }| Element | Parts |
|---|---|
<neocortex-chat> | header, heading, voice-toggle, reset, panel, footer, input, mic |
<neocortex-group-chat> | The chat's parts, plus continue |
<neocortex-quiz> | header, heading, count, progress, panel, leaderboard, question, options, confirm, start, footer, input, mic, hint, pass |
<neocortex-chat-panel> | list, empty, thinking, and each message as message plus its sender (player, character or notice) |
<neocortex-message> | name, bubble |
<neocortex-text-input> | form, input, send |
<neocortex-mic-button> | button |
<neocortex-mic-select> | select |
<neocortex-thinking> | dots |
<neocortex-usage-badge> | pill, dot, label |
The chat, group chat and quiz elements forward the parts of the elements inside them, so you can style those from the page too:
| Forwarded part | Comes from |
|---|---|
list, empty, thinking, dots | The message panel |
message, player, character, notice | Each message, by sender |
name, bubble | Inside each message |
form, send, and input-field (the text box itself) | The text input |
mic-button | The microphone button |
Auth headers
Requests carry your site's cookies by default (same origin only). If your proxy expects a token instead, give every element one client that sends it. It applies to every element without an endpoint attribute, including ones already on the page:
import { NeocortexClient, setDefaultClient } from "@neocortex-link/web/ui";
setDefaultClient(
new NeocortexClient({
baseUrl: "/api/neocortex",
headers: () => ({ authorization: `Bearer ${getToken()}` }),
}),
);headers can be an object or a function that is called for every request. To give a single element its own client, set its client property instead.
Actions
document.querySelector("neocortex-chat").agent.registerAction("OPEN_DOOR", (action) => doors.get(action.targetId)?.open());See Interactables.