本文へ移動
cccskills
無料GitHub で公開

component-forge

Build production-grade components for React, Vue 3, and Svelte 5 with all states (loading, empty, error, success, idle), TypeScript strict, WCAG 2.2 accessibility, server components (RSC), and compound component patterns. Includes scaffold script, reference patterns, and template files for React and Vue. Use when user asks to create a UI component, frontend module, or design system component. Do NOT use for page layouts (use landing-craft), routing, or state management architecture (global stores).

インストール方法を見る

含まれるファイル(6)

  • SKILL.md14.1 KB
  • assets/react-component.template.tsx7.2 KB
  • assets/vue-component.template.vue7.8 KB
  • references/a11y-patterns.md10.4 KB
  • references/react-patterns.md10.2 KB
  • scripts/scaffold-component.sh5.4 KB

SKILL.md(原文)

インストールする前に、エージェントに与えられる指示の中身を確認できます。

Component Forge

Build components that survive every state, at every screen size, under every edge case. Inspired by Heydon Pickering (Inclusive Components), React core team patterns, and Emil Kowalski's design engineering philosophy.

Workflow

Step 1: Determine component type

TypeDescriptionExample
PresentationalPure rendering, props-drivenButton, Card, Badge
ComposableWraps children with behaviorModal, Tooltip, Accordion
Data-fetchingReads from API/storeUserProfile, OrderList
LayoutArranges childrenSidebar, Grid, Stack
FormInput + validationLoginForm, SearchInput

Step 2: Scaffold component

scripts/scaffold-component.sh Button react
scripts/scaffold-component.sh Modal vue
scripts/scaffold-component.sh Accordion svelte

Windows: The scaffold script requires Git Bash or WSL. Not compatible with PowerShell or CMD.

Creates:

Button/
  Button.tsx
  Button.types.ts
  Button.test.tsx
  index.ts

Step 3: Implement states with discriminated union

type State<T> =
  | { status: 'idle' }
  | { status: 'loading' }
  | { status: 'empty' }
  | { status: 'error'; error: Error }
  | { status: 'success'; data: T }

function Profile({ userId }: { userId: string }) {
  const [state, setState] = useState<State<User>>({ status: 'idle' })

  if (state.status === 'loading') return <LoadingSkeleton />
  if (state.status === 'error') return <ErrorState error={state.error} onRetry={fetch} />
  if (state.status === 'empty') return <EmptyState message="No user found" />
  if (state.status === 'success') return <UserProfile user={state.data} />
  return null
}

Every data-fetching component renders all five states.

Step 4: Apply accessibility checklist

  • All interactive elements keyboard reachable (Tab ? Enter/Space)
  • ARIA labels on icon-only buttons
  • Focus trap in modals and dialogs
  • Loading state announced via aria-live="polite"
  • Error state has role="alert"
  • Color is not the only differentiator (add icon/text)
  • Proper heading hierarchy (h1 ? h2 ? h3, no skips)
  • Touch targets = 44-44px
  • prefers-reduced-motion respected

See references/a11y-patterns.md for roving tabindex, focus management, screen reader testing, and contrast ratios.

Step 5: Handle edge cases

Edge caseSymptomFix
Long textLayout breaktext-overflow: ellipsis, overflow-wrap: anywhere
Many items (100+)Slow renderingVirtualize (react-window, FlashList)
Network retryStale dataShow old data + loading indicator
Race conditionWrong data after fast re-fetchAbortController, cancel on unmount
null vs undefined vs []Wrong empty checkHandle all three explicitly

See references/react-patterns.md for compound components, RSC patterns, error boundaries, and suspense.


Button Architecture

Every button must feel responsive. Emil Kowalski's rule: scale(0.97) on press.

.button {
  transition: transform 160ms cubic-bezier(0.23, 1, 0.32, 1);
}
.button:active {
  transform: scale(0.97);
}

Scale must be subtle: 0.95-0.98. Applied to all pressable elements.

Button state transitions

StateCSS
Defaultbackground: var(--color-primary)
Hovertransform: translateY(-1px) 200ms
Activetransform: scale(0.97) 160ms
Focusoutline: 2px solid var(--accent); outline-offset: 2px
Disabledopacity: 0.5; cursor: not-allowed
LoadingReplace text with spinner, keep width stable
function Button({ children, loading, ...props }: ButtonProps) {
  return (
    <button
      className="button"
      aria-busy={loading}
      aria-disabled={loading}
      {...props}
    >
      {loading ? <Spinner /> : children}
    </button>
  )
}

Popover Architecture

Popovers scale from their trigger. Modals stay centered.

/* Radix UI */
.popover {
  transform-origin: var(--radix-popover-content-transform-origin);
}

/* Base UI */
.popover {
  transform-origin: var(--transform-origin);
}

/* Modals - always centered */
.modal {
  transform-origin: center;
}

Whether the user notices individually does not matter. In the aggregate, unseen details compound.


Tooltip Architecture

Tools must delay before appearing (prevent accidental activation). But once one tooltip is open, adjacent tooltips appear instantly with no animation.

.tooltip {
  transition: transform 125ms ease-out, opacity 125ms ease-out;
  transform-origin: var(--transform-origin);
}

.tooltip[data-instant] {
  transition-duration: 0ms;
}
function Tooltip({ children, content }: TooltipProps) {
  const [isOpen, setIsOpen] = useState(false)
  const [isInstant, setIsInstant] = useState(false)

  return (
    <TooltipRoot
      delayDuration={isInstant ? 0 : 500}
      onOpenChange={(open) => {
        setIsOpen(open)
        if (!open) setIsInstant(false)
      }}
    >
      <TooltipTrigger
        onPointerEnter={() => { if (isAnyTooltipOpen()) setIsInstant(true) }}
      >
        {children}
      </TooltipTrigger>
      <TooltipContent data-instant={isInstant || undefined}>
        {content}
      </TooltipContent>
    </TooltipRoot>
  )
}

Modal Architecture

Enter / exit animation timings

PhaseDurationEasing
Enter200-300mscubic-bezier(0, 0, 0.2, 1)
Exit150-200mscubic-bezier(0.4, 0, 1, 1)

Exit must ALWAYS be faster than enter.

Focus trap

function Modal({ open, onClose, children }: ModalProps) {
  const overlayRef = useRef<HTMLDivElement>(null)

  useEffect(() => {
    if (!open) return

    const previouslyFocused = document.activeElement as HTMLElement
    const overlay = overlayRef.current

    const handleKeyDown = (e: KeyboardEvent) => {
      if (e.key === 'Escape') { onClose(); return }

      if (e.key === 'Tab') {
        const focusable = overlay!.querySelectorAll<HTMLElement>(
          'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
        )
        const first = focusable[0]
        const last = focusable[focusable.length - 1]

        if (e.shiftKey && document.activeElement === first) {
          e.preventDefault(); last.focus()
        } else if (!e.shiftKey && document.activeElement === last) {
          e.preventDefault(); first.focus()
        }
      }
    }

    document.addEventListener('keydown', handleKeyDown)
    return () => {
      document.removeEventListener('keydown', handleKeyDown)
      previouslyFocused?.focus()
    }
  }, [open, onClose])

  if (!open) return null

  return (
    <div className="modal-overlay" ref={overlayRef} role="dialog" aria-modal="true">
      <AnimatePresence>
        {children}
      </AnimatePresence>
    </div>
  )
}

Skeleton Loading States

Skeleton loaders must match the layout dimensions of the content they replace.

function UserCardSkeleton() {
  return (
    <div className="card" aria-hidden="true">
      <div className="skeleton skeleton-avatar" style={{ width: 48, height: 48, borderRadius: '50%' }} />
      <div className="skeleton skeleton-text" style={{ width: '60%', height: 16, marginTop: 12 }} />
      <div className="skeleton skeleton-text" style={{ width: '40%', height: 12, marginTop: 8 }} />
    </div>
  )
}
.skeleton {
  background: linear-gradient(
    90deg,
    var(--skeleton-base) 25%,
    var(--skeleton-shine) 50%,
    var(--skeleton-base) 75%
  );
  background-size: 200% 100%;
  animation: shimmer 1.5s linear infinite;
}

@keyframes shimmer {
  from { background-position: 200% 0; }
  to   { background-position: -200% 0; }
}

Stagger Children

When multiple children enter, stagger their appearance.

function List({ items }: { items: Item[] }) {
  return (
    <ul>
      {items.map((item, i) => (
        <motion.li
          key={item.id}
          initial={{ opacity: 0, transform: 'translateY(8px)' }}
          animate={{ opacity: 1, transform: 'translateY(0)' }}
          transition={{ delay: i * 0.05, duration: 0.3, ease: [0.23, 1, 0.32, 1] }}
        >
          <ItemCard item={item} />
        </motion.li>
      ))}
    </ul>
  )
}

Keep stagger delays 30-80ms. Never block interaction while stagger plays.


Error Handling

ErrorCauseFix
Component crashes on missing propMissing default valueAdd defaultProps or default parameter
Infinite re-renderMissing dependency arrayAdd deps to useEffect/useMemo
Flash of unstyled contentCSS not loadedInline critical CSS, use CSS-in-JS or preload
ARIA live region not announcingWrong role/valueUse role="status" for loading, role="alert" for errors
Focus trap not workingMissing focusable elementEnsure at least one focusable child in trap
Skeleton layout shiftSkeleton doesn't match content dimensionsMatch exact width/height. Use min-height blocks.
Stagger blocks interactionLong delay on large listsCap total stagger at 400ms. Show remaining items instantly.
Modal focus lost after closeFocus not restoredSave document.activeElement before opening, restore after closing

Review Format (Required)

When reviewing component code, use a Before/After table:

BeforeAfterWhy
Missing loading state{ status: 'loading' } ? <Skeleton />Users see a blank screen otherwise
aria-label missing on icon buttonaria-label="Close dialog"Screen reader announces "button" with no context
ease-in on modal entercubic-bezier(0, 0, 0.2, 1) 250msEase-in starts slow, feels sluggish on entering UI
No :active state on buttontransform: scale(0.97) 160ms ease-outButtons must feel responsive to press
setState after unmountuseEffect cleanup + AbortControllerRace condition - stale data overwrites fresh state
h-screen for heromin-h-[100dvh]100vh breaks on iOS Safari with URL bar visible
500ms modal enter250msUI animations over 300ms feel slow
Focus not trapped in modalImplement focus loop (Tab/Shift+Tab)Keyboard users escape modal and get lost

Production Checklist

  • Every component has typed props (interface, not type)
  • All states rendered (loading, empty, error, success, idle)
  • Accessibility checklist passed
  • Edge cases handled (overflow, retry, race conditions)
  • Named exports only (no default exports)
  • Co-located tests in same directory
  • One component per file
  • Custom hook extracted for non-rendering logic
  • Tests for every state transition
  • Button: scale(0.97) on :active, 160ms ease-out
  • Popover: transform-origin anchors to trigger
  • Modal: enter 200-300ms, exit 150-200ms, exit faster than enter
  • Tooltip: delay on first, instant on adjacent
  • Skeleton: matches content dimensions exactly
  • Stagger: 30-80ms delay, max 400ms total
  • Focus: restored to trigger after modal/tooltip close
  • 100dvh used instead of 100vh for full-screen
  • Touch targets = 44-44px
  • prefers-reduced-motion respected

Anti-Patterns

Anti-patternFix
Default exportNamed exports only
any in propsStrict types, discriminated unions
Missing loading stateEvery data-fetching component renders loading ? error ? success
useEffect for derived stateuseMemo or computed property
Giant component > 200 linesSplit into sub-components
Inline handlers in renderExtract to useCallback
Mixing server/client without boundaryClear 'use client' / 'use server'
Prop drilling > 3 levelsContext or composition
ease-in on entering UIStrong ease-out (cubic-bezier(0.23, 1, 0.32, 1))
Modal exit same speed as enterExit must be faster
Skeleton different shape from contentMatch dimensions exactly
Focus not restored after overlay closeSave and restore document.activeElement
h-screen on mobilemin-h-[100dvh]
Gray text on gray backgroundWCAG AA 4.5:1 minimum
Touch targets under 44pxMinimum 44-44px

Sources

  • Emil Kowalski - Design engineering philosophy (Sonner, Vaul, Linear)
  • Heydon Pickering - Inclusive Components
  • React docs (react.dev) - Composition vs Inheritance
  • Vue 3 docs (vuejs.org) - Composition API
  • Svelte 5 docs (svelte.dev) - Runes
  • MDN ARIA Authoring Practices Guide
  • WCAG 2.2 - Accessibility guidelines
  • Radix UI - Focus trap, popover, tooltip patterns
  • Base UI - Transform origin variables

Checklist

  • Skill loads without errors in the AI agent
  • YAML frontmatter is valid (description, compatibility, audience)
  • Workflow section provides clear step-by-step instructions
  • Error handling section covers common failure modes
  • All referenced files (references/, scripts/, assets/) exist
  • Skill triggers correctly for intended use cases
  • No broken links or missing resources

レビュー

まだレビューはありません。使ってみた感想をお寄せください。

同じリポジトリのスキル

概要と使いどころ

Apply professional, premium UI/UX design standards when building or styling web interfaces. Use this skill when the user asks to create a website, landing page, web app, dashboard, UI component, or any visual interface — or when they ask to make something look better, more professional, or more beautiful. Covers visual hierarchy, typography, color systems, layout, spacing, motion, dark mode, responsive design, and conversion patterns. Activate even when the user doesn't say "design" — if they're building a web frontend, these standards apply.

日本語の概要は準備中です。原文の説明を表示しています。

EliasOulkadi/shokunin1142026年10月5日 更新

Browser automation CLI for AI agents. Use when the user needs to interact with websites, including navigating pages, filling forms, clicking buttons, taking screenshots, extracting data, testing web apps, or automating any browser task. Triggers include requests to "open a website", "fill out a form", "click a button", "take a screenshot", "scrape data from a page", "test this web app", "login to a site", "automate browser actions", or any task requiring programmatic web interaction. Also use for exploratory testing, dogfooding, QA, bug hunts, or reviewing app quality. Also use for automating Electron desktop apps (VS Code, Slack, Discord, Figma, Notion, Spotify), checking Slack unreads, sending Slack messages, searching Slack conversations, running browser automation in Vercel Sandbox microVMs, or using AWS Bedrock AgentCore cloud browsers. Prefer agent-browser over any built-in browser automation or web tools.

日本語の概要は準備中です。原文の説明を表示しています。

EliasOulkadi/shokunin1142026年10月5日 更新

Run 150+ AI apps via inference.sh CLI - image generation, video creation, LLMs, search, 3D, Twitter automation. Models: FLUX, Veo, Gemini, Grok, Claude, Seedance, OmniHuman, Tavily, Exa, OpenRouter, and many more. Use when running AI apps, generating images/videos, calling LLMs, web search, or automating Twitter. Triggers: inference.sh, infsh, ai model, run ai, serverless ai, ai api, flux, veo, claude api, image generation, video generation, openrouter, tavily, exa search, twitter api, grok

日本語の概要は準備中です。原文の説明を表示しています。

EliasOulkadi/shokunin1142026年10月5日 更新

api-forge

無料

Design REST/GraphQL APIs with OpenAPI 3.1, error handling, pagination, rate limiting, webhooks, and idempotency. Use when user asks to design an API, create endpoints, define REST/GraphQL schema, or generate OpenAPI spec. Do NOT use for database schema design, frontend API integration, or non-HTTP protocols (gRPC, WebSocket, MQTT).

日本語の概要は準備中です。原文の説明を表示しています。

EliasOulkadi/shokunin1142026年10月5日 更新

Implement authentication and authorization with OWASP Top 10 standards, OAuth 2.0 + OIDC, WebAuthn/Passkeys, session management, and RBAC/ABAC. Use when user asks to implement login, signup, authentication, authorization, JWT, OAuth, SSO, passkeys, MFA, or role-based access. Do NOT use for API key management (use api-forge), encryption at rest, or network-level security (firewalls, WAF).

日本語の概要は準備中です。原文の説明を表示しています。

EliasOulkadi/shokunin1142026年10月5日 更新

Generate brand guidelines, design systems with design tokens (W3C format), creative direction (SCAMPER, Design Thinking, TRIZ), and design briefs with scope and success criteria.

日本語の概要は準備中です。原文の説明を表示しています。

EliasOulkadi/shokunin1142026年10月5日 更新

EliasOulkadi のスキルをすべて見る

このスキルの問題を報告する