| name | component-check |
| description | Checks a vibe-ui-kit component against project structure and authoring rules. Use this skill whenever the user asks to "check a component", "validate component structure", "проверить компонент", "check component rules", or when finishing a new component and wanting to verify it follows conventions. Also trigger when the user says "component-check" or "/component-check". The skill audits file structure, file responsibilities, Storybook presence, variant separation, export wiring, and documentation presence — and reports every violation with a fix hint.
|
Component Check
Audit a single component folder in src/components/ui/ against vibe-ui-kit conventions.
How to run
The user either names a component ("check Button") or points at a path. Resolve to the folder:
src/components/ui/<ComponentName>/
If the component name is ambiguous or the folder doesn't exist, say so and stop.
Checks to perform
Work through these in order. For each violation, record it in the report with a one-line fix hint.
1. Required files
Every component folder must contain exactly these four file types:
| File | Purpose |
|---|
ComponentName.variants.ts | tv() config + exported types only — no React |
ComponentName.tsx | Component implementation only |
ComponentName.stories.tsx | Storybook stories |
index.ts | Re-exports only — no logic |
Check: all four exist. Extra .tsx files are OK (e.g. Button.icon.stories.tsx) but must be stories, not components.
Why this matters: The split keeps concerns separate — variants are pure style config that can be imported without React; the component is pure behaviour; stories are pure docs; the index is a stable public API surface.
2. variants.ts content
Open ComponentName.variants.ts and verify:
- Imports
tv and VariantProps from @/lib/tv (not directly from tailwind-variants)
- Does NOT import React or any component
- Exports a
tv() config named after the component in camelCase (e.g. button, dropdownMenuItem)
- Exports at least one of:
ComponentVariant, ComponentMode, ComponentSize (using NonNullable<VariantProps<...>>)
- Contains no JSX and no function components
Size rules: If the tv() config defines a size variant (i.e. variants: { size: { ... } } is present), then:
ComponentSize MUST be exported as export type ComponentSize = NonNullable<VariantProps<typeof component>['size']>
- The type name must match the component name prefix (e.g.
ButtonSize, InputSize)
- ALL 5 size keys must be present:
xs, sm, md, lg, xl — no partial sets allowed
- Each size that includes text MUST use the matching text-size class:
xs→text-xs, sm→text-sm, md→text-md, lg→text-lg, xl→text-xl. Using text-sm for md, text-base for lg, or any other mismatch is a violation.
Size dimension consistency: Components that render a single-line interactive element (input, button, select, trigger, tab, toggle — NOT multiline textarea, NOT purely decorative) MUST follow the standard dimension scale derived from Button:
| size | h | px | py | text |
|---|
| xs | h-8 | px-2 | py-2 | text-xs |
| sm | h-9 | px-2.5 | py-2 | text-sm |
| md | h-10 | px-3 | py-2.5 | text-md |
| lg | h-11 | px-3 | py-3 | text-lg |
| xl | h-12 | px-4 | py-3 | text-xl |
Check each size value: if h-*, px-*, or py-* deviate from this table — flag it with the expected value.
Exceptions — do NOT apply dimension check to:
- Multiline components (
Textarea, inputGroupTextarea) — use min-h-* instead of h-*, py may differ
- Components embedded inside a container that already sets height (e.g.
inputGroupInput inside InputGroup wrapper — py-* may be reduced intentionally)
- Icon-only / square components (
Avatar, Spinner, Switch, Checkbox, RadioGroup) — they use h-* w-* square dimensions, not the same scale
- Decorative / layout components (
Empty, Slider, Kbd, Badge) — intentionally compact or structural
3. Component .tsx content
Open ComponentName.tsx and verify:
- Does NOT define any
tv() config (belongs in variants file)
- Imports its variants from
./ComponentName.variants (sibling, not from index)
- Imports
cn from @/lib/utils if it constructs class strings
- Has
ComponentName.displayName = 'ComponentName' set explicitly
- Exports the component as a named export (not default)
- Each logical sub-component that renders markup (e.g.
TooltipContent, InputGroupAddon) lives here or in its own sibling file — not more than one independent renderable element per .tsx file
JSDoc header rule: ComponentName.tsx MUST start with a JSDoc block (/** … */) before the first import. The block must cover:
- One-line description of the component
variant values (if component has variants) — list all, mark default
mode values (if component has modes) — list all, mark default
size values (if component has sizes) — list all, mark default
- Any non-obvious props (loading, hoverVariant, polymorphic href, utility mode behaviour, icon conventions, tooltip attrs, etc.)
- At least one
@example usage
The block should be concise — aim for ~30 lines. Missing or empty JSDoc = violation. A one-liner // Button component is not sufficient — flag it.
Size prop type rule: If the component accepts a size prop AND ComponentSize is exported from the variants file, then the size prop in the component's Props interface/type MUST use ComponentSize — not inline string literals like 'xs' | 'sm' | 'md' | 'lg' | 'xl'. Check the import list includes ComponentSize from ./ComponentName.variants.
The "one element per file" rule: if a single .tsx file exports two or more completely independent renderable elements (e.g. both <Card> and <CardHeader>), that's a violation — each should be in its own file. Tightly coupled primitives that are only wrappers (like const TooltipTrigger = RadixTooltip.Trigger) are fine to group.
4. Stories file
Open ComponentName.stories.tsx and verify:
- Imports
Meta and StoryObj from @storybook/react
- Has a
meta constant typed as Meta<typeof ComponentName>
- Has
export default meta
- Has at least one named story export (
Default at minimum)
- Has
tags: ['autodocs'] in meta (enables auto-generated docs page)
- Uses
title: 'Components/ComponentName'
5. index.ts
Open index.ts and verify:
- Re-exports the component and its Props type from
./ComponentName
- Re-exports the
tv() config and all variant types from ./ComponentName.variants
- Contains no logic, no JSX, no
tv() calls
- If
ComponentSize is defined in variants.ts, it MUST be re-exported here
6. src/index.ts wiring
Check src/index.ts in the project root:
- The component is exported (search for the component name)
- All public types are exported
- If
ComponentSize exists, it MUST be in the export type { ... } line for this component
7. Documentation file
Check docs/components/ for a markdown file named after the component in kebab-case (e.g. Button → button.md, DropdownMenu → dropdown-menu.md):
- File
docs/components/<component-name>.md must exist
- If it exists, open it and verify it is non-empty (not a stub/placeholder)
- If it exists, check it covers at minimum: props table (or props description), at least one usage example
If the file is missing — flag it. If it exists but is empty or only has a heading — flag it as a stub.
Report format
After all checks, print a structured report:
## component-check: <ComponentName>
✅ PASS or ❌ FAIL — <N> violation(s)
### File structure
✅ All 4 required files present
❌ Missing: ComponentName.stories.tsx
### variants.ts
✅ Imports from @/lib/tv
❌ Contains JSX — move to .tsx
✅ ComponentSize exported via NonNullable<VariantProps<...>>
❌ size variant defined but ComponentSize not exported — add: export type ComponentSize = NonNullable<VariantProps<typeof component>['size']>
✅ All 5 sizes present (xs/sm/md/lg/xl)
❌ Missing sizes: xs, xl — add all 5 size keys to variants.size
✅ Text sizes match scale (xs→text-xs … xl→text-xl)
❌ md uses text-sm, lg uses text-base — fix to text-md and text-lg
✅ Dimensions consistent with standard scale
❌ md: h-9 (expected h-10), lg: px-4 (expected px-3) — fix to match standard scale
### ComponentName.tsx
✅ displayName set
❌ Defines tv() config — move to .variants.ts
✅ size prop uses ComponentSize type
❌ size prop uses inline literals — change to ComponentSize from ./ComponentName.variants
✅ JSDoc header present with variant/mode/size/example
❌ Missing JSDoc header — add /** … */ block before first import covering props, variants, @example
### Stories
✅ Default story present
❌ Missing tags: ['autodocs']
### index.ts
✅ Exports component + types + tv config + ComponentSize
❌ ComponentSize not re-exported — add to export type { ... } from './ComponentName.variants'
### src/index.ts
❌ ComponentName not exported — add to src/index.ts
❌ ComponentSize not exported — add to export type { ... } line for ComponentName
Each ❌ line must include a concrete fix hint so the user knows exactly what to change. Keep hints short (one line). If everything passes, say so and stop — no padding.