Reliability

A set of static, automated checks the CLI installs into your project to catch accessibility and design-token regressions in CI. It is not a replacement for manual review. It's a floor that never slips, running the same way on every pull request.

Don't rely on AI

AI-assisted and human-reviewed code both drift. A reviewer, human or model, can miss a missing alt or a stray bg-red-500in a large diff, especially in AI-generated output where volume is high and review is skimmed. A small, deterministic, mechanically-run check doesn't get tired or skim. It either finds the violation or it doesn't.

The point isn't "don't trust AI-written UI code." It's that trusting review alone, whether the code came from a person or a model, isn't a substitute for a check that runs the same way every time in CI. This design system's own token-contrast gate (scripts/check-contrast.ts) is the same idea applied to design tokens; this CLI command is that idea made portable so any consumer project can adopt it, not just this one.

DaFink UI Audit

Via the CLI, in your project root:

npx dafink-ui audit

This writes scripts/dafink-audit.mjs, a CI workflow file for the target provider (skippable entirely with --no-workflow), and wires an npm run dafink-audit script into package.json.

GitHub Actions and GitLab CI use incompatible config formats, so the CLI generates one or the other based on --provider (it defaults to github). To target GitLab instead:

npx dafink-ui audit --provider gitlab

If your project already has a .gitlab-ci.yml, the CLI appends a dafink-audit job to it rather than overwriting your existing pipeline, so running the command again is a no-op if that job is already present. Need both providers configured (e.g. a repo mirrored to both hosts)? Run the command twice, once per provider. Each writes to its own file, so neither run interferes with the other.

The script is zero-dependency and statically scans your project's own .tsx/.jsx files for three categories of violation. It exits with code 1 if it finds any, so it fails the CI job.

Missing accessible names

<img> elements without alt, and icon-only buttons or links without an aria-label or aria-labelledby.

Unreachable interactive elements

A div or span with an onClick handler but no role and no tabIndex, invisible to keyboard users.

Hardcoded colors

Raw hex or rgb() values, or arbitrary Tailwind color utilities, that bypass token classes and skip contrast checking.

This is a static source-code scan, not a runtime audit. It catches what it catches by pattern-matching your files, nothing more.

ESLint

dafink-auditis scoped narrowly: accessible names, keyboard reachability, and hardcoded colors, nothing more. It is not a general-purpose linter and isn't meant to replace one. Unused variables, hook dependency violations, unreachable code, and dozens of other correctness issues are ESLint's job, not this script's.

If your project already runs ESLint in CI, no extra setup is needed. dafink-auditis designed to run alongside it, not in place of it. If it doesn't yet, add it; the two checks catch different failure modes and neither substitutes for the other.

None of the above renders a component or asserts on its behavior. That's what UI testing is for. Pass --with-tests to dafink-ui addand each component's own .test.tsx is copied in alongside it, giving you a real starting point instead of an empty file:

npx dafink-ui add button --with-tests

Off by default since it assumes Vitest and @testing-library/react. If your project uses a different test runner, skip the flag and treat the component's test file on its docs page as a reference instead.

Google Lighthouse

Both dafink-audit and ESLint operate on source files. Neither ever renders your app. Running Google Lighthouse against the rendered page is the complement: it audits the actual DOM in a real browser, so it also catches contrast failures, malformed ARIA, and performance/SEO issues neither static tool looks for at all.

CI workflow

Either provider's config runs the check on every pull/merge request and on every push to main, failing the job if any violation is found. The lint and test steps below are commented out. Uncomment whichever your project actually has; they aren't required for dafink-audit itself to run.

GitHub Actions: .github/workflows/dafink-audit.yml

name: DaFink UI Audit

on:
  pull_request:
  push:
    branches: [main]

jobs:
  audit:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 20
      - run: npm ci
      - run: node scripts/dafink-audit.mjs
      # - run: npm run lint    # optional, add if your project has a lint script
      # - run: npm test        # optional, add if your project has a test script

GitLab CI: .gitlab-ci.yml

dafink-audit:
  stage: test
  image: node:20
  script:
    - npm ci
    - node scripts/dafink-audit.mjs
    # - npm run lint  # optional, add if your project has a lint script
    # - npm test      # optional, add if your project has a test script
  rules:
    - if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
    - if: '$CI_COMMIT_BRANCH == "main"'