Skip to content
UI Craft / docs

ui-craft-detect

Zero-dependency static scanner for AI-generated UI anti-patterns. Rules mirror the Anti-Slop Test in the skill. Usable as a CI gate.

Updated 2026-04-18

ui-craft-detect is a standalone CLI. It scans a codebase for the same anti-patterns the skill rejects when generating UI — transition: all, bounce easing, purple/cyan gradients, ALL CAPS headings, emoji-as-icons, generic CTAs, glassmorphism stacks, and 12 more. Zero dependencies. One file. CI-ready.

Install nothing. Run it anywhere:

npx ui-craft-detect ./src

Exit code is 0 when clean, 1 when findings.

Install

# one-off
npx ui-craft-detect ./src

# add as a project dev dep
pnpm add -D ui-craft-detect

Published on npm: ui-craft-detect.

Or from a clone of the main repo:

node scripts/detect.mjs ./src

Rules

29 rules grouped by severity (v0.4.0). Rules fire per line (per-line) or per file (file-level).

Critical

IDDetects
transition-alltransition: all — animates unknown properties, causes paint thrash
bounce-elastic-easingeaseInOutBack, easeOutBounce, elastic cubic-béziers — instant AI-slop tell
animate-bounceTailwind’s animate-bounce utility
purple-cyan-gradientPurple-to-cyan, violet-to-pink, indigo-to-pink gradients
uppercase-headingALL CAPS h1/h2/h3 — hard to read, aggressive by default
left-top-animationAnimating left / top / width / height — layout thrash, never compositor
glassmorphism-stack (file)backdrop-filter + rgba(white, low) + border-white together
a11y/modal-without-dialogCustom div modals when native <dialog> or [popover] fits — skips files importing Radix / HeadlessUI / Ariakit / etc.
forms/placeholder-as-labelInputs with a placeholder but no associated <label>
a11y/outline-none-no-replacementoutline: none without a :focus-visible replacement

Major

IDDetects
gradient-text-metricGradient fill on large numerals — unreadable, AI tell
emoji-feature-iconEmoji used as a feature/section icon
pure-black-text#000, rgb(0,0,0), oklch(0% ...) body text — never in real UI
generic-cta”Learn more”, “Click here”, “Read more” as CTA labels
absolute-zindexNuclear z-index values (9999, 99999, 1000000+)
setTimeout-animationsetTimeout driving what should be a CSS or RAF animation
aria-label-emojiEmoji inside aria-label — screen readers read them literally
no-focus-visibleHover state defined without :focus-visible counterpart
pixel-radius-inconsistencyToken-based border-radius mixed with raw pixel values in one file
uniform-border-radius (file)Identical radius on every component — no hierarchy
tables/no-overflow-handlingTables without horizontal overflow or sticky thead (up to 2 findings per file)

Warn

IDDetects
inline-any-styleLong inline style attribute — should be a class or token
unit-mixingMixed length units (px + rem + em) in the same block

File-level rules (glassmorphism-stack, uniform-border-radius) look at the whole file rather than per line, because the anti-pattern is a composition of properties, not a single declaration.

Ignore comments

Silence a finding at the source. Three forms:

/* ui-craft-detect-ignore-file */
// whole-file skip, put at top

/* ui-craft-detect-ignore-next-line */
transition: all 200ms;  // skipped

/* ui-craft-detect-ignore-rule: transition-all */
transition: all 200ms;  // only that rule skipped on this line

.uicraftrc.json

Project-level config. Disable rules by id or change severity.

{
  "rules": {
    "pure-black-text": "off",
    "generic-cta": "warn",
    "uppercase-heading": "critical"
  }
}

Place it at the project root. The CLI walks up from the scanned path to find it.

--fix and --fix-dry-run

Auto-fix the rules that have a safe replacement (for example transition: alltransition-property with the declared properties inferred from the block).

npx ui-craft-detect ./src --fix
npx ui-craft-detect ./src --fix-dry-run   # shows the diff, doesn't write

Only rules with a fix_apply method are auto-fixable. Everything else still needs manual judgment.

--sarif

Emit SARIF v2.1.0 for GitHub Code Scanning or any SARIF-compatible viewer.

npx ui-craft-detect ./src --sarif > report.sarif

Upload report.sarif with github/codeql-action/upload-sarif@v3 to surface findings in the Security tab.

Pre-commit hook (Husky)

Install Husky once in the project, then add a hook that scans staged files.

pnpm add -D husky
pnpm exec husky init

Write .husky/pre-commit:

#!/usr/bin/env sh
npx ui-craft-detect $(git diff --cached --name-only --diff-filter=ACMR | grep -E '\.(css|jsx|tsx|vue|svelte|astro)$')

Commits with findings fail. Skip ad-hoc with git commit --no-verify.

The main ui-craft repo ships a richer version at .githooks/pre-commit that also auto-versions marketplace.json. Enable per clone with git config core.hooksPath .githooks.

GitHub Action

Run on every push and PR.

name: ui-craft-detect
on: [push, pull_request]

jobs:
  scan:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 22
      - name: Scan for anti-slop
        run: npx ui-craft-detect ./src

For SARIF + GitHub Security integration:

      - name: Scan with SARIF output
        run: npx ui-craft-detect ./src --sarif > ui-craft.sarif
        continue-on-error: true
      - name: Upload SARIF
        uses: github/codeql-action/upload-sarif@v3
        with:
          sarif_file: ui-craft.sarif

Source


Spotted something out of date? Open an issue on GitHub →