| name | ui-style-consistency |
| description | Use when auditing a frontend codebase for duplicated UI components, inconsistent design-system usage, or hand-rolled variants of existing primitives. Triggers on "find duplicated components", "audit design system consistency", "find UI drift", or reviewing a component library for gaps. |
| license | CC-BY-NC-4.0 |
UI Unification Audit
Overview
Systematically find UI components and layouts that were intended to be unified but diverged. The core distinction: necessary layout variants (justified by different data/interaction needs) vs actual mismatches (shared primitive exists but is bypassed or duplicated).
When to Use
- Codebase has a
components/ui/ or design-system layer but pages still hand-roll equivalent markup
- Multiple files import a shared component AND re-declare its base styles
- Components live under a domain folder but are imported cross-domain
- You see repeated style/class clusters across route files (cards, badges, buttons, empty states)
- Before a design-system refactor to identify highest-impact targets
Not for: Greenfield projects without existing primitives, pure CSS audits, accessibility-only reviews.
Core Pattern
1. INVENTORY โ Map shared primitives (components/ui/*, CSS utilities, global classes)
2. SEARCH โ Find all usages AND hand-rolled equivalents across routes/components
3. CLASSIFY โ For each candidate: necessary variant or actual mismatch?
4. RANK โ By confidence (primitive exists + bypassed = highest) and blast radius
Quick Reference
| Signal | Likely Mismatch | Likely Justified Variant |
|---|
| File imports primitive AND re-declares its base classes | โ
Mismatch | |
| Component in domain folder imported by other domains | โ
Misplaced | |
| Decorative wrapper markup rendered manually when a wrapper primitive exists | โ
Bypass | |
| Detail page header is richer than list page header | | โ
Needs variant |
| Loading skeleton has different grid columns per page | | โ
Geometry is page-specific |
| Empty state uses smaller padding in inline context | | โ
Needs size variant |
| Selectable card in picker looks like button but isn't | | โ
Different interaction model |
| Two components share 80%+ markup but differ in slots/regions | โ
Extract shell | |
Implementation
All commands use <...> placeholders. Adapt to your project:
| Placeholder | Description | Examples |
|---|
<ui-dir> | Design-system component directory | components/ui/, src/lib/components/, src/shared/ |
<src-dirs> | Directories with pages/routes/features | app/ pages/, src/pages/, src/routes/ |
<component-glob> | Component file extension glob | *.tsx, *.vue, *.svelte, *.component.ts |
<import-prefix> | Import path to UI primitives | @/components/ui/, ~/components/ui/, $lib/components/ |
Phase 1: Inventory Shared Primitives
ls <ui-dir>
rg "<css-pattern>" <global-css-file>
rg "<export-pattern>" <ui-dir> -g '<component-glob>'
Phase 2: Search for Bypasses and Duplicates
Run all searches in parallel for maximum throughput.
A. Imports of each primitive โ Identifies consumers. Files that import a primitive AND re-declare its styles are the strongest mismatch signal.
rg "from \"<import-prefix>" <src-dirs> -g '<component-glob>'
B. Hand-rolled equivalents โ For each primitive identified in Phase 1, extract its signature class/style cluster (the 3-5 most distinctive CSS properties, utility classes, or styled definitions that define its visual identity) and grep for those patterns. Files that render these clusters WITHOUT importing the primitive are bypasses.
How to construct the grep for each primitive:
- Read the primitive's source to identify its core visual classes (layout, border, typography, spacing)
- Build a regex that matches the key classes in proximity, using
.* between tokens to allow ordering variation
- Exclude the primitive's own file from results
- Also search for the primitive's CSS-in-JS equivalent if the codebase mixes approaches (e.g.,
makeStyles, styled.div, sx={{)
rg "<key-class-1>.*<key-class-2>.*<key-class-3>" <src-dirs> -g '<component-glob>' -g '!<ui-dir>/*'
C. Structural bypasses via AST โ Use ast_grep_search to find raw HTML elements that map to existing primitives. For each primitive, derive the HTML tag it renders and search for raw instances of that tag in page/component code.
# Templates โ adapt per primitive:
pattern: <button $$$>$BODY</button> # Potential button primitive bypasses
pattern: <dialog $$$>$BODY</dialog> # Potential modal primitive bypasses
pattern: <table $$$>$BODY</table> # Potential table primitive bypasses
pattern: <input $$$> # Potential input primitive bypasses
D. Cross-domain imports โ Find components in domain/feature folders that are imported by other domains (misplaced shared components).
rg "from \"<import-root>/<domain>/components/" <src-dirs> -g '<component-glob>' --files-with-matches
Fuzzy match policy: Non-exact matches are included, not ignored. If a pattern is close but not identical to a known primitive (e.g., a card with slightly different padding, a button missing one hover variant) and the signal is strong that it was intended to use the existing component, include it in the candidate list. Err on the side of inclusion. If you cannot determine whether a candidate is a real divergence or an intentional difference, flag it and ask the user before dropping it.
Phase 3: Classify Each Candidate
For each divergence found, ask:
- Does a shared primitive already exist for this role? If yes โ likely mismatch.
- Does the file already import the primitive? If yes โ strong mismatch signal.
- Is the divergence in styling or in structure/behavior? Styling-only โ mismatch. Different slots/interactions โ may need variant.
- Is the component in the wrong directory? Domain component imported cross-domain โ misplaced.
- Would adding a variant parameter/prop to the existing primitive cover this? If yes โ mismatch (missing variant). If no โ may be justified separate component.
Phase 4: Rank and Report
Order by:
- Primitive exists + bypassed (highest confidence)
- Primitive exists + needs variant (high confidence)
- No primitive exists + pattern repeated 3+ times (medium confidence)
- Architectural misplacement (cross-domain imports)
Common Mistakes
- Treating all visual differences as defects. A detail page header being richer than a list page header is not a bug โ it needs a variant, not flattening.
- Ignoring unused props/parameters on existing primitives. Parameters like
color/size defined but never wired up indicate abandoned unification intent.
- Only searching imports. The worst mismatches are files that DON'T import the primitive โ they hand-roll it entirely.
- Conflating loading skeletons with real components. Skeleton geometry is page-specific by nature; only the primitive toolkit location and bracket/overlay reuse are audit targets.
- Reporting without classification. A flat list of "these look similar" is not actionable. Always classify: mismatch vs justified variant vs needs-new-primitive.