Diane, note for the file. What the box takes, and what it does with it.
Docs.
Embedding the widget takes two lines of HTML. Everything after the first section is optional: settings for the cases where the defaults are not what your site needs.
- Setting it up
- Which site, which page
- Attributes
- Sites with client-side routing
- Appearance
- Language
- Limits
- Moderation
- What is stored
- Content Security Policy
- When nothing shows up
- What Diane does not do
Setting it up
- Sign in at admin.diane.to with Google.
- Add your domain. One row per hostname:
www.example.comandexample.comare the same site, and subdomains such asblog.example.comare registered separately. - Paste these two lines into your page:
<script async defer src="https://diane.to/w.js"></script> <to-diane></to-diane>
The script can sit in the head or at the end of the body. Place the custom element where the widget should render.
There is no key in that snippet, no site id and nothing per-page. Your site is identified by the Origin header and the page by its own URL, so there is nothing to paste into the markup and nothing to keep in sync.
Which site, which page
The site is identified by the Origin header, which the
browser sets on every cross-origin request and page JavaScript cannot
forge. It is matched exactly against the domain you registered, except
that one leading www. is stripped. All other subdomains
require separate registration.
The page is identified by checking three places, in order:
- the
post-slugattribute on the element, - the pathname of the
<link rel="canonical">tag, - the current
location.pathname.
The resulting identifier is cleaned up the same way in the widget and on
the server: query strings, fragments, doubled slashes and trailing
slashes are removed. So /post, /post/ and /post?utm_source=rss resolve to the same thread. Keep an
identifier under 200 characters — the widget ignores a longer post-slug and falls back to the canonical link, then to location.pathname.
Two elements on one page with different post-slug attributes
keep two separate threads. A canonical link pointing elsewhere gives
syndicated copies of an article one shared thread.
Attributes
| Attribute | Description |
|---|---|
post-slug | The thread identifier, used when the thread is not derived from the address. Up to 200 characters. |
lang | The language of the widget's labels, overriding <html lang>. |
<to-diane post-slug="/blog/twin-peaks"></to-diane>
Both attributes are optional. There are no others.
Sites with client-side routing
The widget detects route changes on its own: it listens for popstate, uses the Navigation API, and otherwise polls location.href once a second. A standard single-page
application needs no further configuration.
If the thread is not identified by the address, update the attribute when the route changes:
document.querySelector("to-diane").setAttribute("post-slug", nextSlug) A server response that arrives after the reader has navigated away is discarded, and an unsent comment is cleared on navigation.
Appearance
The widget derives its palette from the text colour of your page. It
ignores prefers-color-scheme so that it matches the page it
is embedded in rather than the visitor's operating system, which is why
most sites need no theming at all.
To customize it, apply CSS custom properties to the element:
to-diane {
--td-font: Georgia, serif;
--td-radius: 4px;
--td-gap: 24px;
/* A solid brand button instead of the default tinted one */
--td-button-bg: #7c2d12;
--td-button-fg: #fff;
--td-button-border: transparent;
} | Property | Default | Description |
|---|---|---|
--td-font | system sans stack | The typeface for all text. |
--td-gap | 16px | The spacing between elements. |
--td-radius | 8px | The border radius for fields and the button. |
--td-bubble-radius | 14px | The border radius for comment bubbles. |
--td-surface | 11% of your text colour | The comment background colour. |
--td-border | 24% of your text colour | The border colour for fields and the button. |
--td-muted | 62% of your text colour | The colour for names, dates and secondary text. |
--td-button-bg | 13% of your text colour | The button background colour. |
--td-button-bg-hover | 20% of your text colour | The button background colour on hover. |
--td-button-fg | your text colour | The button label colour. |
--td-button-border | same as --td-border | The button border colour; transparent hides it. |
--td-danger | #e5484d | The error text colour. |
Three parts are exposed for styling: ::part(root) targets
the whole container, ::part(heading) the comment-count
heading and ::part(powered) the “Powered by Diane.to” label
below the form. You can restyle that label, or hide it:
to-diane::part(powered) {
display: none;
} No other internal element can be targeted from the page. The element uses display: block; apply a max-width to control
its size.
Language
The widget takes its language from the <html lang> attribute of your page. An attribute on the element overrides it:
<to-diane lang="cs"></to-diane>
English and Czech are supported. An unsupported language falls back to English labels, while dates follow the page's own locale. To request a language, write to hello@diane.to.
Limits
A free account holds one hundred live comments per site. Above that the server rejects new submissions and the widget reports that the box is full; comments already written stay visible. Deleted comments do not count towards the limit, and a deletion can be undone.
A name is limited to 80 characters and a comment to 4000.
Comments are checked for spam, without a CAPTCHA. A rejected comment gets a message in the visitor's own language telling them what to do next. Write to hello@diane.to if legitimate comments are being blocked.
Moderation
Moderate in the console at admin.diane.to. It provides one inbox with every comment, newest first, and a per-site view grouped by thread. You can delete and restore comments, show deleted ones, rename a site, and silence a site entirely — silencing hides the widget on that domain without showing visitors an error.
A new site holds comments until you approve them. They arrive in the console marked waiting, and Approve puts one on the page. Refusing a comment is deleting it — the same button, and it can be undone, which puts the comment back in the queue. Until you approve it, the only person who sees it on the page is whoever wrote it, greyed out and marked as waiting. The site's settings switch this to Publish immediately, and comments then appear as soon as they are posted.
Switching the mode does not reach backwards: comments already waiting go on waiting until you approve them, and published ones stay published. Comments that are waiting do not count towards the limit of a hundred published ones — the queue has a ceiling of its own, also a hundred. Once it is full the site refuses new comments until you approve or delete something.
You can answer from the console. Your reply appears as the newest comment in the thread, marked author so a reader can tell the owner is speaking. It is signed with the Reply as name on the site's screen, which is filled in with your own name when you register the site and can be changed at any time. Changing it also changes the replies you have already posted — it is still one person, not two. A reply counts towards the limit like any other comment, and the same button deletes it.
What is stored
Diane stores the name the visitor typed, the comment text, the timestamp, and an unsalted SHA-256 hash of their IP address. The hash is only ever compared for equality, as part of the spam checks, and no endpoint returns it. On your site the widget sets no cookies and loads no third-party resources.
While a comment of theirs is waiting to be approved, the widget keeps its
id in localStorage on your own domain, under the key to-diane:pending — the id and the page it was written on,
never the text. It is what lets someone reload the page and still see
what they wrote. It is readable only by your site, it never reaches us,
and it is dropped as soon as the comment is published or refused.
Comments are stored as submitted and rendered as plain text: no HTML, no Markdown, no auto-linking. Further details are on the privacy page.
Content Security Policy
If your site sends a Content Security Policy, three directives concern the widget:
script-src https://diane.to connect-src https://diane.to style-src 'unsafe-inline'
script-src permits the script, connect-src the
API requests, and style-src the widget's own stylesheet,
which is a <style> element inside its shadow root that
a nonce cannot reach. The widget loads no fonts, images or third-party
resources.
When nothing shows up
The widget never shows a configuration error to your visitors. It renders
nothing and writes to the browser console with a [to-diane] prefix, so open the console first.
- “… is not registered” — the Origin header matches no
registered domain. Check subdomains:
blog.is a different site from the bare domain,www.is not. - Nothing in the console and nothing on the page — the script never loaded. Check for a CSP violation or an ad blocker.
- Works in production, not on your machine — Diane
serves https and registered domains only.
localhostand127.0.0.1do not work; test on the domain you registered. - Your own test comment is refused — read the message in the widget and follow it.
- The wrong thread in a single-page app — the address
changes in a way the widget cannot see. Set
post-slugwhen the route changes.
What Diane does not do
No reply threads, no votes, no editing after posting, no Markdown, no avatars, no reader accounts, and nothing that notifies a visitor — nobody is told their comment was answered. You are told: the console can push a new comment to your own devices. If one of these decides whether you can use Diane, write to hello@diane.to.