Skip to content

Visual Editor Annotations ​

This document describes the HTML data attributes required for the DCS portal's visual page editor to detect and enable editing of page sections and text content.

Overview ​

The visual page editor displays an interactive preview of customer site pages, allowing users to:

  • View page sections with labeled boundaries
  • Edit text content inline
  • See changes reflected in real-time

For this to work, site HTML must include specific data-* attributes that the snapshot capture script detects.

Required Attributes ​

Section Container: data-section ​

Every editable section of a page must be wrapped in an element with data-section:

html
<section data-section="hero" data-section-label="Hero Banner">
  <!-- Section content -->
</section>
AttributeRequiredDescription
data-section✅Unique identifier within the page (e.g., hero, features, cta)
data-section-label✅Human-readable label shown in the editor sidebar
data-section-type❌Optional type hint (e.g., header, footer, content)
data-dynamic❌Flag for dynamically generated content (e.g., blog post lists)

Text Content: data-text-key ​

Every editable text element must have data-text-key matching a key in .dcs/content.yaml:

html
<h1 data-text-key="hero.heading">{{ t('hero.heading') }}</h1>
<p data-text-key="hero.description">{{ t('hero.description') }}</p>
<button data-text-key="hero.cta">{{ t('hero.cta') }}</button>

The data-text-key value must:

  • Match a key in content.yaml (either global or page-specific)
  • Follow dot notation: section.element or section.item-N.property
  • Be unique within the page

Complete Example ​

Vue Component ​

vue
<script setup lang="ts">
import { useTextContent } from '@duffcloudservices/cms'

const { t } = useTextContent({
  pageSlug: 'home',
  defaults: {
    'hero.heading': 'Welcome to Our Site',
    'hero.subheading': 'Build something amazing',
    'hero.cta': 'Get Started',
    'features.heading': 'Why Choose Us',
    'features.item-1.title': 'Fast',
    'features.item-1.description': 'Lightning fast performance',
    'features.item-2.title': 'Secure',
    'features.item-2.description': 'Enterprise-grade security',
  }
})
</script>

<template>
  <main>
    <!-- Hero Section -->
    <section data-section="hero" data-section-label="Hero Banner">
      <h1 data-text-key="hero.heading">{{ t('hero.heading') }}</h1>
      <p data-text-key="hero.subheading">{{ t('hero.subheading') }}</p>
      <button data-text-key="hero.cta">{{ t('hero.cta') }}</button>
    </section>

    <!-- Features Section -->
    <section data-section="features" data-section-label="Features">
      <h2 data-text-key="features.heading">{{ t('features.heading') }}</h2>
      
      <div class="feature-grid">
        <div class="feature">
          <h3 data-text-key="features.item-1.title">{{ t('features.item-1.title') }}</h3>
          <p data-text-key="features.item-1.description">{{ t('features.item-1.description') }}</p>
        </div>
        <div class="feature">
          <h3 data-text-key="features.item-2.title">{{ t('features.item-2.title') }}</h3>
          <p data-text-key="features.item-2.description">{{ t('features.item-2.description') }}</p>
        </div>
      </div>
    </section>
  </main>
</template>

Matching content.yaml ​

yaml
# .dcs/content.yaml
version: 1
lastUpdated: "2026-01-08T00:00:00Z"

global:
  nav.home: Home
  nav.about: About
  footer.copyright: © 2026 Company. All rights reserved.

pages:
  home:
    hero.heading: Welcome to Our Site
    hero.subheading: Build something amazing
    hero.cta: Get Started
    features.heading: Why Choose Us
    features.item-1.title: Fast
    features.item-1.description: Lightning fast performance
    features.item-2.title: Secure
    features.item-2.description: Enterprise-grade security

How Section Detection Works ​

During deployment, the snapshot capture script:

  1. Navigates to each page defined in .dcs/pages.yaml
  2. Queries the DOM for [data-section] elements
  3. For each section, extracts:
    • data-section → Section ID
    • data-section-label → Display label
    • Bounding box coordinates
  4. Scans for [data-text-key] elements within each section
  5. Records each text key with its current value
  6. Captures a screenshot of the section

The resulting snapshot is stored in blob storage:

/{siteSlug}/site-snapshots/
  ├── manifest.json           # Page list and capture metadata
  └── {pageSlug}/
      ├── snapshot.json       # Page data including sections and text keys
      ├── full-page.png       # Full page screenshot
      └── sections/
          ├── hero.png        # Individual section screenshots
          └── features.png

Framework-Specific Patterns ​

VitePress ​

VitePress uses Markdown with frontmatter, requiring custom components for editable sections:

vue
<!-- .vitepress/theme/components/EditableSection.vue -->
<script setup lang="ts">
defineProps<{
  sectionId: string
  label: string
}>()
</script>

<template>
  <section :data-section="sectionId" :data-section-label="label">
    <slot />
  </section>
</template>
md
<!-- docs/index.md -->
<EditableSection section-id="hero" label="Hero Banner">

# Welcome to CloudSync
<span data-text-key="home.hero.tagline">Cloud sync made simple</span>

</EditableSection>

React ​

tsx
// src/components/Section.tsx
interface SectionProps {
  id: string
  label: string
  children: React.ReactNode
}

export function Section({ id, label, children }: SectionProps) {
  return (
    <section data-section={id} data-section-label={label}>
      {children}
    </section>
  )
}

// src/pages/Home.tsx
import { useTextContent } from '@duffcloudservices/cms-react'
import { Section } from '../components/Section'

export function Home() {
  const { t } = useTextContent({ pageSlug: 'home', defaults: {...} })
  
  return (
    <Section id="hero" label="Hero Banner">
      <h1 data-text-key="hero.heading">{t('hero.heading')}</h1>
      <p data-text-key="hero.description">{t('hero.description')}</p>
    </Section>
  )
}

Angular ​

typescript
// src/app/components/section.component.ts
@Component({
  selector: 'app-section',
  template: `
    <section [attr.data-section]="sectionId" [attr.data-section-label]="label">
      <ng-content></ng-content>
    </section>
  `
})
export class SectionComponent {
  @Input() sectionId!: string
  @Input() label!: string
}

Astro ​

astro
---
// src/components/Section.astro
interface Props {
  id: string
  label: string
}

const { id, label } = Astro.props
---

<section data-section={id} data-section-label={label}>
  <slot />
</section>

Dynamic Content ​

For dynamically generated content (e.g., blog post lists), use data-dynamic to indicate the section contains generated content:

html
<section data-section="blog-posts" data-section-label="Blog Posts" data-dynamic>
  <h2 data-text-key="posts.heading">{{ t('posts.heading') }}</h2>
  <!-- Blog posts are rendered dynamically, not directly editable -->
  <div v-for="post in posts" :key="post.id">
    <h3>{{ post.title }}</h3>
  </div>
</section>

Dynamic sections will appear in the editor but individual items won't have editable text keys.

Troubleshooting ​

"No sections detected" ​

If the page editor shows "No sections detected":

  1. Check HTML attributes - Ensure data-section and data-section-label are present
  2. Re-run snapshot capture - Snapshots may be outdated
  3. Verify deployment - Ensure the latest build with annotations is deployed

Text keys not appearing ​

If sections appear but text keys are missing:

  1. Check data-text-key attributes - Must be on the text element itself
  2. Verify content.yaml - Key must exist in content.yaml
  3. Check snapshot.json - Verify text keys were captured

Section boundaries wrong ​

If section boundaries don't match the visual layout:

  1. Check element structure - data-section should be on the outermost container
  2. Check CSS - Ensure no overflow/clip issues affecting bounding box
  3. Re-capture - Bounding boxes are calculated at capture time

Best Practices ​

  1. Use semantic section IDs - hero, features, testimonials not section-1, section-2
  2. Provide clear labels - "Hero Banner" is better than "Hero"
  3. Keep text keys consistent - Use section.element naming pattern
  4. Include defaults - Always provide fallback values in composables
  5. Test locally - Run snapshot capture locally before deploying
  6. Document custom patterns - Note any non-standard implementations