The EU AI Act’s Article 50 notice, in one line. Tells your visitors they’re interacting with an AI system, and labels AI-generated content — in all 24 official EU languages, automatically.
No dependencies. No network requests. No cookies. ~7 kB gzipped.
<script src="https://cdn.jsdelivr.net/gh/kalbydot/kalby.disclose@1.0.0/src/kalby-disclose.js"
data-mode="both" data-lang="auto" data-theme="auto"></script>
That’s the whole install. Mark generated content with one attribute:
<img src="hero.png" data-ai-generated>
<video src="clip.mp4" data-ai-generated="deepfake" data-ai-overlay></video>
Since 2 August 2026, Article 50 of the EU AI Act requires you to tell people when they interact with an AI system, and to label AI-generated or manipulated content. The duty is small per organisation and universal across every EU-facing business with a chatbot — which is exactly the shape of problem that should be solved once and given away, not billed for by the hour.
So this is free, MIT-licensed, and deliberately boring to install.
| Source | URL | Best for |
|---|---|---|
| Self-hosted | your own server | Recommended. No dependency on anyone, including us |
| jsDelivr (pinned) | cdn.jsdelivr.net/gh/kalbydot/kalby.disclose@1.0.0/src/kalby-disclose.js |
Immutable, survives us |
| npm | npm i @kalbydot/kalby.disclose |
Bundlers, build pipelines |
It’s a single file with no dependencies and no network calls — there is no technical reason it needs a CDN. We’d rather you self-hosted it. A compliance control shouldn’t depend on a third party’s domain still being renewed in five years.
All optional, all set as data- attributes on the script tag.
| Attribute | Values | Default | Does |
|---|---|---|---|
data-mode |
both · chat · content |
both |
Notice, labels, or both |
data-lang |
auto or a language code |
auto |
auto reads <html lang>, then the browser |
data-theme |
auto · light · dark |
auto |
Follows the visitor’s system preference |
data-position |
bottom · top |
bottom |
Where the floating notice sits |
data-chat-target |
CSS selector | — | Render inline inside your chat UI |
data-accent |
any CSS colour | #5b5bd6 |
Match your brand |
data-learn-more-url |
URL | — | Adds a “Learn more” link |
data-remember |
session · persistent · always |
session |
How long a dismissal sticks |
data-label-style |
hover · plain · brand |
hover |
See “The label” below |
data-no-auto |
present | — | Skip auto-init; drive it from JS |
KalbyDisclose.init(opts) // manual init / re-init
KalbyDisclose.update(opts) // change options live, e.g. {lang:"fr"}
KalbyDisclose.setLang("de")
KalbyDisclose.label(el, type) // label one element
KalbyDisclose.labelAll(root) // scan a subtree
KalbyDisclose.dismiss()
KalbyDisclose.resetDismissal()
KalbyDisclose.getAuditLog() // timestamped record of notices shown
KalbyDisclose.destroy()
KalbyDisclose.languages // the 24 supported codes
Content added after load — chat replies, infinite scroll, AJAX — is labelled
automatically via MutationObserver. No extra code.
try/catch, so private
windows and blocked-storage browsers degrade rather than break.role="status" with aria-live="polite", real focus states,
prefers-reduced-motion respected, disclosure text exposed to screen readers
on content chips.At rest the content chip shows the translated term with the sparkles the field has settled on. Move a pointer over it and it crossfades to “AI sniffed” with Kalby’s dog.
| State | Mark | Text |
|---|---|---|
| Resting | sparkles, in the accent colour | the translated term — AI-gegenereerd, KI-generiert, … |
| Hovered | Kalby’s dog, in ink | AI sniffed |
The dog is drawn in ink rather than the accent colour, because it is the brand mark: black on a light ground, inverted on a dark one, and white over an image scrim. The floating notice carries the same bare inked dog — it is the only mark the widget uses, apart from the sparkles on the label’s resting face.
Both faces occupy the same grid cell, so the chip is as wide as the wider of the two and never changes width — the text around it does not reflow on hover.
That chip is the legally operative label under Article 50(2) and 50(4). “AI sniffed” is wordplay: alone it does not tell a reader the content was produced by AI, and it says nothing at all to someone who does not read English. So the disclosing wording is what sits on the page, and the dog is a reward for looking closer — never a substitute for it.
The reveal is pure CSS :hover. There is no timer and nothing updates on its
own, so the WCAG rules on moving and auto-updating content simply don’t apply.
Touch devices have no hover and show the translated term, which is the face
that matters. The accessible name and tooltip always carry the full translated
disclosure.
<script src="..." data-label-style="plain"></script> <!-- no hover face at all -->
<script src="..." data-label-style="brand"></script> <!-- the dog alone -->
opts.icon meanwhile.npm install
npm test # Playwright-driven behaviour tests
npm run build # minify + print the SRI hash
Translation review first, bug reports second, features last. See CONTRIBUTING.md.
MIT. Use it commercially, fork it, ship it inside your product. If Kalby ever disappears, this file keeps working and anyone can maintain it — which is the point.
One thing MIT does not cover: the Kalby name, the dog mark and the “AI sniffed” wording are brand, not code. Forks are welcome — see TRADEMARKS.md for the two-minute rebrand (both identifiers are defined once at the top of the source).