Describe what you want. It gets drafted for you. Paste one line into your site and it appears.
Say what you want. The AI drafts it. #
Three descriptions, and the three widgets that came back. None of them was drawn by hand.
Somebody typed this
And got this
Open the editor and describe your own.
Or write it yourself #
When the blocks do not cover what you want, write the HTML, CSS and JavaScript. It still runs sealed off in its own frame, still gets the accessibility check, and still counts views and clicks.
index.html
<p id="bar">
<strong id="clock">00:00:00</strong> left in the sale
</p>
style.css
#bar { margin: 0; font: 14px/1.4 system-ui, sans-serif; color: #f5f5f7; text-align: center }
#clock { font-variant-numeric: tabular-nums; letter-spacing: 0.02em }
main.js
// Counts down to midnight in the visitor's own timezone.
var end = new Date();
end.setHours(24, 0, 0, 0);
function pad(n) { return String(n).padStart(2, "0"); }
setInterval(function () {
var s = Math.max(0, Math.round((end - Date.now()) / 1000));
document.getElementById("clock").textContent =
pad(Math.floor(s / 3600)) + ":" + pad(Math.floor(s / 60) % 60) + ":" + pad(s % 60);
}, 1000);
Three steps #
Design it
Change the words, the colours, where it sits.
Paste one line
Once, anywhere on your site. Never again.
Watch it work
Views, clicks, replies. Off again in a click.
<script async src="https://kestero.com/widget.js" data-key="your-key"></script>
Squarespace, Shopify, WordPress, or plain HTML.
Start from one of these #
Each one opens the editor with a working example in it.
Announcement bar
One sentence across the top of every page.
Email signup widget
A box that asks, and keeps the answers.
Notification widget
A card in the corner, with a button.
Changelog widget
A bell, and a panel of your updates.
Feedback widget
One question, a few answers, and the replies waiting.
Onboarding checklist
One step per screen, until they are done.
You decide when it shows up #
Pick one and try it on the window below. These are real — not a video.
{
"version": 2,
"name": "Announcement bar",
"kind": "banner",
"theme": {
"fontFamily": "system-ui, -apple-system, \"Segoe UI\", sans-serif",
"accent": {
"hex": "#ffffff",
"alpha": 1
},
"surface": {
"hex": "#1D1D1F",
"alpha": 1
},
"text": {
"hex": "#ffffff",
"alpha": 1
},
"muted": {
"hex": "#6b7280",
"alpha": 1
},
"radius": 14,
"fontSize": 13,
"gap": 8,
"scheme": "auto"
},
"surface": {
"mode": "bar",
"position": "top",
"mount": "",
"width": 360,
"offset": 0,
"backdropBlur": 0,
"backdrop": false,
"dismissible": true,
"animation": {
"preset": "fade",
"durationMs": 200
},
"css": ""
},
"trigger": {
"on": "load",
"delayMs": 0,
"scrollPercent": 50,
"idleMs": 20000,
"siteMs": 60000,
"selector": "",
"frequency": "always",
"cooldownDays": 7,
"maxShows": 3,
"pages": {
"mode": "everywhere",
"paths": []
},
"audience": {
"visitor": "anyone",
"devices": "any"
}
},
"screens": [
{
"id": "main",
"blocks": [
{
"id": "row",
"type": "group",
"direction": "row",
"align": "center",
"justify": "center",
"gap": 12,
"children": [
{
"id": "tag",
"type": "icon",
"name": "tag",
"size": 16,
"label": ""
},
{
"id": "copy",
"type": "text",
"role": "body",
"text": "Free delivery on everything this week.",
"format": "plain"
}
]
}
]
}
],
"start": "main"
}
Safe to put on your site #
You are putting somebody else’s code on your site. Straight answers, with the numbers behind them.
It will not slow your site down
It loads in the background. If we went down, your site would not notice.
18.8 KB gzipped
It cannot break your design
Sealed off both ways. Your CSS cannot reach in; nothing in it reaches out.
Shadow DOM, :host { all: initial }
No cookies. No tracking.
Nothing to consent to. The only thing we store is what someone types into your form.
0 cookies set
It works for everyone
Keyboard, screen reader, reduced motion. The product cannot promise what somebody draws with it, so what it offers instead is a check that runs on demand and a record of what it found.
The check
- axe-core 4.10, against WCAG 2.1 AA.
- Run against the exact spec, inside the real shadow root — not a copy of the widget built for testing.
- In both colour schemes, because a widget set to follow the visitor ships both and a contrast failure in one is a failure.
The record it writes
- checkedAt
- The date it ran.
- specHash
- A fingerprint of the widget it ran against. Edit the widget and the fingerprints stop matching, so the record stops applying — rather than ageing quietly into a badge that means nothing.
If you have to get this signed off — the record every widget carries, and how each claim is known. #
Every widget is saved as plain data with no code in it, which means a program can read a widget and work out what it is able to do. One does. The two cards below are real records it produced, from two real widgets, while this page was rendering — the first is the widget you scrolled past, the second is the same widget with one image added from somebody else’s server.
The widget above
What this widget does not do
- No custom code
- No custom CSS
- No third-party requests
- Not checked for accessibility since it was last edited
Worked out from the saved widget itself every time this page loads, not recorded once and trusted afterwards.
The same widget, with one image from another host
What this widget does not do
- No custom code
- No custom CSS
- Loads from another host — images.example.com
- Not checked for accessibility since it was last edited
Worked out from the saved widget itself every time this page loads, not recorded once and trusted afterwards.
Neither is written down anywhere on this page — which is why the second one is allowed to be worse than the first, and why it is. Both were computed by the same function the API runs on every save and every read, lib/schemas/widget-guarantee.ts
.
Look at the last line of each. Neither widget has been checked for accessibility, and both say so plainly rather than staying quiet about it. A badge that could only ever come out green would not be worth reading, and a reviewer would be right not to.
What the embedded file does
All of this is true by reading one file, which is the point of it being one file.
- PropertyNo third-party code in it — no import, no require, nothing bundled
- PropertyNothing that arrives over the network is ever assigned to innerHTML; the two assignments in the file both take icon markup defined in the file
- PropertyRenders in a shadow root under :host { all: initial }, so the host stylesheet cannot reach it and it cannot reach the host
- UnrepresentableA link can only be http, https or mailto — a javascript: URL is refused by the schema, so no stored widget contains one
- Enforced at saveCustom CSS is parsed, scoped into the widget's own subtree, and refused if it fixes itself over the host page or fetches from a host that is not ours
- Enforced at savePer-project domain allow-list, so a scraped embed key renders nowhere else
What is checked for accessibility
A check, not a promise. The product cannot guarantee what someone draws with it, so what it offers is the check and the date it ran.
- Checked per widgetaxe-core runs against the exact spec, in both colour schemes, inside the real shadow root — and the result is stamped with that spec's fingerprint, so it stops applying the moment the widget is edited
- UnrepresentableAn image without alt text and an input without a label are not storable specs
- PropertyA text block's role decides the heading level the renderer emits, so restyling a block cannot change what it announces itself to be
- TestedFocus moves into a modal and returns on close, Escape closes unless the spec says the widget cannot be dismissed, and a closed widget leaves the DOM rather than hiding in the tab order — 232 tests drive the real renderer in a browser environment
- Propertyprefers-reduced-motion is read by the renderer, and the animation the spec asked for does not run
What is stored about a visitor
One thing, and only because you asked for it.
- PropertyNo cookies, so nothing to consent to
- PropertyNo visitor table and no fingerprint. How often a widget comes back, and which changelog posts have been read, are timestamps in the visitor's own localStorage
- PropertyWhat a visitor types into a form you built is stored, and that is the whole of it. It is stored because your widget asked a question — nothing is collected that a visitor did not type
- SettingRecording the page and recording the referrer are each switchable off, independently
- DeploymentData at rest in the EU — the database is in AWS eu-west-1
Measured, not estimated
- widget.js — 18.8 KB gzipped
- Zero dependencies
- No cookies, no visitor row
- axe-checked per widget
- Data stored in the EU
Each of those has a command beside it in the repository that produces it, and a test that fails when it stops being true. The first column above is checkable without asking us anything — read the file your visitors would run.
One price, or none #
Keeping one is free. Pay only for the second.
Free
Free
One widget, on one site, for as long as you like.
- One saved widget
- A small Kestero link on your widget
- Every block, colour and trigger — build anything Pro can build
- Describe what you want and have it drafted — 10 a day
- The accessibility check, and the record it writes
Pro
€19/month
Coming soon
For running more than one thing at a time.
- Views, clicks and form replies, for every widget
- Which pages and which sources they came from
- As many widgets as you want
- No Kestero link on your widgets
- 100 drafts a day, not 10
- Across as many sites as you want
- Email support, answered by the person who built it
Free does not expire and asks for no card. Pro is not open yet, so there is nothing to buy and nothing to cancel. When it opens it will be billed monthly through Stripe, and cancelling will delete nothing and switch nothing off.
Need an invoice, a DPA or a signed security questionnaire? Talk to us.
Go and make one #
The editor opens now — no account, no email, nothing to fill in. Decide afterwards whether you want to keep what you made.