Slingshot Convention v1
Purpose
When generating a website, your output will likely be edited later by a non-developer through a visual editor (Slingshot). To make that possible without forcing them back into your prompt loop, instrument every meaningfully editable piece of content with data-sl-* attributes. This is a one-time annotation pass during generation. It costs you nothing in output quality and saves the user dozens of re-prompts later.
Core principle
Annotate intent, not implementation. If a human reading the page would think "I might want to change this someday," it gets a data-sl-* attribute. If it's structural or programmatic (a className, a layout div, a script tag), it doesn't.
Required attributes
data-sl-text="<unique-id>"
For any element whose text content should be editable.
Apply to: <h1>–<h6>, <p>, <span>, <a>, <button>, <li>, <label>, <blockquote>, table cells.
<h1 data-sl-text="hero-headline">Welcome to our store</h1>
<p data-sl-text="hero-subtitle">The freshest produce in town.</p>
<button data-sl-text="cta-primary">Shop now</button>
ID rules: kebab-case, semantically meaningful (hero-headline, not text-1), unique within the document. If the same text appears multiple times intentionally, use suffixes: footer-tagline-1, footer-tagline-2.
data-sl-image="<unique-id>"
For any <img> element whose source should be editable. ALWAYS include alt.
<img
data-sl-image="hero-bg"
src="/images/hero.jpg"
alt="Mountain sunrise"
/>
For images embedded as CSS backgrounds, use data-sl-bg-image instead (see Optional Attributes).
data-sl-link="<unique-id>"
For <a> elements where the URL itself should be editable separately from the text.
<a
data-sl-link="header-cta-link"
data-sl-text="header-cta-label"
href="/signup"
>Get started</a>
When data-sl-link and data-sl-text co-exist on the same element, the URL and the link text are independently editable.
data-sl-color="<token-name>"
For elements whose color (text or background) is meant to be themed. Use this when the color comes from a CSS custom property the user might want to recolor globally.
<button
data-sl-color="brand-primary"
style="background: var(--brand-primary);"
>
Subscribe
</button>
The token name should match the CSS custom property name without the -- prefix.
data-sl-section="<section-name>"
Wrap logical groupings (hero, features, pricing, footer) so the editor can group regions in its UI. Section wrappers are NOT themselves editable; they only organize.
<section data-sl-section="hero">
<h1 data-sl-text="hero-headline">…</h1>
<p data-sl-text="hero-subtitle">…</p>
</section>
<section data-sl-section="features">
<!-- feature regions here -->
</section>
Optional attributes
data-sl-bg-image="<unique-id>"
For elements with a CSS background image that should be editable.
<div
data-sl-bg-image="hero-bg"
style="background-image: url('/images/hero.jpg');"
>
data-sl-list="<unique-id>" + data-sl-list-item
For repeatable content blocks (testimonials, feature cards, pricing tiers) where the user should be able to add/remove/reorder items.
<ul data-sl-list="testimonials">
<li data-sl-list-item>
<p data-sl-text="testimonial-1-quote">"Amazing service."</p>
<span data-sl-text="testimonial-1-author">— Jane D.</span>
</li>
<li data-sl-list-item>
<p data-sl-text="testimonial-2-quote">"Changed my life."</p>
<span data-sl-text="testimonial-2-author">— John S.</span>
</li>
</ul>
The editor uses this to offer "Add testimonial" / "Remove" / drag-to-reorder UI.
data-sl-html="<unique-id>"
For arbitrary HTML blocks the user might need to edit as raw markup — embed codes, custom video players, third-party widgets, glyph-based font icons that don't fit other types.
<div data-sl-html="video-embed">
<iframe src="https://www.youtube.com/embed/abc123" />
</div>
Use sparingly. data-sl-html regions are an "escape hatch" — the editor opens a code editor for them, which means the half-stepper user is dropped into raw HTML. Prefer specific attributes (data-sl-image, data-sl-link) when possible.
Prohibited patterns
❌ Don't annotate non-content elements:
<!-- WRONG: layout containers don't need annotations -->
<div data-sl-text="wrapper" class="flex items-center">…</div>
<!-- WRONG: navigation structure isn't editable copy -->
<nav data-sl-text="nav">…</nav>
❌ Don't nest data-sl-text inside data-sl-html:
<!-- WRONG: ambiguous editing ownership -->
<div data-sl-html="hero">
<h1 data-sl-text="headline">…</h1>
</div>
If a region needs raw HTML editing, it owns its entire content. Otherwise, decompose into specific attributes.
❌ Don't use generic IDs:
<!-- WRONG -->
<p data-sl-text="text-1">…</p>
<p data-sl-text="text-2">…</p>
<!-- RIGHT -->
<p data-sl-text="hero-subtitle">…</p>
<p data-sl-text="features-intro">…</p>
❌ Don't annotate dynamic content:
<!-- WRONG: this is a date, not editable copy -->
<span data-sl-text="copyright-year">{currentYear}</span>
The manifest file
After generating the site, output a /slingshot.json file at the repo root that catalogs all editable regions:
{
"version": "1",
"generatedBy": "claude-opus-4-7",
"generatedAt": "2026-05-05T19:00:00Z",
"framework": "react",
"regions": [
{
"id": "hero-headline",
"type": "text",
"section": "hero",
"filePath": "src/components/Hero.tsx",
"lineNumber": 12
},
{
"id": "hero-bg",
"type": "image",
"section": "hero",
"filePath": "src/components/Hero.tsx",
"lineNumber": 18
}
]
}
The manifest is optional but recommended — it speeds up the editor's source-map step. Without it, the editor falls back to scanning all repo files.
Framework-specific notes
The convention is framework-agnostic. The same data-sl-* attributes work across:
- Vanilla HTML: attributes appear directly in
.htmlfiles - React/JSX: attributes are JSX attributes; React passes them through to the DOM
- Vue: use
:data-sl-text="..."for dynamic IDs, plaindata-sl-text="..."for static - Svelte: plain attribute syntax works
- Astro: plain attribute syntax in
.astrofiles - Next.js / Nuxt / SvelteKit / Remix: all framework-built routes inherit attributes
If your output is server-rendered with dynamic content, annotate the static template, not the rendered output. The editor can find the source.
Conformance levels
When generating, target these coverage levels:
| Level | Description |
|---|---|
| Bronze | All <h1>, <h2>, hero text, and primary CTAs annotated. Minimum viable. |
| Silver | All visible text, all <img> tags, all CTAs and links. Recommended default. |
| Gold | Silver + all repeatable lists annotated, all branded colors as tokens, all sections wrapped. The half-stepper can edit the entire site without a developer. |
Default to Silver. Aim for Gold when the user's prompt mentions handing the site off, ongoing edits, or non-technical stakeholders.
Validation checklist
Before completing your output, verify:
- Every visible heading has
data-sl-text - Every
<img>hasdata-sl-imageand analtattribute - Every CTA button/link has
data-sl-text(anddata-sl-linkif URL is editable) - All region IDs are unique
- All region IDs are kebab-case and semantically named
- Logical groupings are wrapped in
data-sl-section - No annotations on layout-only elements
- No nested annotations (e.g.,
data-sl-textinsidedata-sl-html) -
/slingshot.jsonmanifest is generated (Silver+) or omitted (Bronze)
Example: minimal Bronze landing page
<!DOCTYPE html>
<html>
<head><title data-sl-text="page-title">Acme Co.</title></head>
<body>
<section data-sl-section="hero">
<h1 data-sl-text="hero-headline">The future of widgets.</h1>
<p data-sl-text="hero-subtitle">Faster. Stronger. Better.</p>
<a data-sl-link="hero-cta-link" data-sl-text="hero-cta-label" href="/signup">
Get started
</a>
</section>
</body>
</html>
Example: full Gold React component
export default function Hero() {
return (
<section data-sl-section="hero">
<h1 data-sl-text="hero-headline" className="text-5xl font-bold">
The future of widgets.
</h1>
<p data-sl-text="hero-subtitle" className="text-xl mt-4">
Faster. Stronger. Better.
</p>
<img
data-sl-image="hero-illustration"
src="/hero.svg"
alt="Widget illustration"
className="my-8"
/>
<a
data-sl-link="hero-cta-link"
data-sl-text="hero-cta-label"
href="/signup"
data-sl-color="brand-primary"
style={{ background: "var(--brand-primary)" }}
className="px-6 py-3 rounded-lg text-white"
>
Get started
</a>
<ul data-sl-list="hero-features" className="mt-12 grid grid-cols-3 gap-4">
<li data-sl-list-item>
<h3 data-sl-text="feature-1-title">Lightning fast</h3>
<p data-sl-text="feature-1-body">Sub-second response times.</p>
</li>
<li data-sl-list-item>
<h3 data-sl-text="feature-2-title">Always reliable</h3>
<p data-sl-text="feature-2-body">99.99% uptime guaranteed.</p>
</li>
<li data-sl-list-item>
<h3 data-sl-text="feature-3-title">Built for scale</h3>
<p data-sl-text="feature-3-body">From startup to enterprise.</p>
</li>
</ul>
</section>
);
}
Spec version: 1.0 · Last updated: 2026-05-05 · License: MIT