Blocks
Getting Started

Quick Start

Quick Start

Learn how Blocks detects drift and helps maintain consistency through a real example: building resume themes with proper accessibility and design.

The Problem: Consistency Is Hard at Scale

When building many similar components (whether by humans or AI), common issues emerge:

  • Text colors matching backgrounds (completely unreadable)
  • Poor color contrast (fails WCAG accessibility standards)
  • Broken responsive layouts (mobile unusable)
  • Non-semantic HTML (terrible for screen readers)
  • Missing ARIA labels (accessibility nightmare)

Blocks helps by detecting drift from your domain rules and providing specific, actionable feedback to help you maintain consistency.

Real Example: JSON Resume Themes

This example is from examples/json-resume-themes in the repo.

We'll build a resume theme and show how Blocks catches inconsistencies and drift from your domain rules.

Step 1: Define Your Domain (NOT in a blocks/ folder!)

Create blocks.yml in your project root — you don't need a special blocks/ directory:

blocks.yml
project:
  name: "JSON Resume Themes"
  domain: "jsonresume.themes"

philosophy:
  - "Resume themes must prioritize readability and professionalism."
  - "All themes must be responsive and accessible."
  - "Semantic HTML and proper structure are required."

domain:
  entities:
    resume:
      fields: [basics, work, education, skills]

  signals:
    readability:
      description: "How easy is the resume to read and scan?"
      extraction_hint: "Look for typography, spacing, visual hierarchy"

    accessibility:
      description: "WCAG compliance and screen reader friendliness"
      extraction_hint: "Verify semantic HTML, ARIA labels, color contrast"

  measures:
    valid_html:
      constraints:
        - "Must output valid HTML5"

    accessibility_score:
      constraints:
        - "Must use semantic HTML5 tags"
        - "Color contrast must meet WCAG AA standards"

blocks:
  theme.modern_professional:
    type: template
    description: "Clean, modern resume theme"
    path: "themes/modern-professional"  # ← Your code lives here
    template_engine: hbs
    test_data: "test-data/sample-resume.json"
    inputs:
      - name: resume
        type: entity.resume
    outputs:
      - name: html
        type: string
        measures: [valid_html, accessibility_score]
    domain_rules:
      - id: semantic_html
        description: "Must use semantic HTML tags (header, main, section, article)"
      - id: accessibility
        description: "Must include proper ARIA labels and semantic structure"
      - id: responsive_design
        description: "Must use CSS media queries for responsive layout"
      - id: visual_hierarchy
        description: "Must establish clear visual hierarchy with typography"

Flexible Project Structure: Notice the path: "themes/modern-professional" field. You can organize your code however you want! Blocks doesn't force you into a blocks/ folder. Just point to where your code lives.

Step 2: Create Your Theme Implementation

Your code can live anywhere. We'll put it in themes/modern-professional/:

themes/modern-professional/block.ts
import Handlebars from "handlebars";
import * as fs from "fs";
import * as path from "path";

/**
 * Domain compliance enforced at development time by Blocks validator.
 * Validator analyzes template.hbs source for semantic HTML, ARIA, responsiveness.
 */
export function modernProfessionalTheme(
  resume: Resume,
  config?: ThemeConfig
) {
  // Input validation only
  if (!resume.basics?.name || !resume.basics?.label) {
    throw new Error("Resume must include basics.name and basics.label");
  }

  // Render template (template is validated by Blocks, so we trust it)
  const templateSource = fs.readFileSync(
    path.join(__dirname, "template.hbs"),
    "utf-8"
  );
  const template = Handlebars.compile(templateSource);

  return {
    html: template(resume),
  };
}

interface Resume {
  basics: {
    name: string;
    label: string;
    email?: string;
    phone?: string;
    summary?: string;
  };
  work?: Array<any>;
  education?: Array<any>;
  skills?: Array<any>;
}

interface ThemeConfig {
  colors?: {
    primary?: string;
    background?: string;
  };
}
themes/modern-professional/template.hbs
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>{{basics.name}} - Resume</title>
  <style>
    /* Responsive layout */
    @media (max-width: 768px) {
      .container { padding: 1rem; }
    }

    /* WCAG AA compliant colors */
    body {
      color: #1a1a1a;
      background: #ffffff;
    }
  </style>
</head>
<body>
  <header role="banner">
    <h1>{{basics.name}}</h1>
    <p aria-label="Professional title">{{basics.label}}</p>
  </header>

  <main role="main">
    {{#if basics.summary}}
    <section aria-labelledby="summary-heading">
      <h2 id="summary-heading">Summary</h2>
      <p>{{basics.summary}}</p>
    </section>
    {{/if}}

    {{#if work}}
    <section aria-labelledby="experience-heading">
      <h2 id="experience-heading">Experience</h2>
      {{#each work}}
      <article>
        <h3>{{position}} at {{name}}</h3>
        <p>{{summary}}</p>
      </article>
      {{/each}}
    </section>
    {{/if}}
  </main>
</body>
</html>
themes/modern-professional/index.ts
export { modernProfessionalTheme } from "./block.js";

Step 3: Validate Your Theme

Run the Blocks validator:

blocks run theme.modern_professional

✅ When everything is correct:

🧱 Blocks Validator

📦 Validating: theme.modern_professional

  ✓ schema ok (inputs/outputs match spec)
  ✓ shape ok (files exist, exports present)
  ✓ domain ok (semantic HTML, ARIA labels, responsive)

  ✅ Block "theme.modern_professional" passed all validations

❌ When AI generates bad code:

Let's say an AI agent generates this broken template:

<!-- BAD: No semantic HTML, no ARIA, poor contrast -->
<div>
  <div style="color: #ccc; background: #ddd">{{basics.name}}</div>
  <div>{{basics.label}}</div>
</div>

Running blocks run theme.modern_professional:

🧱 Blocks Validator

📦 Validating: theme.modern_professional

  ✓ schema ok
  ✓ shape ok

  ⚠ [domain] Missing semantic HTML structure
  → Found: Generic <div> tags
  → Expected: <header>, <main>, <section>, <article>
  → Rule: semantic_html

  ⚠ [domain] Missing ARIA labels
  → No aria-label or aria-labelledby attributes found
  → Required for screen reader accessibility
  → Rule: accessibility

  ⚠ [domain] Poor color contrast detected
  → Text color #ccc on background #ddd
  → Contrast ratio: 1.8:1 (WCAG AA requires 4.5:1)
  → Rule: accessibility

  ❌ Block "theme.modern_professional" has 3 warnings

Humans Can Write Code Too!

CRITICAL: This example shows AI writing code, but humans can freely edit any file:

  • ✅ You can manually edit template.hbs
  • ✅ You can manually edit block.ts
  • ✅ Blocks detects drift regardless of who wrote the code
  • ✅ Validation helps you decide: fix code or update spec

The workflow is the same:

  1. Human or AI writes code
  2. Run blocks run theme.modern_professional
  3. See drift report
  4. Decide what to fix (code or spec)
  5. Re-validate until consistent

Example: Working with an AI Assistant

When you're coding with Claude Code (or Cursor, Copilot, etc.), here's how the workflow looks. Note: This same workflow applies when humans write code!

1. Someone Writes Code

You or an AI: "Create a modern resume theme with professional styling"

Code gets written (by human or AI agent).

2. Run Validation

After writing or modifying code, run validation:

blocks run theme.modern_professional

3. Review Drift Report

⚠ [domain] Poor color contrast detected
→ Text color #e0e0e0 on background #ffffff
→ Contrast ratio: 1.4:1 (WCAG AA requires 4.5:1)

4. Fix the Issue

Read the error, understand the domain rule, and fix it (human or AI can do this):

- color: #e0e0e0;
+ color: #1a1a1a; /* WCAG AA compliant */

5. Re-Validate

blocks run theme.modern_professional
✅ Block "theme.modern_professional" passed all validations

This feedback loop is the same for humans and AI - iterative refinement guided by your domain spec, catching drift and helping maintain consistency.

Output Validators: Future Vision

In the current version, Blocks validates source code. Future versions will add output validators that render and analyze the actual HTML output.

Example: Computer Vision for Color Contrast

validators:
  output:
    - id: color_contrast_cv
      run: "output.vision.contrast.v1"
      config:
        model: "gpt-4o-vision"  # or Claude with vision
        test_data: "test-data/sample-resume.json"

This validator would:

  1. Render output.html from your theme
  2. Screenshot the page
  3. Use a vision model to analyze:
    • Color contrast ratios
    • Text readability
    • Visual hierarchy
    • Responsive breakpoints

Why this is powerful:

  • Catches issues that are hard to detect in source (like computed CSS styles)
  • Validates the actual user experience
  • Works even with complex CSS frameworks

Understanding the Validators

Schema Validator (Fast, Deterministic)

  • ✅ Input types match blocks.yml
  • ✅ Output types match specification
  • ✅ Required fields present

Shape Validator (Fast, Deterministic)

  • ✅ File structure correct (block.ts, index.ts, template.hbs)
  • ✅ Exports present and named correctly

Domain Validator (Slow, AI-Powered)

  • Reads ALL files in your block (not just block.ts)
  • ✅ Analyzes template source for semantic HTML
  • ✅ Checks domain rules compliance
  • ✅ Validates against philosophy statements
  • ✅ Detects undocumented concepts (drift detection)

Key insight: The domain validator reads template.hbs source, not rendered output. It analyzes the code with AI to ensure semantic correctness.

What If I Don't Use a blocks/ Folder?

You don't have to! Blocks respects your project structure:

blocks:
  theme.modern_professional:
    path: "themes/modern-professional"  # ← Custom path

  theme.creative:
    path: "src/themes/creative"  # ← Anywhere you want

  utils.format_date:
    path: "lib/utilities/date-formatter"  # ← Whatever structure

If you don't specify path, Blocks defaults to blocks/<block-name>/ (or whatever you set in targets.discover.root).

Next Steps