Example
Live, interactive demos built with @landbot/core — a property-search assistant, a cinematic landing page, a split-flap board, and a self-writing site — plus the canonical minimal React chat.
-
See it in action — property-search assistant — A full product-style app: an eight-step real-estate assistant (Location → Property type → Bedrooms → Budget → Timeline → Results → Contact → Goodbye) where the chat drives a rich custom UI instead of a bubble stream. Built with
@landbot/core+ React + Tailwind + shadcn/ui; source inrealtor/at the repo root. The pattern: the bot tags each step with acustomer.uivalue and the app dispatches to a bespoke React component per step — a slider for budget, a card grid for results, a form for contact — so every prompt gets purpose-built controls while Core owns the conversation state. The closest of these demos to a real integration. -
See it in action — cinematic conversation — A scene-per-message landing page built on
@landbot/core. Each bot reply takes over the full viewport in oversized DM Serif Display; five visual themes rotate per scene (Midnight, Rose, Cream, Ocean, Electric). Source lives incinematic/at the repo root — three files (index.html,style.css,cinematic.js) totalling ~670 lines. Demonstrates@landbot/corepatterns you don't see in a typical chat widget: subscribing to$readableSequencefor paced delivery, filtering user messages out of the render stream via thesamuraifield, a length-scaled dwell loop with a "Continue ▼" affordance so readers can pace themselves through bot bursts, and reserved layout space so the message doesn't jump when the input fades in. -
See it in action — split-flap departure board — A Solari-style departure board where each bot reply rolls in character-by-character across a grid of amber flap tiles, dialog choices become numbered "tracks" (TRACK 01 / TRACK 02), and prior messages scroll up the board as faded history rows. Source lives in
departures/at the repo root — three files (index.html,style.css,departures.js). This one is built on$typingSequencespecifically, so the SDK's typing state actually drives the UI —state: trueticks flip the status bar toBOARD ACTIVE · INCOMINGwith a faster pulse, and the matchingstate: falsetick triggers the flap roll. Also shows howtype: 'dialog'choices and thepayloadsarray map naturally onto a non-chat affordance (numbered platforms), and how to throw away the chat-bubble metaphor entirely in favor of a single in-place "now showing" display plus a small scrolling log. -
See it in action — the site that writes itself — A dual-pane demo: real
@landbot/corechat on the left, a marketing-page mock on the right. Each user reply mutates the page in real time — product name fills the logo and hero, the one-liner becomes the subhead, a brand-colour answer re-themes every CTA and accent, three feature replies populate the feature cards, the audience reply lights up the closing block and fires a celebratory pulse. Source:composer/index.html,style.css,composer.js— plus aBOT_SETUP.mdguide in the same directory. The pattern:core.pipelines.$readableSequence.subscribe(handleMessage)renders the bot's prompts and counts user replies; each user reply (in order) advances a local state machine whoseapply()functions mutate the page (setProductName,setHeadline,setBrandColour,setFeature,setAudience). No bot-side Custom JS, no Code blocks — just bot questions in sequence and a parent that owns the state. Chat is the controller, the page is the canvas. A note on the default config: the demo loads against the same sample bot the other two examples use, which means it works out of the box — page mutations fire correctly because they're driven by user-reply count, not by what the bot says — but the prompts won't match the wizard's intent. For the coherent experience, followcomposer/BOT_SETUP.md(in the repo) to build a wizard bot in the Landbot dashboard, then pass its config URL via?bot=...or swap the fallback incomposer.js. About 20 minutes of bot-builder work; no code changes.
Minimal chat example
The cinematic demo above is a creative remix. The example here is the canonical minimal chat — a React component with no styling commitments that captures just the standard @landbot/core wiring. Start here when you're learning the SDK; copy ideas from the cinematic version once you want to deviate.
The five lines you'll always write:
- Fetch the bot config from
index.json. - Create a
Coreinstance from the parsed config. - Subscribe to
$readableSequencefor incoming messages. - Call
init()to load the welcome / history. - Use
sendMessageto push user input back to the bot.
You don't have to render it as a chat — Core is just a messaging engine. Once you have the message stream and a way to send, the UI is yours.
Tip Want the same wiring without React or a build step? The cookbook recipe Build a custom chat UI with
@landbot/coreis these five steps in a single copy-paste HTML file, with notes on the patterns to get right (subscribe beforeinit(), discriminate onauthor_type, dedupe onkey).
Source code
import React, { useState, useEffect, useRef } from 'react';
import Core from '@landbot/core';
function parseMessage(data) {
return {
key: data.key,
text: data.title || data.message,
author: data.samurai !== undefined ? 'bot' : 'user',
timestamp: data.timestamp,
type: data.type,
};
}
function parseMessages(messages) {
return Object.values(messages).reduce((obj, next) => {
obj[next.key] = parseMessage(next);
return obj;
}, {});
}
function messagesFilter(data) {
// Render only basic message types in this minimal example
return ['text', 'dialog'].includes(data.type);
}
function scrollBottom(container) {
if (container) {
container.scrollTo({
top: container.scrollHeight,
behavior: 'smooth',
});
}
}
export default function Chat() {
const [messages, setMessages] = useState({});
const [input, setInput] = useState('');
const [config, setConfig] = useState(null);
const core = useRef(null);
useEffect(() => {
fetch('https://chats.landbot.io/u/H-441480-B0Q96FP58V53BJ2J/index.json')
.then(res => res.json())
.then(setConfig);
}, []);
useEffect(() => {
if (config) {
core.current = new Core(config);
core.current.pipelines.$readableSequence.subscribe(data => {
setMessages(messages => ({
...messages,
[data.key]: parseMessage(data),
}));
});
core.current.init().then(data => {
setMessages(parseMessages(data.messages));
});
}
}, [config]);
useEffect(() => {
const container = document.getElementById('landbot-messages-container');
scrollBottom(container);
}, [messages]);
const submit = () => {
if (input !== '' && core.current) {
core.current.sendMessage({ message: input });
setInput('');
}
};
return (
<>
<div className="landbot-header">
<h1 className="subtitle">Landbot core example</h1>
</div>
<div
className="landbot-messages-container"
id="landbot-messages-container"
>
{Object.values(messages)
.filter(messagesFilter)
.sort((a, b) => a.timestamp - b.timestamp)
.map(message => (
<article
className="media landbot-message"
data-author={message.author}
key={message.key}
>
<figure className="media-left landbot-message-avatar">
<p className="image is-64x64">
<img
alt=""
className="is-rounded"
src="https://i.pravatar.cc/100"
/>
</p>
</figure>
<div className="media-content landbot-message-content">
<div className="content">
<p>{message.text}</p>
</div>
</div>
</article>
))}
</div>
<div className="landbot-input-container">
<div className="field">
<div className="control">
<input
className="landbot-input"
onChange={e => setInput(e.target.value)}
onKeyUp={e => {
if (e.key === 'Enter') {
e.preventDefault();
submit();
}
}}
placeholder="Type here..."
type="text"
value={input}
/>
<button
className="button landbot-input-send"
disabled={input === ''}
onClick={submit}
type="button"
>
<span className="icon is-large" style={{ fontSize: 25 }}>
➤
</span>
</button>
</div>
</div>
</div>
</>
);
}
What to swap in
- Replace the
H-441480-...bot ID in thefetchURL with your bot's config URL. - Pick the pipeline that matches the cadence you want —
$sequencefor instant ordered delivery,$readableSequence(used here) for natural pacing, or$typingSequenceif you want to render a typing indicator. - Extend
messagesFilterto render more message types —image,iframe, custom rendering forhidden, etc.
Next steps
- Landbot.Core API — full method and property reference.
- Messages — every message shape Core accepts and emits.
- Pipelines — the three pacing strategies.
- Events — emitting and subscribing to custom events.