Taste Lint
Catch AI slop before you ship
For developers shipping UI an agent helped write. Taste Lint flags the copy, type and motion a design reviewer would send back.
npx taste-lint@latest initnpx taste-lint@latest init --agentTry it on your copy
Pick a sample or paste your own. These 8 copy rules load from the CLI’s own rule files and check each line as you type.
One line is one string. It runs in your browser and nothing leaves the page.
6 findings from 8 rules.
Success message padded with "successfully"
copywriting-toast-successfullyLine 1
Successfully saved your changes! 🎉
Fix: Name the thing that changed in past tense and stop: "Project deleted", not "Successfully deleted the project."
Emoji in interface copy
copywriting-emoji-in-uiLine 1
Successfully saved your changes! 🎉
Fix: Remove the emoji. Headings, descriptions, buttons and labels carry their meaning in words.
Verb buried in a noun
copywriting-nominalisationsLine 2
Please make a selection before you continue...
Fix: Use the verb: decide, analyse, review, help, consider, conclude, assess, evaluate, select, describe.
Three periods instead of an ellipsis character
typography-ellipsisLine 2
Please make a selection before you continue...
Fix: Use the single ellipsis character (U+2026), for example "Saving…".
Software described as thinking, wanting or knowing
copywriting-anthropomorphismLine 3
The app thinks your session expired, i.e. you were away too long.
Fix: Use the precise verb: returns, sends, rejects, creates, deletes, validates, stores, triggers.
Latin abbreviation in running text
copywriting-latinismsLine 3
The app thinks your session expired, i.e. you were away too long.
Fix: Write "for example", "that is" and "and so on". Latin abbreviations trip screen readers and non-native readers.
210 rules. 28 can fail your build.
Taste Lint never fails a build on a model’s opinion. Only a regex or a measured value can block a run. The other 182 rules leave review notes, and the judgement calls among them go to Jev, which answers with a probability.
How a rule gets promotedAgent instructions7 rules, 0 blocking
authoring-approval-every-stepBlanket approval gate blocks routine local workReview noteauthoring-fixed-agent-quotaWorkflow imposes an unconditional agent quotaReview noteauthoring-generic-coding-adviceInstruction contains only generic coding adviceReview noteauthoring-generic-verificationVerification instruction names no observable checkReview noteauthoring-plan-vague-acceptancePlan acceptance criterion is not observableReview noteauthoring-instruction-missing-scriptInstruction invokes an undefined package scriptReview noteauthoring-skill-frontmatterSkill frontmatter is missing or invalidReview note
Copy74 rules, 10 blocking
copywriting-anthropomorphismSoftware described as thinking, wanting or knowingBlocks a runcopywriting-antithesis-contrastDenies a thing nobody claimed to sharpen a pointReview notecopywriting-antithesis-frameSets up a claim by denying one nobody madeBlocks a runcopywriting-artificial-balanceForces symmetrical both-sides framingReview notecopywriting-bare-confirm-labelGeneric confirmation label obscures a consequential actionReview notecopywriting-boilerplate-offerEnds with a generic offer to help or continueReview notecopywriting-canned-phrasingUses canned phrases common in assistant responsesReview notecopywriting-claim-without-evidenceQuality claimed, nothing the reader could checkReview notecopywriting-click-here-linksLink text that names nothingBlocks a runcopywriting-conditions-firstAn instruction hides its prerequisite after the actionReview notecopywriting-confirmshamingDecline label shames the personReview notecopywriting-copula-avoidanceDresses up "is" or "has"Review notecopywriting-cta-names-outcomeButton that names an action, not what the reader getsReview notecopywriting-dead-linkLink with nowhere to goReview notecopywriting-dead-weight-hedgesHedge or throat-clearing phrase the sentence does not needReview notecopywriting-em-dash-in-uiSpaced em dash in interface copyReview notecopywriting-emoji-in-uiEmoji in interface copyBlocks a runcopywriting-empty-state-no-actionEmpty state that names the void but not the first moveReview notecopywriting-error-descriptionsAn error explanation provides no recovery guidanceReview notecopywriting-excessive-hedgingHedges claims more than the subject requiresReview notecopywriting-external-arrow-internal-linkExternal-link arrow on a link that stays on the siteReview notecopywriting-fabricated-specificitySuspiciously specific details without grounding or attributionReview notecopywriting-filler-wordsIntensifiers and filler that add nothingReview notecopywriting-flat-descriptionInformation with no reason for the reader to careReview notecopywriting-friction-ctaCall to action that names a commitment without reducing the riskReview notecopywriting-generic-audienceBenefit promised to everyone, so it lands on no oneReview notecopywriting-generic-framingRelies on generic framing instead of concrete contextReview notecopywriting-generic-languageRelies on generic or interchangeable languageReview notecopywriting-generic-validationUses vague praise or generic validationReview notecopywriting-latinismsLatin abbreviation in running textReview notecopywriting-leaked-error-messageLeaked raw error message in UIReview notecopywriting-live-region-feedbackAnnounce Status Changes with Live RegionsReview notecopywriting-machine-proseSentence shapes that give away generated writingReview notecopywriting-mechanism-over-outcomeDescribes the mechanism where the reader needed the outcomeReview notecopywriting-meta-commentaryComments on how it is presenting or organising the answerReview notecopywriting-missing-reasoning-linkA conclusion with no link to what came beforeReview notecopywriting-nominalisationsVerb buried in a nounBlocks a runcopywriting-over-explanationExplains straightforward points more than necessaryReview notecopywriting-participle-tailSentence ends in a participle tailBlocks a runcopywriting-passive-voicePassive voice where the actor is knownReview notecopywriting-permission-benefitPermission request states no user benefitReview notecopywriting-promotional-docs-voiceMarketing voice inside documentationReview notecopywriting-prompt-restatementReads like a restatement of an implied prompt or assignmentReview notecopywriting-punctuation-ticsOveruses dashes, colons or parenthetical asides in a patterned wayReview notecopywriting-reader-firstHeadline that leads with what the product isReview notecopywriting-register-shiftFormal or slangy register breaking the product voiceReview notecopywriting-repetitive-transitionsRepeatedly uses formal or formulaic transitionsReview notecopywriting-requirements-languageA requirement presented as a recommendationReview notecopywriting-searchable-headingsA documentation heading names no topic or actionReview notecopywriting-semantic-linksUse Semantic Links for NavigationReview notecopywriting-slogan-headingsHeadings built from stacked slogan fragmentsReview notecopywriting-structural-symmetryExcessive structural symmetry or formulaic organisationReview notecopywriting-success-state-specificSuccess message does not name the completed operationReview notecopywriting-tee-upTees up a point instead of stating itBlocks a runcopywriting-temporal-wordsWording anchored to the time of writingReview notecopywriting-toast-successfullySuccess message padded with "successfully"Blocks a runcopywriting-unexplained-actionNames an action that cannot be picturedReview notecopywriting-unnatural-polishPolished in a way that suppresses personal textureReview notecopywriting-unverifiable-proofUnverifiable proof standing in for real evidenceReview notecopywriting-vague-errorError message with no cause and no next stepReview notecopywriting-visual-direction-instructionUI instruction depends on visual positionReview notecopywriting-long-sentenceSentence over 25 words joins several clausesBlocks a runcopywriting-readme-scaffoldREADME still contains scaffold instructionsReview notecopywriting-readme-descriptionREADME has no introductory descriptionReview notecopywriting-readme-quickstart-elisionQuickstart example contains an unfinished placeholderReview notecopywriting-document-empty-headingA section has no contentReview notecopywriting-document-heading-orderHeading skips a structural levelReview notecopywriting-document-code-fence-languageCode fence has no language tagReview notecopywriting-document-broken-local-linkDocument links to a missing local fileBlocks a runcopywriting-readme-license-linkREADME license link points to a missing fileReview notecopywriting-readme-install-nameREADME installs an old or unrelated package nameReview notecopywriting-fact-paddingThe same few facts restated to fill the pageReview notecopywriting-idea-repetitionRepeats the same idea without adding new evidenceReview notecopywriting-invented-capabilityStates a capability the brief never gaveReview note
Visual craft21 rules, 0 blocking
craft-atmosphere-gradientPurple or cyan gradient used as atmosphereReview notecraft-decoration-no-roleStacked decorative layers with no roleReview notecraft-decorative-ordinalsOrdinal numbers on items that are not a sequenceReview notecraft-default-indigoIndigo accent that looks like a template defaultReview notecraft-emoji-in-copyEmoji in interface copyReview notecraft-faux-product-chromeFaux product chrome wrapping nothingReview notecraft-hover-only-affordanceCritical actions hidden behind hover-only affordancesReview notecraft-i18n-rtl-untestedLayout uses physical (left/right) instead of logical (start/end) propertiesReview notecraft-i18n-untestedHardcoded light-mode token with no dark variant on the lineReview notecraft-inline-stylesInline style object or style element in a componentReview notecraft-lazy-load-offscreenHero or above-the-fold image marked loading lazyReview notecraft-left-accent-borderA coloured left border on cards that are not calloutsReview notecraft-token-driftArbitrary values drifting off the themeReview notecraft-viewport-scalingViewport explicitly disables user zoomReview notecraft-near-duplicate-scaleArbitrary font size within 1px of a declared theme stepReview notecraft-raw-colour-classPalette colour with a numeric shade instead of a theme tokenReview notecraft-arbitrary-value-classLiteral visual styling worth reviewing for a design tokenReview notecraft-interpolated-class-stringClass name built from a template expressionReview notecraft-image-dimensions-and-priorityImage has no declared dimensions or styleReview notecraft-eyebrow-overuseAn eyebrow over nearly every headingReview notecraft-opacity-ladderA ladder of text opacitiesReview note
Engineering36 rules, 0 blocking
engineering-catch-rethrowCatch block that only rethrowsReview noteengineering-ci-cancels-default-branchConcurrency cancels runs on the default branchReview noteengineering-ci-coverage-on-prCoverage collected on every pull requestReview noteengineering-ci-full-cloneCheckout fetches full history without needing itReview noteengineering-ci-no-concurrencyPull request runs are never cancelledReview noteengineering-ci-uncached-installDependencies installed with no cacheReview noteengineering-dead-branchBranch that can never runReview noteengineering-debug-log-in-uiconsole.log left in a componentReview noteengineering-empty-catchError caught and droppedReview noteengineering-hardcoded-secretSecret committed in sourceReview noteengineering-heavy-dependencyImports a heavy library with a native replacementReview noteengineering-insecure-random-tokenMath.random used for a token or secretReview noteengineering-non-atomic-writesRelated writes without a transactionReview noteengineering-query-in-loopOne query per item inside a loop (N+1)Review noteengineering-request-waterfallIndependent awaits run one after anotherReview noteengineering-restating-commentComments restate the code under themReview noteengineering-sensitive-data-loggedCredential or personal data written to logsReview noteengineering-silent-fallbackFailure turned into a default with no recordReview noteengineering-stale-feature-flagFeature flag hard-coded to one valueReview noteengineering-sync-io-in-handlerBlocking sync call inside a request handlerReview noteengineering-template-residuePlaceholder or debug residueReview noteengineering-test-focusedFocused test left in (.only)Review noteengineering-test-mock-echoTest only checks that a mock was calledReview noteengineering-test-render-onlyComponent test renders and checks presence onlyReview noteengineering-test-self-comparisonTest compares a value with itselfReview noteengineering-test-skippedSkipped or placeholder testReview noteengineering-token-in-local-storageAuth token stored in localStorageReview noteengineering-unbounded-readList read with no limit or paginationReview noteengineering-test-no-assertionTest asserts nothingReview noteengineering-test-snapshot-onlyTest asserts only a snapshotReview noteengineering-untested-moduleLogic the tests barely runReview noteengineering-oversized-fileFile is over 1000 linesReview noteengineering-type-bypassType check bypassedReview noteengineering-duplicated-blockThe same block appears twice in one fileReview noteengineering-commented-out-codeCommented-out codeReview noteengineering-unused-dependencyDependency nothing in the package importsReview note
Interaction29 rules, 1 blocking
interaction-a11y-color-only-meaningAvoid Color-Only MeaningReview noteinteraction-a11y-data-table-semanticsMark Up Data Tables With Real Table SemanticsReview noteinteraction-a11y-disabled-control-tooltipDo not put tooltips on disabled controlsReview noteinteraction-a11y-icon-controls-labeledLabel Icon-Only ControlsReview noteinteraction-a11y-media-captionsCaption Video and Transcribe AudioReview noteinteraction-a11y-semantic-html-firstPrefer Native Semantics Before ARIAReview noteinteraction-a11y-tooltip-no-interactiveHover tooltips must not contain interactive contentReview noteinteraction-broken-focus-trapModal without working focus trapReview noteinteraction-dont-block-paste-imeDo Not Block Paste or IME InputReview noteinteraction-error-associationAssociate and Announce Form ErrorsReview noteinteraction-focus-visiblePreserve Visible Focus StatesReview noteinteraction-inline-errors-first-focusShow Inline Errors and Focus the First Invalid FieldReview noteinteraction-input-affix-hit-targetOverlay input affixes on the fieldReview noteinteraction-keyboard-operableEnsure Full Keyboard OperabilityReview noteinteraction-labels-and-autocompleteLabel Inputs and Set Autocomplete MetadataReview noteinteraction-lost-data-on-errorForm data lost on validation errorReview noteinteraction-mobile-input-font-sizeKeep Mobile Input Text at Readable SizeReview noteinteraction-no-disable-while-submittingSubmit button not disabled while pendingReview noteinteraction-no-empty-stateEmpty state has no call to actionReview noteinteraction-no-error-boundaryClient async tree without a local error boundaryReview noteinteraction-no-error-stateAsync UI failure has no local recoveryReview noteinteraction-no-suspense-boundaryAsync server component without Suspense boundaryReview noteinteraction-not-restoredFocus not restored after a modal, sheet or popover closesReview noteinteraction-on-dynamic-contentRoute change without focus management or a live regionReview noteinteraction-optimistic-without-rollbackOptimistic update without rollback on server rejectReview noteinteraction-out-of-order-responsesInput handler fetches with no cancellationReview noteinteraction-target-sizeButton sized below the touch target floorReview noteinteraction-use-form-status-misuseuseFormStatus called in same component as form (silent bug)Review noteinteraction-a11y-image-alt-textImage is missing an alt attributeBlocks a run
Motion12 rules, 7 blocking
motion-pulse-dotA pulsing dot with nothing live behind itReview notemotion-will-change-misusewill-change promotes a property the compositor cannot animateReview notemotion-ease-in-on-transitionTransition eases in, so it starts slow while the user is watchingBlocks a runmotion-linear-easing-on-transitionLinear easing on motion that is not a spinner or marqueeBlocks a runmotion-duration-over-300msTransition longer than 300ms on product UIBlocks a runmotion-transition-alltransition-all animates every property, layout includedBlocks a runmotion-scale-from-zeroEntrance from scale zeroBlocks a runmotion-animate-without-reduced-motionAnimation has no local reduced-motion guardReview notemotion-reduced-motion-animationContinuous animation explicitly targets reduced-motion usersReview notemotion-layout-property-transitionTransition explicitly animates layout propertiesReview notemotion-css-transition-alltransition: all animates every property, layout includedBlocks a runmotion-framer-motion-importImports framer-motion instead of motion/reactBlocks a run
Product2 rules, 0 blocking
product-nested-modalsA modal that opens inside another modalReview noteproduct-select-for-few-optionsA select with two or three static optionsReview note
Search6 rules, 0 blocking
seo-canonical-wrong-hostCanonical points at a dev or preview hostReview noteseo-sitemap-build-time-lastmodSitemap lastmod is the build timeReview noteseo-unconditional-noindexPage set to noindex in sourceReview noteseo-page-missing-metadataPage inherits its parent's title and descriptionReview noteseo-missing-metadata-baseRoot metadata has URLs but no metadataBaseReview noteseo-missing-crawl-filesApp has no sitemap or robots fileReview note
Typography23 rules, 10 blocking
typography-dashesDouble hyphen or spaced hyphen standing in for a dashBlocks a runtypography-ellipsisThree periods instead of an ellipsis characterBlocks a runtypography-hover-weight-shiftDo not change font-weight on hover or selectedReview notetypography-midpoint-separatorsPipe or bullet used as an inline separatorBlocks a runtypography-multiplication-signThe letter x used as a multiplication signBlocks a runtypography-nbsp-value-unitBreaking space between a value and its unitBlocks a runtypography-readable-scaleSet a Readable Type ScaleReview notetypography-straight-quotesStraight quotes in rendered copyBlocks a runtypography-title-case-headingHeading, button or label set in Title CaseReview notetypography-trending-display-serifInstrument Serif as the default display faceReview notetypography-body-below-15pxRunning text set below 15pxReview notetypography-letterspaced-bodyPositive letter-spacing on lowercase body textReview notetypography-light-weight-small-bodyThin or light weight on text-sized copyBlocks a runtypography-line-height-out-of-bandLine height outside the comfortable band for its sizeBlocks a runtypography-hierarchy-size-onlyTwo roles told apart by a small size step aloneReview notetypography-numeric-cell-proportional-figuresNumbers in a table cell without tabular figuresReview notetypography-stacked-emphasisBold, caps, colour and size stacked on one elementBlocks a runtypography-uppercase-without-trackingUppercase text with no added letter-spacingBlocks a runtypography-justify-without-hyphensJustified text explicitly disables hyphenationReview notetypography-disabled-kerningText explicitly disables font kerningReview notetypography-stretched-glyphsText glyphs are horizontally stretchedReview notetypography-italic-accent-wordOne italic word in every headingReview notetypography-mono-overuseMonospace on everything that is not codeReview note
Live from npm and GitHub
Questions
What does Taste Lint check?
Copy, typography, interaction and motion in JSX, TSX and CSS. Add --profile writing for Markdown, MDX and READMEs, or --profile instructions for AGENTS.md, skills and plans. Each finding names the rule, quotes the source and suggests a fix.
Do I need an API key?
No. Every mechanical check runs without a key and makes no model calls. Add a Vercel AI Gateway key only if you want Jev review notes on judgement calls. Those calls bill your own account, and repeat runs reuse cached answers.
Will it fail my CI build?
Only on the 28 active rules, and each of those is mechanical: a regex or a measured value decides. The other 182 leave review notes that never fail a run. A Jev rule can block only after tune --write proves its precision on a labelled holdout.
Is Taste Lint free?
Yes. Taste Lint is MIT licensed and the mechanical checks cost nothing to run. The one optional cost is uncached Jev checks, billed to your Vercel AI Gateway key. Run with --dry-run first to see the requests and the estimated cost.
What do I need to run it?
Node.js 24.11 or later. Run npx taste-lint@latest init in your project folder. It installs Taste Lint locally and adds a taste script, so npm run taste lints the repo from then on.
Where do the rules come from?
From Agent Skills and Taste Training, both by Matthew Blode. Each rule file names the source file and line it came from, and port-rules --check confirms every ported rule still matches that source.
Lint your next pull request
Mechanical checks need no key and make no model calls. Add --dry-run to preview Jev requests and their cost first.
npx taste-lint@latest initnpx taste-lint@latest init --agent