Skip to content

Component Examples

Real-world component implementations you can use as templates.

Hello Card (Minimal)

A minimal component showing the basics.

Use case: Learning component structure

manifest.json:

json
{
  "name": "hello-card",
  "version": "1.0.0",
  "description": "Minimal hello world component",
  "entry": "index.html",
  "component": {
    "width": 300,
    "height": 200,
    "schema": {
      "type": "object",
      "properties": {
        "message": {
          "type": "string",
          "default": "Hello World!"
        }
      }
    }
  }
}

index.html:

html
<!DOCTYPE html>
<html>
<head>
  <style>
    body {
      margin: 0;
      display: flex;
      align-items: center;
      justify-content: center;
      height: 100vh;
      background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
      color: white;
      font-family: sans-serif;
    }
  </style>
</head>
<body>
  <div>
    <h1 id="message">Hello!</h1>
    <button onclick="sayHello()">Click Me</button>
  </div>

  <script>
    const props = window.componentProps || {};
    document.getElementById('message').textContent = props.message || 'Hello World!';

    function sayHello() {
      alert('Hello! 👋');
    }
  </script>
</body>
</html>

Download ZIP | Try on Board


Vocabulary Card

Interactive flashcard for language learning.

Use case: Educational platforms, language schools

Features:

  • Front/back flip animation
  • Audio pronunciation
  • Progress tracking
  • Difficulty levels

manifest.json:

json
{
  "name": "vocabulary-card",
  "version": "1.0.0",
  "description": "Interactive vocabulary flashcard",
  "entry": "index.html",
  "component": {
    "width": 300,
    "height": 200,
    "schema": {
      "type": "object",
      "properties": {
        "word": { "type": "string", "required": true },
        "translation": { "type": "string" },
        "example": { "type": "string" },
        "audioUrl": { "type": "string", "format": "uri" },
        "difficulty": {
          "type": "string",
          "enum": ["easy", "medium", "hard"],
          "default": "medium"
        }
      }
    }
  }
}

index.html:

html
<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <style>
    * { margin: 0; padding: 0; box-sizing: border-box; }

    body {
      font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
      width: 100%;
      height: 100%;
    }

    .card {
      width: 100%;
      height: 100%;
      perspective: 1000px;
    }

    .card-inner {
      position: relative;
      width: 100%;
      height: 100%;
      transition: transform 0.6s;
      transform-style: preserve-3d;
    }

    .card.flipped .card-inner {
      transform: rotateY(180deg);
    }

    .card-face {
      position: absolute;
      width: 100%;
      height: 100%;
      backface-visibility: hidden;
      display: flex;
      flex-direction: column;
      align-items: center;
      justify-content: center;
      padding: 20px;
      border-radius: 12px;
      box-shadow: 0 4px 6px rgba(0,0,0,0.1);
    }

    .card-front {
      background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
      color: white;
    }

    .card-back {
      background: white;
      color: #333;
      transform: rotateY(180deg);
    }

    .word {
      font-size: 32px;
      font-weight: bold;
      margin-bottom: 10px;
    }

    .example {
      font-size: 14px;
      opacity: 0.9;
      font-style: italic;
      margin-top: 10px;
    }

    .translation {
      font-size: 24px;
      color: #667eea;
      margin-bottom: 15px;
    }

    .difficulty {
      position: absolute;
      top: 10px;
      right: 10px;
      padding: 4px 8px;
      border-radius: 4px;
      font-size: 12px;
      text-transform: uppercase;
    }

    .difficulty-easy { background: #10b981; color: white; }
    .difficulty-medium { background: #f59e0b; color: white; }
    .difficulty-hard { background: #ef4444; color: white; }

    .audio-button {
      margin-top: 15px;
      padding: 8px 16px;
      background: rgba(255,255,255,0.2);
      border: 2px solid white;
      border-radius: 20px;
      color: white;
      cursor: pointer;
      font-size: 14px;
    }

    .audio-button:hover {
      background: rgba(255,255,255,0.3);
    }
  </style>
</head>
<body>
  <div class="card" id="card">
    <div class="card-inner">
      <!-- Front -->
      <div class="card-face card-front">
        <div class="difficulty" id="difficulty"></div>
        <div class="word" id="word">Word</div>
        <div class="example" id="example"></div>
        <button class="audio-button" id="audioBtn" onclick="playAudio()">
          🔊 Pronounce
        </button>
      </div>

      <!-- Back -->
      <div class="card-face card-back">
        <div class="translation" id="translation">Translation</div>
        <button class="audio-button" onclick="flipCard()">
          ← Back
        </button>
      </div>
    </div>
  </div>

  <script>
    const props = window.componentProps || {};
    let flipped = false;

    function init() {
      // Set content
      document.getElementById('word').textContent = props.word || 'Word';
      document.getElementById('translation').textContent = props.translation || 'Translation';
      document.getElementById('example').textContent = props.example || '';

      // Set difficulty badge
      const difficulty = props.difficulty || 'medium';
      const badge = document.getElementById('difficulty');
      badge.textContent = difficulty;
      badge.className = `difficulty difficulty-${difficulty}`;

      // Hide audio button if no URL
      if (!props.audioUrl) {
        document.getElementById('audioBtn').style.display = 'none';
      }
    }

    function flipCard() {
      const card = document.getElementById('card');
      flipped = !flipped;
      card.classList.toggle('flipped', flipped);

      // Emit event
      window.parent.postMessage({
        type: 'component-event',
        event: 'card-flipped',
        data: { word: props.word, flipped }
      }, '*');
    }

    function playAudio() {
      if (props.audioUrl) {
        const audio = new Audio(props.audioUrl);
        audio.play();
      }
    }

    // Flip on click (front only)
    document.querySelector('.card-front').addEventListener('click', (e) => {
      if (e.target.tagName !== 'BUTTON') {
        flipCard();
      }
    });

    init();
  </script>
</body>
</html>

Usage:

json
{
  "components": [
    {
      "type": "vocabulary-card",
      "props": {
        "word": "Hello",
        "translation": "Привет",
        "example": "Hello, how are you?",
        "audioUrl": "https://example.com/audio/hello.mp3",
        "difficulty": "easy"
      }
    }
  ]
}

Todo Card

Simple task card with checkbox.

Use case: Project management, Kanban boards

index.html:

html
<!DOCTYPE html>
<html>
<head>
  <style>
    body {
      margin: 0;
      padding: 15px;
      font-family: sans-serif;
      background: white;
      border: 1px solid #e5e7eb;
      border-radius: 8px;
    }

    .todo {
      display: flex;
      align-items: flex-start;
      gap: 10px;
    }

    .checkbox {
      margin-top: 2px;
      width: 20px;
      height: 20px;
      cursor: pointer;
    }

    .content {
      flex: 1;
    }

    .title {
      font-size: 16px;
      font-weight: 500;
      margin-bottom: 5px;
    }

    .title.completed {
      text-decoration: line-through;
      color: #9ca3af;
    }

    .description {
      font-size: 14px;
      color: #6b7280;
    }

    .priority {
      display: inline-block;
      padding: 2px 8px;
      border-radius: 12px;
      font-size: 12px;
      margin-top: 5px;
    }

    .priority-high { background: #fee2e2; color: #dc2626; }
    .priority-medium { background: #fef3c7; color: #d97706; }
    .priority-low { background: #dbeafe; color: #2563eb; }
  </style>
</head>
<body>
  <div class="todo">
    <input type="checkbox" class="checkbox" id="checkbox" onchange="toggleComplete()">

    <div class="content">
      <div class="title" id="title">Task title</div>
      <div class="description" id="description"></div>
      <span class="priority" id="priority"></span>
    </div>
  </div>

  <script>
    const props = window.componentProps || {};
    let completed = props.completed || false;

    function init() {
      document.getElementById('title').textContent = props.title || 'Untitled Task';
      document.getElementById('description').textContent = props.description || '';

      const priority = props.priority || 'medium';
      const badge = document.getElementById('priority');
      badge.textContent = priority.toUpperCase();
      badge.className = `priority priority-${priority}`;

      document.getElementById('checkbox').checked = completed;
      updateUI();
    }

    function toggleComplete() {
      completed = !completed;
      updateUI();

      // Emit event
      window.parent.postMessage({
        type: 'component-event',
        event: 'task-completed',
        data: { taskId: props.id, completed }
      }, '*');
    }

    function updateUI() {
      const title = document.getElementById('title');
      title.classList.toggle('completed', completed);
    }

    init();
  </script>
</body>
</html>

Counter Widget

Real-time counter with increment/decrement.

Use case: Live polls, voting, scorekeeping

html
<!DOCTYPE html>
<html>
<head>
  <style>
    body {
      margin: 0;
      display: flex;
      flex-direction: column;
      align-items: center;
      justify-content: center;
      height: 100vh;
      background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
      color: white;
      font-family: sans-serif;
    }

    .count {
      font-size: 64px;
      font-weight: bold;
      margin: 20px 0;
    }

    .buttons {
      display: flex;
      gap: 10px;
    }

    button {
      width: 50px;
      height: 50px;
      border: none;
      border-radius: 50%;
      background: white;
      color: #f5576c;
      font-size: 24px;
      cursor: pointer;
      transition: transform 0.1s;
    }

    button:active {
      transform: scale(0.95);
    }
  </style>
</head>
<body>
  <p>Count</p>
  <div class="count" id="count">0</div>
  <div class="buttons">
    <button onclick="decrement()">−</button>
    <button onclick="increment()">+</button>
  </div>

  <script>
    let count = window.componentProps?.count || 0;

    function updateDisplay() {
      document.getElementById('count').textContent = count;
    }

    function increment() {
      count++;
      updateDisplay();
      broadcastChange();
    }

    function decrement() {
      count--;
      updateDisplay();
      broadcastChange();
    }

    function broadcastChange() {
      window.parent.postMessage({
        type: 'component-event',
        event: 'counter-changed',
        data: { count }
      }, '*');
    }

    // Listen for changes from other instances
    window.addEventListener('message', (e) => {
      if (e.data.type === 'board-event' && e.data.event === 'counter-changed') {
        count = e.data.data.count;
        updateDisplay();
      }
    });

    updateDisplay();
  </script>
</body>
</html>

More Examples

Educational

  • Quiz Question - Multiple choice with timer
  • Progress Bar - Student completion tracking
  • Grade Calculator - Automatic grade computation

Business

  • Pipeline Card - Sales stage tracking
  • Time Tracker - Log hours worked
  • Customer Card - Contact info display

Creative

  • Color Picker - Collaborative color selection
  • Mood Board Image - Annotated image card
  • Sticky Note - Classic sticky note

Need Help?

Can't find what you're looking for?