From ux-harness
Scaffold a new component following the UX Harness standard. Calls ux-prime first to search Storybook for existing components (reduce, reuse, recycle). If a new component is needed, scaffolds: test file FIRST (TDD), component file (150-line cap, Tailwind only, strong types), types file, and hook for business logic. All files reference design system tokens. Optionally promotes to Storybook via ux-story-writer. Works on any React/Tailwind frontend codebase. Triggers on: "build a component", "create a component", "scaffold a component", "new component", "ux-component-builder", or any request to create UI elements.
How this skill is triggered — by the user, by Claude, or both
Slash command
/ux-harness:ux-component-builderThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Scaffold a new component following every UX Harness rule. Test first, build second,
Scaffold a new component following every UX Harness rule. Test first, build second, reuse always.
Announce at start: "Building component with UX Harness standard..."
Run ux-prime to load:
Before writing ANY code, search Storybook using these concrete strategies:
Search story file names and component titles for the requested component name or synonyms (e.g., "card" also matches "tile", "panel").
If the user described what the component should accept (e.g., "a card with a title and a metric value"), search existing component argTypes for matching prop shapes.
Scan the appropriate tier based on the request:
Search component JSDoc comments and story descriptions for keywords from the request.
Based on search results, select EXACTLY ONE path.
🛑 PROGRESSIVE DISCLOSURE GATE: Present search results and your recommendation to the user before proceeding. Wait for confirmation.
Storybook search results:
✓ Found MetricsCard (Widget Garage) — similar prop shape, but missing trend indicator
✗ No exact match
Recommendation: Extend MetricsCard with a trend prop, or scaffold a new TrendCard.
Your call — extend or build new?
Before scaffolding, ask yourself: Is this the simplest version that's complete?
Don't gold-plate. Build what's needed now. Extend later when the need is real.
What Storybook section does this belong in?
Does it need business logic?
useComponentName.ts)Does it need API data?
Does it need its own types file?
Always write the test file before the component file.
Create ComponentName.test.tsx:
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { ComponentName } from './ComponentName';
describe('ComponentName', () => {
it('renders with default props', () => {
render(<ComponentName />);
// Assert the component renders its primary content
});
it('handles [primary interaction]', async () => {
const user = userEvent.setup();
const onAction = jest.fn();
render(<ComponentName onAction={onAction} />);
// Assert interaction behavior
});
it('renders empty state', () => {
render(<ComponentName data={[]} />);
// Assert empty state is shown
});
it('renders error state', () => {
render(<ComponentName error="Something went wrong" />);
// Assert error state is shown
});
});
Test rules:
Run the tests. They must FAIL. If they pass, you're testing existing behavior — fix the test.
If the component needs a separate types file, create ComponentName.types.ts:
export interface ComponentNameProps {
/** Brief description of this prop */
variant?: 'primary' | 'secondary' | 'ghost';
/** Brief description of this prop */
size?: 'sm' | 'md' | 'lg';
/** Brief description of this prop */
disabled?: boolean;
/** Callback when [action] occurs */
onAction?: () => void;
/** Content to render inside the component */
children: React.ReactNode;
}
Type rules:
any. Ever. In any file created by this skill.string.React.ReactNode for children, not any or string.If business logic is needed, create useComponentName.ts:
import { useState, useCallback } from 'react';
// import from service layer if API calls needed
// import { featureService } from '@services/featureService';
interface UseComponentNameReturn {
// Typed return value — no any
}
export const useComponentName = (): UseComponentNameReturn => {
// State, callbacks, effects — all business logic lives here
// Component file only calls this hook, never contains logic directly
};
Hook rules:
Create ComponentName.tsx:
import { ComponentNameProps } from './ComponentName.types';
// import { useComponentName } from './useComponentName';
export const ComponentName: React.FC<ComponentNameProps> = ({
variant = 'primary',
size = 'md',
disabled = false,
onAction,
children,
}) => {
// const { ... } = useComponentName();
return (
<div className="flex items-center gap-sm rounded-md bg-background-default p-lg">
{children}
</div>
);
};
Component rules:
style={{}}, no CSS imports. Only Tailwind classes from the design system.bg-background-primary not bg-[#0284c7] or bg-blue-500. Use text-text-weak not text-gray-500. Every color class must come from the semantic token system.{isError ? <ErrorState /> : <Content />} is fine. 10 lines of conditional JSX is not — make it a component.Evidence before claims. Run these commands and read the output before claiming completion.
wc -l on every new file. All must be under 150 (200 hard cap).any. grep -n ': any\|as any' [files]. Must be zero.grep -n 'bg-\[\|text-\[\|w-\[\|h-\[' [files]. Must be zero.grep -n 'bg-blue-\|bg-red-\|bg-green-\|bg-gray-\|text-blue-\|text-red-\|text-green-\|text-gray-\|border-blue-\|border-red-\|border-gray-' [files]. Must be zero — use semantic token classes instead.🛑 Do NOT claim "tests pass" without running them. Do NOT claim "no any types" without grepping. Evidence before assertions.
🛑 HARD GATE: Do NOT auto-promote to Storybook. Ask the user first.
Component [ComponentName] is ready. Should it be added to Storybook?
- "yes" → I'll draft a story for your review (you'll approve before it's written)
- "no" → Component stays page-specific, no story created
Wait for explicit "yes" before calling ux-story-writer. Do NOT assume. Do NOT auto-call.
If yes → Run ux-story-writer, which will draft the story and present it for a second approval before writing to disk. Two gates: one here (should we make a story?) and one in ux-story-writer (does this story look right?).
If no → Component is page-specific and doesn't need a story. Done.
A complete component scaffold looks like:
src/components/[feature]/ComponentName/
ComponentName.test.tsx ← written FIRST
ComponentName.types.ts ← if props > 5 properties
useComponentName.ts ← if business logic needed
ComponentName.tsx ← written LAST
Or for Storybook components:
src/components/common/ComponentName/
ComponentName.test.tsx
ComponentName.types.ts
useComponentName.ts
ComponentName.tsx
src/stories/[Tier]/
ComponentName.stories.tsx ← via ux-story-writer
Creates, edits, and optimizes skills for Claude Code, including drafting, evaluating with test prompts, iterating on performance, and improving skill descriptions for better triggering accuracy.
npx claudepluginhub jakecjones/ux-harness --plugin ux-harness