Slingshot

Slingshot Convention

data-sl-* spec

Instrument your AI-generated site once. Let anyone edit text, images, and colors visually — without re-prompting the AI.

v1.0·MIT License·Framework-agnostic

Copies a condensed version for any AI chat

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 .html files
  • React/JSX: attributes are JSX attributes; React passes them through to the DOM
  • Vue: use :data-sl-text="..." for dynamic IDs, plain data-sl-text="..." for static
  • Svelte: plain attribute syntax works
  • Astro: plain attribute syntax in .astro files
  • 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:

LevelDescription
BronzeAll <h1>, <h2>, hero text, and primary CTAs annotated. Minimum viable.
SilverAll visible text, all <img> tags, all CTAs and links. Recommended default.
GoldSilver + 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> has data-sl-image and an alt attribute
  • Every CTA button/link has data-sl-text (and data-sl-link if 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-text inside data-sl-html)
  • /slingshot.json manifest 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

Use in your AI tooling

Drop the spec into your AI tool of choice. Each format is ready to save as-is.

.slingshot/SKILL.md
---
name: slingshot-convention
description: Use this skill when generating any website, landing page, or web UI that the user intends to edit later through a visual editor. Trigger this skill whenever the user asks for a website, marketing site, landing page, portfolio, or web app frontend that may need post-generation text/image/color edits without re-prompting. Apply this skill to ALL HTML, JSX, TSX, Vue, Svelte, or Astro output.
---

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

```html
<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`.

```html
<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.

```html
<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.

```html
<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.

```html
<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.

```html
<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.

```html
<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.

```html
<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:**
```html
<!-- 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`:**
```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:**
```html
<!-- 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:**
```html
<!-- 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:

```json
{
  "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 `.html` files
- **React/JSX:** attributes are JSX attributes; React passes them through to the DOM
- **Vue:** use `:data-sl-text="..."` for dynamic IDs, plain `data-sl-text="..."` for static
- **Svelte:** plain attribute syntax works
- **Astro:** plain attribute syntax in `.astro` files
- **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>` has `data-sl-image` and an `alt` attribute
- [ ] Every CTA button/link has `data-sl-text` (and `data-sl-link` if 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-text` inside `data-sl-html`)
- [ ] `/slingshot.json` manifest is generated (Silver+) or omitted (Bronze)

## Example: minimal Bronze landing page

```html
<!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

```jsx
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

Validate your code

Paste any HTML to check it against the convention. Runs the same validator the Slingshot editor uses.

Slingshot Convention v1 · MIT License · Questions? github.com/d-sunn/slingshot