Skip to content

Put a message on your website.

A banner, a notice, a sign-up box, a what’s-new panel. Design it here, paste one line into your site, and it appears.

No account needed to start. Free to keep one.

You decide when it shows up

Pick one and try it on the window below. These are real — not a video.

Watch the window.

yourshop.example
What that one is, underneath — the whole widget, written out. No code in it anywhere.
{
  "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,
    "selector": "",
    "frequency": "always"
  },
  "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"
}

A real page running the same file your site would. Nothing it does leaves your browser.

Three steps

  1. Design it

    Change the words, the colours, where it sits.

  2. Paste one line

    Once, anywhere on your site. Never again.

  3. 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.

Or start with a blank one. 8 in total.

Safe to put on your site

You are putting somebody else’s code on your site. Four straight answers.

It will not slow your site down

It loads in the background. If we went down, your site would not notice.

It cannot break your design

Sealed off both ways. Your CSS cannot reach in; nothing in it reaches out.

No cookies. No tracking.

Nothing to consent to. The only thing we store is what someone types into your form.

It works for everyone

Keyboard, screen reader, reduced motion. One click checks yours and records the answer.

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 hostimages.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 — 89 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 — 16.1 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
  • Every block, colour and trigger — nothing is held back
  • Describe what you want and have it drafted for you
  • Views, clicks and form replies, per widget
  • The accessibility check, and the record it writes
Make one, free

Pro

$19/month

For running more than one thing at a time.

  • Everything in Free
  • As many widgets as you want
  • Across as many sites as you want
  • Email support, answered by the person who built it
Make one, then upgrade

Free does not expire and asks for no card. Pro is billed monthly through Stripe and cancels in one click, on Stripe’s own page. Cancelling deletes nothing and switches 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.

Open the editor