api-consistency
Skill9 starsChanged 6 days agoMITView on GitHub
Review component prop interfaces across the design system, or scoped to one component or family, for naming inconsistencies, missing standard props, mixed colour vocabulary, contract violations, and descriptions that disagree with the contract. Use when asked to review component APIs, audit prop naming consistency, check a component's API, or check TypeScript interfaces for API inconsistencies.
In this repository33 other files it gives its agents.
- CLAUDE.mdCLAUDE.md
- accessibility-auditSkill
- animation-jump-checkSkill
- chat-qualitySkill
- checkpointSkill
- component-consistency-auditSkill
- component-doc-pageSkill
- content-auditSkill
- dependency-advisorySkill
- design-qaSkill
- dogfoodSkill
- drift-auditSkill
- first-deploySkill
- landSkill
- link-rotSkill
- new-componentSkill
- new-pageSkill
- new-presetSkill
- new-templateSkill
- new-tokenSkill
- new-validatorSkill
- parkSkill
- pre-deploySkill
- release-postSkill
- releaseSkill
- responsive-sweepSkill
- security-auditSkill
- seo-auditSkill
- shipSkill
- super-shipSkill
- token-auditSkill
- token-renameSkill
- visual-reviewSkill
The file
We split this skill into the rules it gives an agent
10 rules. About 0 are common, 3 are distinctive, and 7 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈3
Distinctive · estimate
This author's own choices. Worth a look.
7
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Distinctive: line 15
Use this skill when asked to review component APIs, check prop naming consistency, or audit TypeScript interfaces — phrases like "review …
- Distinctive: line 29
Default values (from destructuring defaults in the function signature)
- Very specific: line 47
Should follow is*/has* convention OR plain adjective — not both (e.g.
- Very specific: line 53
Must be on* (e.g. onClick, onChange, onDismiss)
- Distinctive: line 61
Should use a consistent vocabulary across components
- Very specific: line 74
All components rendering clickable/interactive elements should have className?: string
- Very specific: line 75
All components with visual disabled states should have disabled?: boolean
- Very specific: line 76
All form-like components should have id?: string
- Very specific: line 81
If one component accepts iconLeft/iconRight, siblings in the same family should follow the same pattern
- Very specific: line 82
Default values: if size defaults to "default" on a Gadget, it should not default to "medium" on a related Sprocket
≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
api-consistency
Review component prop interfaces across the design system for naming inconsistencies, missing standard props, and pattern violations.
When invoked
Use this skill when asked to review component APIs, check prop naming consistency, or audit TypeScript interfaces — phrases like "review component APIs", "prop consistency audit", "are our component props consistent", "check for API inconsistencies", "check this component's API".
It also runs scoped inside other flows: new-component runs it on a new component before registering, and ship and super-ship run it on the components a diff touched. When run scoped inside another flow, report only findings about the components in scope (the touched components and their family). Library-wide findings the owner has already reviewed and accepted are not news to that flow, and re-raising them there buries the finding the flow exists to catch.
Instructions
- Determine scope. Accept one of:
- Scoped (the cheap mode other skills call): one component, or a family. A family is the component's shared folder (the registry entry's
folder) or itscategoryinsrc/components/registry.json, plus any component that composes it or that it composes. Every check below runs, but only over these components, and the family is the comparison set for the family checks. - A list of specific components (e.g.
Button, CircularButton, ButtonGroup) → compare those all→ scan all components insrc/components/, plus the chat widget's documented exports insrc/chat/(they ride the same generated API withimportPathrift-ds/chat)- A category description (e.g. "all button-like components", "all form inputs") → infer the relevant components
- Scoped (the cheap mode other skills call): one component, or a family. A family is the component's shared folder (the registry entry's
- Read every component's TypeScript interface. Start from
website/src/data/component-api.generated.tsfor the prop inventory — it holds every own prop with type, default, requiredness and description, parsed exactly as the published.d.tsdocuments them. Then read the.tsxfiles in scope for what the generated file does not capture:- The structural contract (the ref,
{...rest}placement, the own-props split,'use client') - Default values (from destructuring defaults in the function signature)
- The structural contract (the ref,
First, check conformance to the published contract. This is the highest-value part of the review and takes precedence over style preferences below. The contract is defined in the
new-componentskill and summarised in CLAUDE.md's Component Anatomy — read one of them rather than trusting this list, which exists to tell you what to look for, not to restate the rules:- The ref, the rest spread and the native props.
node scripts/validate-component-contract.mjsbuild-enforces a ref (forwardRef with adisplayName, or React 19's plainrefprop), props extending the native element's type, and the ref and{...rest}on one node with the root class after the spread; the props-contract story (src/stories/Contract.stories.tsx) proves at runtime that both reach the same element. Run the script rather than re-reading for these. What it cannot judge, and you still check: whether the node it found is the primary one (the panel for a portal component, the control for a form field), and whether own handlers the component sets on that node compose with the consumer's (onMouseEnterfrom rest must still fire) instead of overwriting them. 'use client'present when the component is interactive, and absent when it is purely presentational. A needless directive silently costs consumers Server Component rendering — flag both directions.- Native event signatures keep the standard names.
onChangemust be aChangeEventHandler, never(value) => void. The convenience callback is named for the value's shape:onValueChange(string/number),onCheckedChange(boolean),onValuesChange(array) — and both fire. - The controlled/uncontrolled pair convention. State a consumer can own comes as
x/defaultX/onXChange, and works withxomitted. A controlled-only prop with nodefault*partner, or a selection namedactiveTab/onTabChangewhere the convention saysvalue/onValueChange, is a finding, unless the component's design.md spec says it is fully controlled by design. - English text is a prop, announced or visible. A literal English
aria-label(or visually hidden text) the component writes with no*Labelprop to replace it is a finding: a consumer cannot localise it. The same goes for visible words.validate-accessible-names.mjsandvalidate-visible-text.mjscatch the literal attributes and JSX text; what they cannot see, and you check, is English inside string literals and arrays: a weekday or month list, a pluralised count built in a template (${n} results), a status default. - No Figma variant properties in the code API. A
stateenum mixinghover/active(CSS pseudo-classes the browser owns) withdisabled(real semantics) has two sources of truth. Prefervariantoverpriority/kind, anddisabledas a real boolean. - Labelled form controls compose inside
Fieldrather than re-implementing label/helper/required/ARIA wiring. - Deprecations, not removals — an old prop should still work and carry
@deprecatednaming its replacement.
Where a prop deliberately shadows a native attribute with different meaning (
sizevs the native character-width attribute,titlevs the native tooltip), that is acceptable only if the collision is documented in the prop's JSDoc. Flag undocumented collisions.- The ref, the rest spread and the native props.
Then check these style-level inconsistencies:
Boolean prop naming:
- Should follow
is*/has*convention OR plain adjective — not both (e.g.isDisabledon one component,disabledon another doing the same thing) - Flag: mixed usage within the same component family
The machine-held half of this vocabulary is
website/public/schema/component.schema.json: itsallOfrules are the list of what it locks, every generated component schema is proven against it on each build, and CLAUDE.md's Schemas are generated twins bullet owns the rule that a new sanctioned tone changes that file andSANCTIONED_TONEStogether. A finding that changes a locked vocabulary is also an edit there. The decisions in HISTORY.md's 2026-11-01 schemas entry are accepted findings: do not re-raise them.Event handler naming:
- Must be
on*(e.g.onClick,onChange,onDismiss) - Flag:
handleClick,clickHandler,onClickHandler, or similar
Content prop naming:
labelfor display text,childrenfor slot content- Flag:
text,title,copy,contentused interchangeably across components for the same purpose
Size enum values:
- Should use a consistent vocabulary across components
- Flag:
"sm"/"md"/"lg"on one component and"small"/"medium"/"large"on another, or"compact"/"default"on one and"small"/"medium"on another
Colour vocabulary:
- Emphasis and colour are separate axes.
variantholds emphasis (primary/secondary/tertiary);toneholds colour (default/neutral/destructive). Flag avariantunion that mixes the two without the colour values marked as legacy aliases beside atoneprop (CLAUDE.md's Component Anatomy states the rule; design.md's Components intro owns the details;scripts/validate-prop-vocabulary.mjsenforces the mechanical half). ItsSANCTIONED_TONES,SANCTIONED_FORM_VARIANTS,SANCTIONED_SIZESandLEGACY_VALUEStables are the record of the deliberate exceptions (atonein another vocabulary, avariantthat picks a form rather than an emphasis, a size scale of its own, a labelled legacy alias), so read them before flagging any of these - One status set. A status-bearing component carries the five status roles on
variant, the same five every other status component uses. Flag a sixth role, a missing one, or a synonym (successforpositive,dangerforerror) - One word for danger:
destructive. Flagdanger,negativeand other strays naming the same thing, unless the value is a labelled legacy alias inLEGACY_VALUES - Data entries take a boolean, components take a tone. An item inside a data array (a menu item, a command row) marks danger with
destructive?: boolean; a component marks it withtone. Flag the two crossed
Popup side vocabulary:
- The components that open an anchored panel name its preferred side with
position(Tooltip, Popover, HoverCard),placement(ModelPicker, ThemePicker, AiButton'ssummaryPlacementandrequestPlacement) oralignfor the edge (DropdownMenu, Popover, SplitButton, ThemePicker). Those are settled and never renamed (the API only grows); flag a new component that introduces a fourth spelling, or uses one of these words for something else
Missing standard props on interactive components:
- All components rendering clickable/interactive elements should have
className?: string - All components with visual disabled states should have
disabled?: boolean - All form-like components should have
id?: string - A form control's
namemust reachFormData, whatever element it renders: on the native input, or through a hidden input when the control is adivorbuttonwith an ARIA role (design.md, "A control reaches a form throughname"). Flag anamethat a form never reads, including one spread onto abuttonor adiv. The controls design.md lists as taking nonameare not missing one.
Family consistency:
- Components in the same family (e.g. Button / CircularButton / ButtonGroup) should share
sizeenum values - If one component accepts
iconLeft/iconRight, siblings in the same family should follow the same pattern - Default values: if
sizedefaults to"default"on a Gadget, it should not default to"medium"on a related Sprocket
- Should follow
Then check the contract agrees with its description. For each component in scope, read its entry in
website/src/data/component-api.generated.ts(exactly what/api/mcp'sget_componentserves, so this is the contract strangers' agents see), then read the two prose descriptions of it against that entry:- its registry
description(src/components/registry.json, orsrc/chat/registry.jsonfor the widget), which also ships through the site chat's corpus, the consumer agent skill and the page metadata - its
###spec section indesign.md
Flag any prop or value a description names that the contract does not carry, or carries only as
@deprecated(a description still saying "neutral and destructive variants" after those moved totoneis the classic case). A design.md spec is not a props list and is not expected to name every prop: the prop JSDoc is the one home of the full contract, and the generated component API, the per-component.mdpages and the MCP endpoint all serve it, so restating it in design.md would be a second copy to drift. Flag a spec that omits a prop only when the prop changes what the component looks like or how it behaves (a variant, a size, a mode) and a reader of the spec would design wrongly without it. The fix goes in the source of the description, never in a generated file.- its registry
Output a grouped findings report. The component names in this example are fictional by design — findings about real components go stale the moment someone fixes them, so this block only demonstrates the format:
## API Consistency Report ### Contract violations (highest impact) - Gadget — takes no ref; a consumer cannot reach its node - Sprocket — props do not extend ComponentPropsWithoutRef<'span'>; data-* unreachable - MetricPod — has 'use client' but no hooks or handlers; blocks Server Component rendering ### Boolean prop naming - Gadget: uses `disabled` (plain adjective) - Doodad: uses `isDisabled` (is* prefix) → Standardise to `disabled` across all interactive components ### Size enum values - Gadget: "compact" | "default" | "large" - Whatsit: "small" | "medium" | "large" → Standardise to the enum of the most-used component in the family ### Colour vocabulary - Whatsit: `variant` holds "primary" | "danger"; danger is a colour, and a stray word for destructive → Move it to `tone="destructive"`, keep `variant="danger"` as a deprecated alias ### Description disagrees with contract - Doodad — registry description names a "ghost variant"; the contract has no such value ### Missing className prop - Doodad — no className passthrough - Gadget — no className passthrough ### Summary X naming inconsistencies · Y missing props · Z structural mismatches- Prioritise fixes by impact:
- High: Renames that would require consuming code changes — flag these clearly so the owner can decide whether to batch into a breaking release
- Medium: Missing props that are commonly needed by consumers
- Low: Style preferences with no breaking impact
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

