README

Structural layout validator for HTML generated by other skills. Runs the 8-rule Box Layout Theory against any HTML file and returns a JSON report with a score, violations, and natural-language fix instructions the calling agent can act on.

Used by content-factory, codi-brand, and any other skill that produces fixed-aspect-ratio HTML (social posts, slides, documents, posters).

Usage from another skill

node ~/.claude/skills/codi-box-validator/scripts/validate.mjs \
  --input /path/to/design.html \
  --width 1080 --height 1350 \
  --threshold 0.85

Output is JSON on stdout. Exit code: 0 valid, 1 invalid, 2 error.

First-run setup

Playwright + chromium install on first use:

bash ~/.claude/skills/codi-box-validator/scripts/setup.sh

Idempotent — skip if already installed.

Rules

#RuleDetects
1Boxes OnlyText in non-leaf nodes
2Full CoverageDead space in parent containers
3Shared DimensionSibling width/height mismatches
4Uniform Spacingpadding ≠ gap, asymmetric padding
5RecursiveApplied at every depth
6Leaf RuleClassifier — exempts leaves from box checks
7No Empty NodesParent-less, content-less divs
8Sibling ConsistencyStructurally equivalent siblings with mismatched children
9Leaf AtomicityCompound leaves mixing icons and text (promote to parent)
10Content FitLeaf content overflowing its allocated flex box

See references/rules-detailed.md for examples per rule.


SKILL.md

Purpose

Every time you generate HTML for a fixed-aspect-ratio layout (social post, slide, document page, poster, card), you MUST validate it against the 8 rules of Box Layout Theory before delivering to the user. This skill gives you a CLI that renders the HTML with Playwright, walks the DOM, and returns a JSON report with a score and actionable fix instructions. You read the report and iterate.

The validator runs in your own bash environment. No API, no server, no user involvement. You generate → save → validate → fix → deliver, all in one turn.

Skip When

  • Pure prose output (articles, blog posts, READMEs) — no fixed-aspect layout
  • Standalone chart or plot without a surrounding card or slide layout
  • Exporting an existing PDF or image, not generating new HTML
  • CLI or terminal output — no HTML layer at all

The 10 Rules (working memory)

#RuleWhat it enforces
1BOXES ONLYLayout is nested boxes. Text lives only in leaf elements.
2FULL COVERAGEChildren + spacing fill the parent completely. No dead space.
3SHARED DIMENSIONColumn siblings share width. Row siblings share height.
4UNIFORM SPACINGpadding = gap at every level. All four padding sides equal.
5RECURSIVERules 2–4 apply at every depth until leaves.
6LEAF RULENodes with no children are leaves. Exempt from box validation.
7NO EMPTY NODESEvery node is either a parent (has children) or a leaf (has content).
8SIBLING CONSISTENCYStructurally equivalent siblings must have matching child dimensions and positions.
9LEAF ATOMICITYA leaf holds exactly ONE atom (one text run OR one icon). Never a mix.
10CONTENT FITA leaf’s rendered content (scrollWidth/scrollHeight) must fit its allocated box (clientWidth/clientHeight). No overflow.

For deep examples per rule, read ${CLAUDE_SKILL_DIR}[[/references/rules-detailed.md]].

Rule 9 in practice — the most common mistake

If you write <div class="delta">↑ 34.2% vs Q3</div>, the leaf packs TWO atoms (an arrow icon and a text run) into one box. You lose independent control over color, size, spacing, and alignment between them, and Rule 4’s uniform-spacing rhythm cannot be applied between atoms. The validator will flag this.

Wrong:

<div class="delta">↑ 34.2% vs Q3</div>

Right:

<div class="delta">
  <div class="arrow">↑</div>
  <div class="value">34.2% vs Q3</div>
</div>

Same for timestamps like 2026-04-14 · 14:20 UTC (3 atoms — date, bullet, time), checkmarks like ✓ Done (icon + text), and navigation like A → B (text + icon + text).

Not flagged (single atoms): $12.4M, +15%, Terms & Conditions, Hello world — currency, punctuation, and plain text never count as icons.

Rule 10 in practice — the font-vs-slot trap

R2 (full coverage) verifies parents’ math, but it cannot see inside a leaf. A leaf styled font-size: 120px allocated 40px of flex height will render text glyphs that spill past the leaf’s rect and overlap whatever sits below. The layout looks correct to flex but wrong to the eye. R10 catches this by comparing scrollHeight/scrollWidth (content natural size) against clientHeight/clientWidth (box size).

Common causes and fixes:

OverflowCauseFix
VerticalFont too big for flex slotShrink font, OR raise the leaf’s flex ratio, OR reduce siblings
Horizontalwhite-space: nowrap on long textRemove nowrap, OR shrink font, OR widen the leaf
BothParent is tiny and content is largeRestructure — content needs a bigger home

Rule 10 is an error, not a warning. Visual overflow = broken layout.


Canonical CSS pattern

Every non-leaf box uses this pattern at every depth. S is the spacing value (same integer px at a given depth):

.box {
  display: flex;
  flex-direction: column; /* or row */
  padding: Spx;
  gap: Spx;               /* MUST equal padding */
  box-sizing: border-box;
}
.box > * {
  flex: 1;                /* ensures full coverage */
  min-width: 0;
  min-height: 0;
}

Leaves hold text. Non-leaves hold only other boxes.


Workflow

Run this loop for every HTML layout you generate. The user sees only the final validated result.

1. Read the rules above. If a violation is unclear, read references/rules-detailed.md.

2. Determine dimensions.

Either the user specifies, or infer from context. For preset formats: ${CLAUDE_SKILL_DIR}[[/references/aspect-ratios.md]]

Common presets:

  • Instagram Post (4:5): 1080×1350
  • Instagram Square: 1080×1080
  • Slide Deck (16:9): 1920×1080
  • A4 Portrait: 794×1123

3. Generate the HTML.

Start from a template if helpful: ${CLAUDE_SKILL_DIR}[[/assets/templates/]] Save to an absolute path, e.g. /tmp/design.html.

4. Ensure Playwright is installed (first run only).

bash ${CLAUDE_SKILL_DIR}[[/scripts/setup.sh]]

Idempotent. Skips install if already present. Takes ~30s on first run.

5. Run the validator.

node ${CLAUDE_SKILL_DIR}[[/scripts/validate.mjs]] \
  --input /tmp/design.html \
  --width 1080 --height 1350 \
  --threshold 0.85

Flags:

FlagDefaultMeaning
--inputrequiredAbsolute path to the HTML file
--widthrequiredViewport width in pixels
--heightrequiredViewport height in pixels
--toleranceauto-scaledOverride the computed tolerance (default scales with canvas: max(2, minDim × 0.002))
--threshold0.85Minimum score to be considered valid
--presetSeverity preset: strict or lenient. Default is balanced.
--prettyoffHuman-readable output instead of JSON

All other thresholds are derived automatically from canvas dimensions and tree complexity. The validator scales from 300×600 mobile stories up through 3840×2160 4K slides without retuning. See ${CLAUDE_SKILL_DIR}[[/references/rules-detailed.md]] for the scaling model.

Exit codes: 0 valid, 1 invalid, 2 error.

6. Read the JSON report.

{
  "valid": false,
  "score": 0.72,
  "threshold": 0.85,
  "summary": { "totalNodes": 37, "errors": 3, "warnings": 2 },
  "violations": [
    {
      "rule": "R4",
      "severity": "error",
      "path": "root > card-01 > content-col",
      "message": "padding (12px) ≠ gap (8px)",
      "fix": "Set padding and gap to the same value (10px)"
    }
  ],
  "fixInstructions": "Three boxes have padding-gap mismatches. Unify padding and gap in card-01 content-col, card-02 content-col, and the footer row."
}

7. Decide.

  • If valid: true → deliver the HTML. Briefly mention the score to the user (e.g. “Layout validated — score 0.94, 0 errors, 1 minor warning”).
  • If valid: false → read fixInstructions and each violation’s fix field. Modify the HTML. Go back to step 5.
  • Max 4 iterations. If still failing, deliver the best-scoring attempt and surface the remaining warnings to the user.

8. Never show the raw JSON to the user unless they explicitly ask. Summarize.


Rule 8 — Sibling Consistency (special handling)

Rule 8 compares structurally equivalent sibling boxes. Two siblings are “structurally equivalent” when they have the same orientation and child count. The validator auto-detects these groups and compares child dimensions across group members.

To make grouping explicit and catch more cases, tag sibling parents with data-box-group="name":

<div class="card" data-box-group="card">...</div>
<div class="card" data-box-group="card">...</div>
<div class="card" data-box-group="card">...</div>

All three cards will be validated as one group. Their children at each index must have matching widths, heights, and relative positions.


Composability

This skill validates STRUCTURE, not aesthetics. It pairs with:

  • Brand skills (codi-brand, brand-creator) for colors and typography
  • frontend-design for aesthetic guidelines
  • content-factory for HTML generation (slides, documents, business deliverables)

Run this validator AFTER applying visual styling from those skills.


References

  • ${CLAUDE_SKILL_DIR}[[/references/rules-detailed.md]] — deep examples per rule
  • ${CLAUDE_SKILL_DIR}[[/references/aspect-ratios.md]] — format presets with dimensions
  • ${CLAUDE_SKILL_DIR}[[/references/troubleshooting.md]] — common violations mapped to CSS fixes
  • ${CLAUDE_SKILL_DIR}[[/assets/templates/]] — starter HTML layouts that pass validation