09-architecture

All docs

Architecture Deep Dive

This page covers the internal design decisions and algorithms behind AutoSkeleton.

Leaf-Only Replacement

AutoSkeleton follows a leaf-only replacement strategy:

This means the skeleton inherits the exact same layout as the real content — same gaps, same alignment, same grid structure.

What counts as a "leaf"?

An element is skeletonized as a leaf if any of these are true:

An element is kept as a container if:


Wrapper + Content Pattern

Each skeleton block is wrapped in an outer element that preserves spacing:

 Outer Wrapper   margin, padding, flex props        Inner Skeleton        width, height, color            borderRadius, animation          

The outer wrapper carries:

The inner skeleton carries:

This separation ensures skeletons maintain exact spacing without the inner block affecting layout.


Score-Based Role Inference

Each element is scored across multiple roles. The highest score wins.

Scoring Table

Text

SignalPoints
Has non-empty text content+40
Height between minTextHeight and minTextHeight * 3+30
Tag is P, SPAN, H1-H6, LABEL, A, DIV+20
Font size between 0-100px+20

Image

SignalPoints
Tag is IMG+100
Has role="img" attribute+60
Has CSS background-image (not none)+50
Area > minImageSize^2 (default 1024px^2)+30

Icon

SignalPoints
Tag is SVG+70
Area < iconMaxSize^2 AND aspect ratio 0.8-1.2 (square-ish)+40

Button

SignalPoints
Tag is BUTTON+80
Has role="button" attribute+60
Has cursor: pointer AND area < 20,000px^2+30

Input

SignalPoints
Tag is INPUT, TEXTAREA, or SELECT+80
Has contenteditable attribute+50

Skip (spacer elements)

SignalPoints
Area < 100px^2+50
Height < 5px OR width < 5px+40

Selection Logic

  1. Each role's signals are summed
  2. The highest-scoring role wins
  3. Minimum threshold: 30 points — below this, defaults to text
  4. Container role is handled separately (not part of scoring)

Multi-Line Text Detection

AutoSkeleton detects multi-line text blocks and renders them as multiple skeleton bars:

Algorithm

  1. Get lineHeight from computed styles
  2. If lineHeight is 'normal', calculate as fontSize * 1.2
  3. If height > lineHeight: lines = Math.ceil(height / lineHeight)
  4. Otherwise: lines = 1

Rendering

   <- line 1 (100% width)   <- line 2 (100% width)             <- line 3 (70% width)

Table Structure Preservation

Tables are handled specially to maintain their grid structure:

Structural Tags (never skeletonized)

TABLE, THEAD, TBODY, TFOOT, TR

These are rendered as actual HTML table elements in the skeleton. Their children are processed recursively.

Cell Tags (cell preserved, content skeletonized)

TH, TD

The cell element is preserved with its styles (padding, borders, background). The cell's children are skeletonized as leaf elements.

Why not just replace the whole table?

A single skeleton block can't represent column widths, header styles, or row counts. By preserving the table structure, the skeleton accurately shows:


Overlay Architecture

The AutoSkeleton component renders three layers:

tsx
<div style={{ position: 'relative' }}>  {/* Layer 1: Content (always rendered) */}  <div className="auto-skeleton-content">    {children}  </div>  {/* Layer 2: Skeleton overlay (absolute positioned) */}  <div style={{ position: 'absolute', top: 0, left: 0, right: 0 }}>    <SkeletonRenderer blueprint={blueprint} />  </div>  {/* Layer 3: Measurement container (invisible) */}  <div style={{ opacity: 0, position: 'absolute', pointerEvents: 'none' }}>    {children}  </div></div>

Layer 1 (Content): Always in the DOM. Hidden via visibility: hidden during loading. This prevents layout shifts when content appears.

Layer 2 (Skeleton): Positioned absolutely on top. Fades out over 300ms when loading ends. Unmounted after fade.

Layer 3 (Measurement): A hidden copy of children used for DOM measurement. Only present during loading. Completely invisible (opacity: 0).

Why this approach?


Image Handling

Images with empty or unloaded src attributes get special treatment:

  1. Detect empty/loading state: src is empty, naturalWidth === 0, or src resolves to the current page URL
  2. Calculate intended dimensions from:
  1. Use calculated dimensions instead of measured dimensions

This ensures image skeletons have the correct size even before the image loads.