Blog/Guides
Guides4 min read

Custom HTML Templates in Potato

Create custom annotation interfaces with HTML templates, CSS styling, and JavaScript interactivity.

By Potato Teamยท

Custom HTML Templates in Potato

While Potato provides built-in annotation types, custom HTML templates let you create specialized interfaces for unique annotation needs. This guide covers template creation, styling, and JavaScript integration.

When to Use Custom Templates

Custom templates are useful for:

  • Complex layouts not supported by built-in types
  • Domain-specific interfaces (medical, legal, etc.)
  • Interactive visualizations
  • Custom input widgets
  • Branded annotation experiences

Basic Template Structure

Template Configuration

annotation_task_name: "Custom Template Annotation"
 
html_layout: "templates/my_template.html"

Template File

<!-- templates/my_template.html -->
<div class="custom-annotation-container">
  <!-- Display the text -->
  <div class="content-display">
    <div class="text-content">
      {{text}}
    </div>
  </div>
 
  <!-- Custom annotation interface -->
  <div class="annotation-area">
    <!-- Potato will inject annotation schemes here -->
    {{annotation_schemes}}
  </div>
</div>

Template Variables

Available Variables

<!-- Item data -->
{{id}}           <!-- Item ID -->
{{text}}         <!-- Main text content -->
{{image_url}}    <!-- Image URL if present -->
{{audio_url}}    <!-- Audio URL if present -->
 
<!-- Metadata -->
{{metadata.field_name}}  <!-- Any metadata field -->

Styling Custom Templates

Inline CSS

<style>
  .custom-annotation-container {
    max-width: 800px;
    margin: 0 auto;
    padding: 20px;
    font-family: -apple-system, BlinkMacSystemFont, sans-serif;
  }
 
  .task-header {
    background: linear-gradient(135deg, #6E56CF, #9F7AEA);
    color: white;
    padding: 15px 20px;
    border-radius: 8px;
    margin-bottom: 20px;
  }
 
  .content-display {
    background: #F8FAFC;
    border: 1px solid #E2E8F0;
    border-radius: 8px;
    padding: 20px;
    margin-bottom: 20px;
  }
 
  .text-content {
    font-size: 16px;
    line-height: 1.6;
    color: #1E293B;
  }
 
  .metadata {
    margin-top: 15px;
    padding-top: 15px;
    border-top: 1px solid #E2E8F0;
    font-size: 14px;
    color: #64748B;
  }
 
  .annotation-area {
    background: white;
    border: 1px solid #E2E8F0;
    border-radius: 8px;
    padding: 20px;
  }
 
  /* Custom highlight styles */
  .highlight-positive {
    background-color: #D1FAE5;
    padding: 2px 4px;
    border-radius: 3px;
  }
 
  .highlight-negative {
    background-color: #FEE2E2;
    padding: 2px 4px;
    border-radius: 3px;
  }
</style>

JavaScript Integration

Basic Interactivity

<script>
  document.addEventListener('DOMContentLoaded', function() {
    // Get the text content element
    const textContent = document.querySelector('.text-content');
 
    // Add click-to-highlight functionality
    textContent.addEventListener('mouseup', function() {
      const selection = window.getSelection();
      if (selection.toString().trim()) {
        highlightSelection(selection);
      }
    });
 
    function highlightSelection(selection) {
      const range = selection.getRangeAt(0);
      const span = document.createElement('span');
      span.className = 'user-highlight';
      range.surroundContents(span);
    }
  });
</script>

Advanced Templates

Side-by-Side Comparison

<div class="comparison-container">
  <style>
    .comparison-container {
      display: grid;
      grid-template-columns: 1fr 1fr;
      gap: 20px;
    }
 
    .comparison-item {
      border: 1px solid #E2E8F0;
      border-radius: 8px;
      padding: 15px;
    }
 
    .comparison-item.selected {
      border-color: #6E56CF;
      box-shadow: 0 0 0 2px rgba(110, 86, 207, 0.2);
    }
 
    .item-label {
      font-weight: 600;
      margin-bottom: 10px;
      color: #6E56CF;
    }
  </style>
 
  <div class="comparison-item" data-option="A" onclick="selectOption('A')">
    <div class="item-label">Option A</div>
    <div class="item-content">{{option_a}}</div>
  </div>
 
  <div class="comparison-item" data-option="B" onclick="selectOption('B')">
    <div class="item-label">Option B</div>
    <div class="item-content">{{option_b}}</div>
  </div>
 
  <script>
    function selectOption(option) {
      // Update visual selection
      document.querySelectorAll('.comparison-item').forEach(el => {
        el.classList.remove('selected');
      });
      document.querySelector(`[data-option="${option}"]`).classList.add('selected');
    }
  </script>
</div>

Interactive Highlighting

<div class="highlight-annotation">
  <style>
    .highlight-toolbar {
      display: flex;
      gap: 10px;
      margin-bottom: 15px;
    }
 
    .highlight-btn {
      padding: 8px 16px;
      border: 1px solid #E2E8F0;
      border-radius: 6px;
      cursor: pointer;
      background: white;
    }
 
    .highlight-btn.active {
      background: #6E56CF;
      color: white;
      border-color: #6E56CF;
    }
 
    .highlightable-text {
      line-height: 1.8;
    }
 
    .highlight-positive { background: #D1FAE5; }
    .highlight-negative { background: #FEE2E2; }
    .highlight-neutral { background: #FEF3C7; }
  </style>
 
  <div class="highlight-toolbar">
    <button class="highlight-btn" data-color="positive" onclick="setHighlightMode('positive')">
      Positive
    </button>
    <button class="highlight-btn" data-color="negative" onclick="setHighlightMode('negative')">
      Negative
    </button>
    <button class="highlight-btn" data-color="neutral" onclick="setHighlightMode('neutral')">
      Neutral
    </button>
    <button class="highlight-btn" onclick="clearHighlights()">
      Clear All
    </button>
  </div>
 
  <div class="highlightable-text" id="textContent">
    {{text}}
  </div>
 
  <script>
    let currentMode = null;
    let highlights = [];
 
    function setHighlightMode(mode) {
      currentMode = mode;
      document.querySelectorAll('.highlight-btn').forEach(btn => {
        btn.classList.toggle('active', btn.dataset.color === mode);
      });
    }
 
    document.getElementById('textContent').addEventListener('mouseup', function() {
      if (!currentMode) return;
 
      const selection = window.getSelection();
      if (selection.toString().trim()) {
        const range = selection.getRangeAt(0);
        const span = document.createElement('span');
        span.className = `highlight-${currentMode}`;
 
        // Store highlight data
        highlights.push({
          text: selection.toString(),
          type: currentMode,
          start: range.startOffset,
          end: range.endOffset
        });
 
        range.surroundContents(span);
        selection.removeAllRanges();
      }
    });
 
    function clearHighlights() {
      highlights = [];
      document.getElementById('textContent').innerHTML = '{{text}}';
    }
  </script>
</div>

Tabbed Interface

<div class="tabbed-interface">
  <style>
    .tab-buttons {
      display: flex;
      border-bottom: 2px solid #E2E8F0;
    }
 
    .tab-btn {
      padding: 12px 24px;
      border: none;
      background: none;
      cursor: pointer;
      font-size: 14px;
      font-weight: 500;
      color: #64748B;
      border-bottom: 2px solid transparent;
      margin-bottom: -2px;
    }
 
    .tab-btn.active {
      color: #6E56CF;
      border-bottom-color: #6E56CF;
    }
 
    .tab-content {
      display: none;
      padding: 20px 0;
    }
 
    .tab-content.active {
      display: block;
    }
  </style>
 
  <div class="tab-buttons">
    <button class="tab-btn active" onclick="showTab('text')">Text</button>
    <button class="tab-btn" onclick="showTab('metadata')">Metadata</button>
    <button class="tab-btn" onclick="showTab('context')">Context</button>
  </div>
 
  <div class="tab-content active" id="tab-text">
    <div class="text-content">{{text}}</div>
  </div>
 
  <div class="tab-content" id="tab-metadata">
    <pre>{{metadata | json}}</pre>
  </div>
 
  <div class="tab-content" id="tab-context">
    <p><strong>Source:</strong> {{metadata.source}}</p>
    <p><strong>Date:</strong> {{metadata.date}}</p>
    <p><strong>Author:</strong> {{metadata.author}}</p>
  </div>
 
  <script>
    function showTab(tabName) {
      document.querySelectorAll('.tab-btn').forEach(btn => {
        btn.classList.remove('active');
      });
      document.querySelectorAll('.tab-content').forEach(content => {
        content.classList.remove('active');
      });
 
      event.target.classList.add('active');
      document.getElementById(`tab-${tabName}`).classList.add('active');
    }
  </script>
</div>

Best Practices

  1. Keep templates simple: Complex logic should be in config, not templates
  2. Test responsiveness: Ensure templates work on different screen sizes
  3. Accessible design: Use proper contrast, labels, and keyboard navigation
  4. Performance: Minimize JavaScript and optimize images
  5. Fallbacks: Provide fallbacks for missing data
  6. Documentation: Comment your templates for maintainability

Full documentation at /docs/core-concepts/annotation-types.