Draft · Features
What Taggor does today
Every line below is built, and has a page of the docs or a tested path behind it. Nothing here is a plan.
01
Capabilities
| Capability | What it does | More |
|---|---|---|
| SDK | Your agent writes a component in TypeScript with a typed contract: props, events, slots and the data it may collect. | Docs |
| Check on your machine | The publish gates (types, size, security) run locally, with no network, before anything is sent. | Docs |
| Styles | CSS, SCSS and Tailwind (configured in CSS only), scoped to the component. | Docs |
| Accessibility | Keyboard and screen-reader behavior inside the Shadow DOM, including form fields, is tested on the templates. | Docs |
| Build in a sandbox | Source is built in an isolated container, apart from your machine and from Taggor's own servers. | Docs |
| Immutable versions | Each publish is a new version addressed by its content. A version never changes; a change is a new version. | Docs |
| Channels | A channel (staging, production) points at one version. Promote moves it; the sites that use the channel follow. | Docs |
| Rollback | Point a channel back at the previous version from the CLI or the portal. Nothing is rebuilt. | Docs |
| Private preview | Each build can be seen at a private, temporary link before it reaches a channel. | Docs |
| One snippet per site | One async script tag per site loads a small loader that fetches only the components that site uses. | Docs |
| Allowed domains | A site lists the domains where its components mount. It stops a copied snippet from working elsewhere; it is not access control. | Docs |
| Values per site | The same component can carry different default values on each site, set without a new version. | Docs |
| Collected data | A component declares collections in its manifest; submissions are validated against the schema, checked for spam and stored for you. | Docs |
| Consent | When a component declares personal data, it shows Taggor's consent box and each submission carries a proof of what the visitor accepted. | Docs |
| Export | Submissions are listed in the portal and exported as CSV or NDJSON from the portal and the CLI. | Docs |
| Portal | Sites, components, versions, previews, props and promote or rollback, without the command line. | |
| Made for agents | llms.txt, an agent setup page, and a skill for coding agents. | |
| Hosts and browsers | Components run in a matrix of plain HTML, a strict Content-Security-Policy, Next.js, a React single-page app and WordPress, in Chromium, Firefox and WebKit. |
02
Try it
Three real components, one at a time. Move the controls: the component and the code for your framework change together.
Props
Generated from the component's manifest and the values above.
index.html
<contact-form
headline="Ask about your site"
privacy-policy="https://example.com/privacy"
></contact-form>
<script>
const element = document.querySelector("contact-form");
element.addEventListener("contact-form:submitted", (event) => {
console.log(event.detail);
});
</script>
- Add the site's Taggor script once; the component appears when it loads.
taggor-component.js
const element = document.createElement("contact-form");
element.setAttribute("headline", "Ask about your site");
element.setAttribute("privacy-policy", "https://example.com/privacy");
element.addEventListener("contact-form:submitted", (event) => {
console.log(event.detail);
});
document.body.append(element);
- Add the site's Taggor script once; the component appears when it loads.
ContactForm.tsx
import { useEffect, useRef } from "react";
export function ContactForm() {
const ref = useRef<HTMLElement>(null);
useEffect(() => {
const element = ref.current;
if (!element) return;
const onSubmitted = (event: Event) => {
console.log((event as CustomEvent).detail);
};
element.addEventListener("contact-form:submitted", onSubmitted);
return () => {
element.removeEventListener("contact-form:submitted", onSubmitted);
};
}, []);
return <contact-form
ref={ref}
headline="Ask about your site"
privacy-policy="https://example.com/privacy"
/>;
}
- Add the site's Taggor script once; the component appears when it loads.
- React 18 sends every prop of a custom element as a text attribute.
- The event is heard with addEventListener, inside an effect.
ContactForm.tsx
import { useEffect, useRef } from "react";
export function ContactForm() {
const ref = useRef<HTMLElement>(null);
useEffect(() => {
const element = ref.current;
if (!element) return;
const onSubmitted = (event: Event) => {
console.log((event as CustomEvent).detail);
};
element.addEventListener("contact-form:submitted", onSubmitted);
return () => {
element.removeEventListener("contact-form:submitted", onSubmitted);
};
}, []);
return <contact-form
ref={ref}
headline="Ask about your site"
privacy-policy="https://example.com/privacy"
/>;
}
- Add the site's Taggor script once; the component appears when it loads.
- React 19 sets a property when the element already has it, an attribute otherwise.
- The event is heard with addEventListener, inside an effect.
src/ContactForm.vue
<script setup lang="ts">
function onSubmitted(event: Event) {
console.log((event as CustomEvent).detail);
}
</script>
<template>
<contact-form
headline="Ask about your site"
privacy-policy="https://example.com/privacy"
@contact-form:submitted="onSubmitted"
/>
</template>
vite.config.ts
import vue from "@vitejs/plugin-vue";
import { defineConfig } from "vite";
export default defineConfig({
plugins: [
vue({
template: {
compilerOptions: {
isCustomElement: (tag) => tag === "contact-form",
},
},
}),
],
});
- Add the site's Taggor script once; the component appears when it loads.
- vite.config.ts tells Vue that this tag is not a Vue component.
src/app/contact-form.component.ts
import { afterNextRender, Component, CUSTOM_ELEMENTS_SCHEMA, DestroyRef, ElementRef, inject } from "@angular/core";
@Component({
selector: "app-contact-form",
schemas: [CUSTOM_ELEMENTS_SCHEMA],
template: `
<contact-form
headline="Ask about your site"
privacy-policy="https://example.com/privacy"
></contact-form>
`,
})
export class ContactFormComponent {
constructor() {
const host = inject<ElementRef<HTMLElement>>(ElementRef);
const element = () => host.nativeElement.querySelector("contact-form");
const onSubmitted = (event: Event) => {
console.log((event as CustomEvent).detail);
};
afterNextRender(() => element()?.addEventListener("contact-form:submitted", onSubmitted));
inject(DestroyRef).onDestroy(() => element()?.removeEventListener("contact-form:submitted", onSubmitted));
}
}
- Add the site's Taggor script once; the component appears when it loads.
- The component declares CUSTOM_ELEMENTS_SCHEMA.
- The event is heard with addEventListener, inside an effect.
src/TaggorComponent.svelte
<script>
function onSubmitted(event) {
console.log(event.detail);
}
</script>
<contact-form
headline="Ask about your site"
privacy-policy="https://example.com/privacy"
oncontact-form:submitted={onSubmitted}
></contact-form>
- Add the site's Taggor script once; the component appears when it loads.
How an agent gets a component like this onto a site:
taggor checktaggor publishThe CSS variables the component exposes, set the way a customer's page would.
styles.css
contact-form {
--cf-accent: #0a7d5a;
--cf-radius: 6px;
}What this form collects
Each submission becomes one row in your workspace, with the consent the visitor gave.
| Field | Type | Rule |
|---|---|---|
| name | string | required |
| required | ||
| message | string | optional |
Declared as personal data: the form shows a consent box and keeps its proof.
A row, as it is stored
{
"name": "Ada Example",
"email": "ada@example.com",
"message": "Can the form email me every new lead?"
}Made-up example data (reserved example.com address). Nothing from this page is stored. This panel is a description: the picture sends nothing.
Props
Generated from the component's manifest and the values above.
index.html
<quote-wizard currency="USD" locale="en"></quote-wizard>
<script>
const element = document.querySelector("quote-wizard");
element.addEventListener("quote-wizard:step", (event) => {
console.log(event.detail);
});
element.addEventListener("quote-wizard:submitted", (event) => {
console.log(event.detail);
});
</script>
- Add the site's Taggor script once; the component appears when it loads.
taggor-component.js
const element = document.createElement("quote-wizard");
element.setAttribute("currency", "USD");
element.setAttribute("locale", "en");
element.addEventListener("quote-wizard:step", (event) => {
console.log(event.detail);
});
element.addEventListener("quote-wizard:submitted", (event) => {
console.log(event.detail);
});
document.body.append(element);
- Add the site's Taggor script once; the component appears when it loads.
QuoteWizard.tsx
import { useEffect, useRef } from "react";
export function QuoteWizard() {
const ref = useRef<HTMLElement>(null);
useEffect(() => {
const element = ref.current;
if (!element) return;
const onStep = (event: Event) => {
console.log((event as CustomEvent).detail);
};
element.addEventListener("quote-wizard:step", onStep);
const onSubmitted = (event: Event) => {
console.log((event as CustomEvent).detail);
};
element.addEventListener("quote-wizard:submitted", onSubmitted);
return () => {
element.removeEventListener("quote-wizard:step", onStep);
element.removeEventListener("quote-wizard:submitted", onSubmitted);
};
}, []);
return <quote-wizard ref={ref} currency="USD" locale="en" />;
}
- Add the site's Taggor script once; the component appears when it loads.
- React 18 sends every prop of a custom element as a text attribute.
- The event is heard with addEventListener, inside an effect.
QuoteWizard.tsx
import { useEffect, useRef } from "react";
export function QuoteWizard() {
const ref = useRef<HTMLElement>(null);
useEffect(() => {
const element = ref.current;
if (!element) return;
const onStep = (event: Event) => {
console.log((event as CustomEvent).detail);
};
element.addEventListener("quote-wizard:step", onStep);
const onSubmitted = (event: Event) => {
console.log((event as CustomEvent).detail);
};
element.addEventListener("quote-wizard:submitted", onSubmitted);
return () => {
element.removeEventListener("quote-wizard:step", onStep);
element.removeEventListener("quote-wizard:submitted", onSubmitted);
};
}, []);
return <quote-wizard ref={ref} currency="USD" locale="en" />;
}
- Add the site's Taggor script once; the component appears when it loads.
- React 19 sets a property when the element already has it, an attribute otherwise.
- The event is heard with addEventListener, inside an effect.
src/QuoteWizard.vue
<script setup lang="ts">
function onStep(event: Event) {
console.log((event as CustomEvent).detail);
}
function onSubmitted(event: Event) {
console.log((event as CustomEvent).detail);
}
</script>
<template>
<quote-wizard
currency="USD"
locale="en"
@quote-wizard:step="onStep"
@quote-wizard:submitted="onSubmitted"
/>
</template>
vite.config.ts
import vue from "@vitejs/plugin-vue";
import { defineConfig } from "vite";
export default defineConfig({
plugins: [
vue({
template: {
compilerOptions: {
isCustomElement: (tag) => tag === "quote-wizard",
},
},
}),
],
});
- Add the site's Taggor script once; the component appears when it loads.
- vite.config.ts tells Vue that this tag is not a Vue component.
src/app/quote-wizard.component.ts
import { afterNextRender, Component, CUSTOM_ELEMENTS_SCHEMA, DestroyRef, ElementRef, inject } from "@angular/core";
@Component({
selector: "app-quote-wizard",
schemas: [CUSTOM_ELEMENTS_SCHEMA],
template: `
<quote-wizard currency="USD" locale="en"></quote-wizard>
`,
})
export class QuoteWizardComponent {
constructor() {
const host = inject<ElementRef<HTMLElement>>(ElementRef);
const element = () => host.nativeElement.querySelector("quote-wizard");
const onStep = (event: Event) => {
console.log((event as CustomEvent).detail);
};
afterNextRender(() => element()?.addEventListener("quote-wizard:step", onStep));
inject(DestroyRef).onDestroy(() => element()?.removeEventListener("quote-wizard:step", onStep));
const onSubmitted = (event: Event) => {
console.log((event as CustomEvent).detail);
};
afterNextRender(() => element()?.addEventListener("quote-wizard:submitted", onSubmitted));
inject(DestroyRef).onDestroy(() => element()?.removeEventListener("quote-wizard:submitted", onSubmitted));
}
}
- Add the site's Taggor script once; the component appears when it loads.
- The component declares CUSTOM_ELEMENTS_SCHEMA.
- The event is heard with addEventListener, inside an effect.
src/TaggorComponent.svelte
<script>
function onStep(event) {
console.log(event.detail);
}
function onSubmitted(event) {
console.log(event.detail);
}
</script>
<quote-wizard
currency="USD"
locale="en"
onquote-wizard:step={onStep}
onquote-wizard:submitted={onSubmitted}
></quote-wizard>
- Add the site's Taggor script once; the component appears when it loads.
How an agent gets a component like this onto a site:
taggor checktaggor publishThe CSS variables the component exposes, set the way a customer's page would.
styles.css
quote-wizard {
--qw-radius: 10px;
}Props
Generated from the component's manifest and the values above.
index.html
<quiz-result locale="en"></quiz-result>
<script>
const element = document.querySelector("quiz-result");
element.addEventListener("quiz-result:answered", (event) => {
console.log(event.detail);
});
element.addEventListener("quiz-result:completed", (event) => {
console.log(event.detail);
});
</script>
- Add the site's Taggor script once; the component appears when it loads.
taggor-component.js
const element = document.createElement("quiz-result");
element.setAttribute("locale", "en");
element.addEventListener("quiz-result:answered", (event) => {
console.log(event.detail);
});
element.addEventListener("quiz-result:completed", (event) => {
console.log(event.detail);
});
document.body.append(element);
- Add the site's Taggor script once; the component appears when it loads.
QuizResult.tsx
import { useEffect, useRef } from "react";
export function QuizResult() {
const ref = useRef<HTMLElement>(null);
useEffect(() => {
const element = ref.current;
if (!element) return;
const onAnswered = (event: Event) => {
console.log((event as CustomEvent).detail);
};
element.addEventListener("quiz-result:answered", onAnswered);
const onCompleted = (event: Event) => {
console.log((event as CustomEvent).detail);
};
element.addEventListener("quiz-result:completed", onCompleted);
return () => {
element.removeEventListener("quiz-result:answered", onAnswered);
element.removeEventListener("quiz-result:completed", onCompleted);
};
}, []);
return <quiz-result ref={ref} locale="en" />;
}
- Add the site's Taggor script once; the component appears when it loads.
- React 18 sends every prop of a custom element as a text attribute.
- The event is heard with addEventListener, inside an effect.
QuizResult.tsx
import { useEffect, useRef } from "react";
export function QuizResult() {
const ref = useRef<HTMLElement>(null);
useEffect(() => {
const element = ref.current;
if (!element) return;
const onAnswered = (event: Event) => {
console.log((event as CustomEvent).detail);
};
element.addEventListener("quiz-result:answered", onAnswered);
const onCompleted = (event: Event) => {
console.log((event as CustomEvent).detail);
};
element.addEventListener("quiz-result:completed", onCompleted);
return () => {
element.removeEventListener("quiz-result:answered", onAnswered);
element.removeEventListener("quiz-result:completed", onCompleted);
};
}, []);
return <quiz-result ref={ref} locale="en" />;
}
- Add the site's Taggor script once; the component appears when it loads.
- React 19 sets a property when the element already has it, an attribute otherwise.
- The event is heard with addEventListener, inside an effect.
src/QuizResult.vue
<script setup lang="ts">
function onAnswered(event: Event) {
console.log((event as CustomEvent).detail);
}
function onCompleted(event: Event) {
console.log((event as CustomEvent).detail);
}
</script>
<template>
<quiz-result
locale="en"
@quiz-result:answered="onAnswered"
@quiz-result:completed="onCompleted"
/>
</template>
vite.config.ts
import vue from "@vitejs/plugin-vue";
import { defineConfig } from "vite";
export default defineConfig({
plugins: [
vue({
template: {
compilerOptions: {
isCustomElement: (tag) => tag === "quiz-result",
},
},
}),
],
});
- Add the site's Taggor script once; the component appears when it loads.
- vite.config.ts tells Vue that this tag is not a Vue component.
src/app/quiz-result.component.ts
import { afterNextRender, Component, CUSTOM_ELEMENTS_SCHEMA, DestroyRef, ElementRef, inject } from "@angular/core";
@Component({
selector: "app-quiz-result",
schemas: [CUSTOM_ELEMENTS_SCHEMA],
template: `
<quiz-result locale="en"></quiz-result>
`,
})
export class QuizResultComponent {
constructor() {
const host = inject<ElementRef<HTMLElement>>(ElementRef);
const element = () => host.nativeElement.querySelector("quiz-result");
const onAnswered = (event: Event) => {
console.log((event as CustomEvent).detail);
};
afterNextRender(() => element()?.addEventListener("quiz-result:answered", onAnswered));
inject(DestroyRef).onDestroy(() => element()?.removeEventListener("quiz-result:answered", onAnswered));
const onCompleted = (event: Event) => {
console.log((event as CustomEvent).detail);
};
afterNextRender(() => element()?.addEventListener("quiz-result:completed", onCompleted));
inject(DestroyRef).onDestroy(() => element()?.removeEventListener("quiz-result:completed", onCompleted));
}
}
- Add the site's Taggor script once; the component appears when it loads.
- The component declares CUSTOM_ELEMENTS_SCHEMA.
- The event is heard with addEventListener, inside an effect.
src/TaggorComponent.svelte
<script>
function onAnswered(event) {
console.log(event.detail);
}
function onCompleted(event) {
console.log(event.detail);
}
</script>
<quiz-result
locale="en"
onquiz-result:answered={onAnswered}
onquiz-result:completed={onCompleted}
></quiz-result>
- Add the site's Taggor script once; the component appears when it loads.
How an agent gets a component like this onto a site:
taggor checktaggor publishThe CSS variables the component exposes, set the way a customer's page would.
styles.css
quiz-result {
--qz-radius: 12px;
}03
Not on this list
Plugins for specific platforms and billing are not part of this list. A capability appears here when it is built.