CLI tool
TheOrcDev/shadscan avatar
TheOrcDev/shadscan

shadscan: a deterministic UI audit for shadcn apps, run from the terminal

Deterministic UI audits for shadcn apps, built for your terminal, your CI, and your AI agent.

544 stars12 forksTypeScriptMIT

At a glance

What is it?
shadscan scores a shadcn React app from 0 to 100 across six UI categories and attaches repository-relative evidence to each finding. The design bet is that a read-only static scan plus an optional rendered check is enough to catch postponed product details.
Who is it for?
Adopt shadscan if your app is a shadcn React project and you want a repeatable score plus file-level evidence in CI, pinned to an exact version such as @shadscan/cli@0.17.1. Skip it if you are not on shadcn, if you need a browser-driven accessibility audit, or if a numeric grade on UI polish would be misread by your team.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository received new commits within the last day.
What is it written in?
Mainly TypeScript, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on September 15, 2026, and from our analysis. They are not legal advice.

DEEP OPEN-SOURCE ANALYSIS

The postponed details shadscan is built to catch

The README lists what it looks for: command menus, theme shortcuts, route states, accessible controls, form feedback, metadata, mobile behavior. These are the items that survive code review because nothing breaks when they are absent. A missing Cmd/Ctrl+K shortcut does not throw. A route with no loading state renders fine on a fast connection. The README's own example report shows the shape of a finding: "Missing: command menu has a Cmd/Ctrl+K shortcut", followed by an evidence line and a fix line. The audience is a team already on shadcn and React, working in a repository where a CLI can be pointed at a directory. The 62 rules referenced in the README are grouped into six categories: Foundation, Interaction, States, Accessibility, Forms and Data Entry, Production Polish. The example output shows Interaction as the weak category at 61 percent while the other five sit at 100 percent, which is a fair illustration of what the tool is for: it does not find crashes, it finds the category of work nobody scheduled.

Static rules, an optional browser pass, and a fixed score

The default scan is described as deterministic and read-only. The README states plainly that it does not start the app, edit files, call an AI model, upload source, or require application secrets. That is the mechanism: source inspection plus a rule engine, no runtime execution. A second mode exists. Passing --check-ui with a URL adds rendered checks at mobile and desktop widths, and the progress phases change to match browser work: "Resolving UI target", "Checking mobile and desktop layouts", "Preparing UI report". The two modes are visibly distinct in the terminal, which matters because the static pass and the rendered pass carry different confidence. Findings are sorted into four buckets: Fixes (high-confidence defects with repository-relative evidence), Decisions (product choices to implement or explicitly waive), Advisories (lower-confidence checks needing rendered or manual verification), and Not applicable (rules excluded because the relevant UI surface is absent). That last bucket is the load-bearing one. A scoring tool that penalizes you for a form you never built is noise, and excluding absent surfaces is how shadscan keeps the denominator honest.

Running it: dlx, JSON, prompt, and the CI floor

The quick start is a single command from the project root: pnpm dlx @shadscan/cli. The path defaults to the current directory, and a positional argument scans elsewhere, as in pnpm dlx @shadscan/cli ../my-shadcn-app. npm and Bun users get npx --yes @shadscan/cli and bunx @shadscan/cli. Output selection has three explicit forms: --format human, --format json, --format prompt. The machine-readable path is --json; the agent handoff is --prompt, and --apply --agent codex launches an installed agent with the generated plan. CI gating uses --fail-under 80 --no-interactive --no-roast, and the README's example pins the version: pnpm dlx @shadscan/cli@0.17.1. That pinning advice is repeated in the text, because unqualified commands resolve to the latest stable release and prereleases live under the next tag. Monorepo behavior has two flags: --list-projects prints the workspace packages shadscan found without scanning, and --project apps/web scans one package instead of pooling every application. For focused work there is --category accessibility. The rendered pass adds --check-ui http://localhost:3000, and --route /dashboard --route /settings extends coverage to more same-origin routes; the target URL is always checked.

The interactive menu and where progress output goes

An interactive scan ends with an arrow-key menu offering four actions: copy the agent handoff to the clipboard, print it, launch an installed coding agent, or add a pre-commit score gate. The handoff is highlighted first, so a single Enter grabs it and Esc keeps just the score. Progress checklists are written to stderr and only for interactive human output. JSON, CI, TERM=dumb, non-TTY stderr, and --no-interactive stay quiet, and so does prompt output. Redirecting stdout keeps the report clean while progress stays on an eligible stderr terminal. This is a small decision with a real consequence: piping the report to a file or another process does not drag the progress checklist along with it. The pre-commit score gate in that menu is the most consequential item, because it converts a one-off audit into a threshold that blocks commits. The README does not spell out what the gate writes or which file it touches, so treat that menu entry as something to inspect before accepting it in a shared repository.

A static scanner cannot see what a browser sees

The README draws the line itself: Advisories are checks that need rendered or manual verification. That is the honest boundary of the default mode. Anything that depends on computed styles, actual focus order in a mounted tree, or responsive behavior at a real viewport width falls outside a read-only source pass, which is why --check-ui exists. The rendered pass is not a substitute for a full accessibility audit either. It visits a target URL and a set of same-origin routes at two widths, which is narrower than a screen-reader pass, a keyboard-only traversal, or a contrast measurement against rendered pixels. The scoring model is the other thing to weigh. A single 0 to 100 number with a letter grade compresses six categories into one figure, and the README's own example shows a 92 that hides an Interaction score of 61 percent. A team that reads the headline number and stops will miss exactly the category shadscan was built to surface. The tool is also scoped to shadcn React apps; nothing in the material suggests the rule set transfers to a different component system.

Where shadscan sits next to lint and axe

The obvious comparison is a browser-driven accessibility scanner such as axe-core, usually run through a test runner or a browser extension. The difference is not coverage, it is timing and determinism. An axe-style check needs a running page and a driver, and it reports against the live DOM. shadscan's default pass needs neither a server nor a browser, produces the same result for the same source, and emits a versioned JSON report that a CI job can diff. That determinism is the whole reason the README can promise the scan does not call an AI model or upload source. The trade is that shadscan cannot tell you whether a control is reachable by keyboard in practice, only whether the source suggests it should be. ESLint with jsx-a11y sits on the other side: it is already in most React toolchains and catches attribute-level mistakes, but it has no concept of a score, no category breakdown, and no notion of a route state or a command menu. shadscan is the layer above lint that asks whether the product surface is finished, not whether the JSX is well-formed.

Version pinning, the MIT licence, and upgrade cost

The package is MIT licensed, and the licence file lives at packages/cli/LICENSE in the repository. MIT permits commercial use and modification; it also means no warranty and no support obligation from the maintainers. That is a normal arrangement for a CLI, but it puts the burden of version discipline on the adopter. The release history in the material shows v0.12.0 in August 2026 and v0.17.1 later the same month, so the minor line is moving. Rule sets change between minors, and a score computed under one version is not automatically comparable to a score computed under the next. The README's instruction to pin an exact version in CI exists for that reason. Practically, an upgrade means re-running the scan, comparing the JSON report against the previous run, and deciding whether a new finding is a genuine regression or a new rule. The --fail-under threshold is the setting most likely to break on upgrade, since a stricter rule set can push a passing repository below the floor without any code change.

Who should run it, and what to check first

The fit is a shadcn React codebase with a CI pipeline and at least one person who owns UI quality. Run pnpm dlx @shadscan/cli --json once, read the categories rather than the total, and decide whether the Interaction and States buckets describe real gaps. If they do, pin @shadscan/cli@0.17.1 and add --fail-under with a floor you have actually met, not one you aspire to. In a monorepo, start with --list-projects before choosing a --project target, because pooling every application into one score will hide which app is dragging it down. The wrong fit is a non-shadcn stack, a team that wants a real browser accessibility audit rather than a source-level one, or an organization where a letter grade will be treated as a release gate for visual design. Before adopting, verify two things the material does not settle: what the pre-commit score gate writes into your repository, and whether two consecutive runs on the same commit produce byte-identical JSON. The first is a shared-config decision; the second is the determinism claim the entire tool rests on.

Editorial conclusion

Adopt shadscan if your app is a shadcn React project and you want a repeatable score plus file-level evidence in CI, pinned to an exact version such as @shadscan/cli@0.17.1. Skip it if you are not on shadcn, if you need a browser-driven accessibility audit, or if a numeric grade on UI polish would be misread by your team. Before trusting the number, run --list-projects on your monorepo, run --category accessibility alone to see how the rules behave on your code, and diff the JSON output of two consecutive runs to confirm the determinism the README claims.

Official sources

  1. License: MIT
  2. Project website
  3. README
  4. Releases
  5. TheOrcDev/shadscan on GitHub
Community notes

Community notes