Core Concepts
Understanding the key concepts behind uiMatch.
Overview
uiMatch compares Figma designs with actual implementations by:
- Fetching the design from Figma
- Resolving the implementation element using selectors
- Comparing pixels and reporting differences
Three key concepts make this work: Anchors, Quality Gates, and Content Basis.
Anchors
Anchors are plugins that resolve CSS selectors to actual DOM elements. They provide flexible ways to target elements in your implementation.
Why Anchors?
Different frameworks and testing tools use different selector strategies:
- Playwright uses
data-testid - Testing Library uses
getByRole/getByLabelText - Custom component libraries may use proprietary selectors
Anchors let you plug in the selector strategy that matches your project.
Built-in Anchors
uiMatch includes a default CSS selector anchor:
npx @uimatch/cli compare \
figma=abc123:1-2 \
story=http://localhost:3000 \
selector="#my-component"
Custom Anchor Plugins
Create your own anchor to match your testing strategy:
import type { SelectorResolverPlugin } from '@uimatch/selector-spi';
export const testIdAnchor: SelectorResolverPlugin = {
name: 'test-id-anchor',
version: '1.0.0',
async resolve(context) {
const { initialSelector, probe } = context;
// Transform selector to use data-testid attribute
const selector = `[data-testid="${initialSelector}"]`;
// Optionally verify the selector is valid
const probeResult = await probe.check(selector);
if (!probeResult.isValid) {
return {
selector: initialSelector, // fallback to original
reasons: ['data-testid selector not found, using original'],
stabilityScore: 50,
};
}
return {
selector,
reasons: ['Resolved via data-testid anchor'],
stabilityScore: 80,
};
},
};
Then use it:
npx @uimatch/cli compare \
figma=abc123:1-2 \
story=http://localhost:3000 \
selector=my-button \
selectorsPlugin=@my-company/uimatch-test-id-plugin
See Plugins for complete plugin development guide.
Quality Gates
Quality Gates define what "matching" means for your comparisons. They enforce consistency standards.
Quality Gate Profiles
uiMatch uses profiles to manage thresholds:
profile=component/strict # Pixel-perfect comparison
profile=component/dev # Relaxed for development
profile=page-vs-component # Accounts for padding
profile=lenient # Very relaxed for prototyping
How it works:
- Each profile has predefined thresholds for pixel differences and color variations
- Profiles include
pixelDiffRatio(acceptable pixel difference ratio) anddeltaE(color difference threshold) - Fails if comparison exceeds profile thresholds
Choosing a Profile
| Profile | pixelDiffRatio | deltaE | Use Case |
|---|---|---|---|
component/strict | 0.01 (1%) | 3.0 | Pixel-perfect - Design system components |
component/dev | 0.08 (8%) | 5.0 | Development - Iterative work |
page-vs-component | 0.12 (12%) | 5.0 | Padded - Letterboxed comparisons |
page/text-doc | 0.20 (20%) | 6.0 | Text-heavy - Terms, privacy, docs |
lenient | 0.15 (15%) | 8.0 | Prototyping - Early drafts |
Important Note on
component/strict:The
component/strictprofile is intentionally very strict. Even with a visually "perfect" implementation, font rendering differences and anti-aliasing can easily produce 2–3% pixel differences. In practice:
- For day-to-day CI: Use
component/devorlenientprofiles- For
component/strict: Reserve for design-system components in controlled environments with fixed fonts and consistent rendering stacks- Expected behavior: DFS scores of 95-99 with 2-3% pixelDiffRatio are normal for high-quality implementations due to rendering variations
Advanced Features
Current implementation supports:
- Pixel difference ratio - Percentage of acceptable pixel differences
- Color difference (deltaE) - Perceptual color difference threshold
- Layout issue detection - Flags high-severity layout problems
- Automatic re-evaluation - Smart re-checking with adjusted content basis
- Flexible area gap handling - Area gap violations are downgraded to warnings when other metrics (pixelDiff, colorDeltaE, styleCoverage) are within thresholds, allowing comparisons to pass despite dimension mismatches
Note: For detailed information on area gap handling behavior, see Area Gap Handling in v0.2.0.
Size Handling
Size Mode Options
Control size matching behavior:
size=strict # Sizes must match exactly (default)
size=pad # Pad smaller image with letterboxing
size=crop # Compare common area only
size=scale # Scale implementation to Figma size
Example:
npx @uimatch/cli compare \
figma=abc123:1-2 \
story=http://localhost:3000 \
selector="#responsive-card" \
size=pad \
contentBasis=intersection
This pads the smaller image and uses intersection for content-only comparison (excludes padding noise).
Content Basis
Control which area to use for calculating pixel difference ratio denominator:
contentBasis=union # Union of both content areas (default)
contentBasis=intersection # Intersection (recommended for pad mode)
contentBasis=figma # Use Figma's content area only
contentBasis=impl # Use implementation's content area only
Best Practice: Use intersection with size=pad to exclude letterboxing from pixel difference metrics.
Architecture Overview
uiMatch follows a modular pipeline architecture with three main stages: input acquisition, comparison, and output generation.
High-Level Architecture
flowchart TD
FIGMA["Figma design<br/>API, MCP, or bypass"] -->|PNG| CORE["@uimatch/core<br/>size handling, pixel and style diff, quality gate"]
IMPL["Implementation<br/>Storybook or URL"] -->|"Playwright screenshot + CSS"| CORE
CORE --> REPORT["DFS score + reports<br/>Pass or fail"]
REPORT --> CI["CI/CD integration"]
Key Components
@uimatch/cli- CLI entry point and user interface@uimatch/core- Internal comparison engine bundled into@uimatch/cli@uimatch/selector-anchors- Optional AST-based selector plugin@uimatch/scoring- Internal Design Fidelity Score calculator bundled into@uimatch/cli
Input Acquisition Modes
Figma (3 modes):
- API - Direct Figma API access with
FIGMA_ACCESS_TOKEN - MCP - Figma MCP server integration for enhanced workflows
- Bypass - Local PNG files for offline/CI-cached scenarios
Implementation:
- Playwright browser automation
- Screenshot capture + computed CSS extraction
- DPR/viewport configuration support
Comparison Workflow
Step-by-step comparison process:
graph LR
A[Figma Design] --> B[Fetch PNG]
C[Implementation URL] --> D[Navigate Browser]
D --> E[Resolve Selector with Anchor]
E --> F[Capture Screenshot]
B --> G[Compare Pixels]
F --> G
G --> H[Apply Quality Gates]
H --> I[Pass/Fail + Diff Image]
- Fetch Figma design as PNG
- Navigate to implementation URL
- Resolve selector using anchor plugin
- Capture screenshot
- Compare pixels
- Apply quality gates (threshold check)
- Report results with diff visualization
Best Practices
1. Start Broad, Refine Later
# Initial setup - lenient profile
profile=lenient
# After stabilization - tighter profile
profile=component/strict
2. Use Meaningful Selectors
# ✅ Good: Semantic, stable selector
selector="[data-testid='checkout-button']"
# ❌ Bad: Fragile, implementation-dependent
selector="div.container > div:nth-child(3) > button"
3. Group Related Comparisons
Create suite files for logical groupings:
{
"name": "Authentication Flow",
"defaults": {
"profile": "component/dev"
},
"items": [
{ "name": "Login Form", "figma": "...", "story": "...", "selector": "#login" },
{ "name": "Signup Form", "figma": "...", "story": "...", "selector": "#signup" },
{ "name": "Password Reset", "figma": "...", "story": "...", "selector": "#reset" }
]
}
4. Version Control Your Baselines
Commit your Figma references and suite files:
tests/
visual-regression/
suite.json
baseline-screenshots/
Next Steps
- CLI Reference - Learn all available options
- Troubleshooting - Debug common issues
- Plugins - Build custom anchor plugins
Scoring and Threshold Layers
Understanding how uiMatch processes comparisons through multiple layers:
Layer 1: Comparison Thresholds
Controls how raw pixel and style differences are interpreted:
| Setting | Purpose | Default | Configuration |
|---|---|---|---|
pixelmatchThreshold | Pixelmatch sensitivity (0=strict, 1=lenient) | 0.1 | .uimatchrc.json |
colorDeltaEThreshold | StyleDiff significance and SFS normalization | 3.0 | .uimatchrc.json |
Purpose: Fine-tune what constitutes a "difference" in pixel/color comparison.
Layer 2: Acceptance Thresholds (Quality Gate)
Controls pass/fail decision for the overall comparison:
| Setting | Purpose | Default | Configuration |
|---|---|---|---|
acceptancePixelDiffRatio | Max pixel difference ratio (0-1) | 0.01 (1%) | Profiles or .uimatchrc.json |
acceptanceColorDeltaE | Max average color ΔE | 3.0 | Profiles or .uimatchrc.json |
areaGapCritical | Area gap that causes an immediate failure | 0.15 (15%) | .uimatchrc.json |
areaGapWarning | Area gap warning threshold | 0.05 (5%) | .uimatchrc.json |
Purpose: Define pass/fail criteria based on detected differences.
Although the two color thresholds share a default, they are separate settings.
A quality profile supplies one deltaE value and intentionally overrides both
for that run.
Layer 3: Scoring (Design Fidelity Score)
Numerical score (0-100) combining multiple factors:
- Pixel accuracy (from Layer 1 detections)
- Style accuracy (color, spacing, typography)
- Layout accuracy (dimension, positioning)
- Quality gate status (from Layer 2)
Output: DFS score shown in comparison results.
Processing Workflow
Detection (Layer 1) → Differences detected with thresholds
↓
Acceptance (Layer 2) → Pass/fail based on acceptance thresholds
↓
Scoring (Layer 3) → DFS score calculated (0-100)
Example:
- Layer 1 detects 100 different pixels with
pixelmatchThreshold=0.1 - Layer 2 checks if
pixelDiffRatio < acceptancePixelDiffRatio(0.01) - Layer 3 calculates DFS combining all factors
Text Matching
While uiMatch primarily performs pixel-based comparison, text fidelity is equally important for validating design-implementation alignment. Text matching helps identify subtle differences that may not be visible in pixel comparison alone.
Why Text Matching?
Text differences can occur due to:
- Typography variations - Different fonts or font rendering
- Whitespace handling - Extra spaces, tabs, or line breaks
- Case sensitivity - Uppercase vs lowercase differences
- Unicode variations - Full-width vs half-width characters (e.g.,
123vs123) - Localization - Translation differences or missing translations
Text Matching Capabilities
uiMatch provides text comparison through the compareText utility and text-diff CLI command.
Four Classification Levels
Text differences are classified into four categories:
| Classification | Description | Example |
|---|---|---|
exact-match | Texts are identical without modification | "Login" = "Login" |
whitespace-or-case-only | Only whitespace, case, or Unicode normalization differs | "Sign in" ≈ "SIGN IN" |
normalized-match | Similarity is at or above the selected threshold | "Save changes now" vs "Save changes later" at 0.6 |
mismatch | Similarity is below the selected threshold | The same pair at 0.7 |
Normalization Process
Text comparison applies multi-level normalization:
- NFKC Unicode normalization - Converts full-width characters to half-width equivalents
- Example:
"Button123"→"Button123"
- Example:
- Whitespace collapsing - Collapses consecutive whitespace into single space
- Example:
"Sign in"→"Sign in"
- Example:
- Trim - Removes leading/trailing whitespace
- Example:
" Login "→"Login"
- Example:
- Case normalization - Converts to lowercase (unless case-sensitive mode)
- Example:
"SUBMIT"→"submit"
- Example:
Similarity Scoring
For texts that don't match exactly after normalization, similarity is calculated using token overlap and character position matching.
- Range: 0.0 (completely different) to 1.0 (identical)
- Default threshold: 0.9 (90% similarity)
- Configurable: Adjust threshold based on use case
Use Cases
Design Label Validation
Compare text labels from Figma designs with implementation:
npx @uimatch/cli text-diff "Sign in" "SIGN IN"
# → whitespace-or-case-only (minor formatting difference)
Threshold Behavior
Apply a lower threshold when a known wording change should remain acceptable:
npx @uimatch/cli text-diff "Save changes now" "Save changes later" --threshold=0.6
# → normalized-match
Typography Debugging
Identify subtle text differences affecting rendering:
npx @uimatch/cli text-diff "Button123" "Button123"
# → whitespace-or-case-only (full-width digits normalized)
Integration with Pixel Comparison
Text matching complements pixel-based comparison by:
- Identifying content differences - Detect text changes even when visual rendering is similar
- Debugging typography issues - Pinpoint exact text differences causing visual discrepancies
- Supporting automation - Enable programmatic validation of text content
- Enhancing quality gates - Add text fidelity to comparison criteria
@uimatch/core is an internal package and is not a supported public API. Use the
text-diff command for text comparison.
Advanced Topics
For more details, see the API Reference (available in the navigation menu - auto-generated from TypeScript types).