Claude Code Plugins

Community-maintained marketplace

Feedback

|

Install Skill

1Download skill
2Enable skills in Claude

Open claude.ai/settings/capabilities and find the "Skills" section

3Upload to Claude

Click "Upload skill" and select the downloaded ZIP file

Note: Please verify skill by going through its instructions before using it.

SKILL.md

name ui-component
description Reusable UI component design and implementation methodology. Integrates design tokens, accessibility (a11y via W3C APG), and semantic HTML. Reference for building accessible, theme-compliant React components.

UI Component Skill

Component Design Principles

1. Design Token Integration

Use CSS custom properties from design tokens:

// ✅ Use CSS variables
const variantStyles = {
  primary: "bg-primary text-primary-foreground hover:bg-primary-hover",
  secondary: "bg-secondary text-secondary-foreground hover:bg-secondary-hover",
}

// ❌ Don't hardcode colors
const badStyles = {
  primary: "bg-cyan-600 text-white hover:bg-cyan-700",
}

2. Composition over Configuration

Prefer composable components over complex prop APIs:

// ✅ Composable
<Button iconBefore={<Plus />}>Add Item</Button>
<Button iconAfter={<ChevronRight />}>Next</Button>

// ❌ Over-configured
<Button icon="plus" iconPosition="left" iconSize="sm">
  Add Item
</Button>

3. Accessible by Default

Build accessibility into the component API:

interface IconButtonProps {
  icon: ReactNode
  "aria-label": string  // Required, not optional
  pressed?: boolean     // Maps to aria-pressed
}

// Forces accessible usage
<IconButton icon={<X />} aria-label="Close" />

Component Template

React 19 では forwardRef は非推奨です。ref を通常の props として受け取ります。

import type { ComponentProps, ReactNode, Ref } from "react"

export type ButtonVariant = "primary" | "secondary" | "ghost" | "destructive"
export type ButtonSize = "sm" | "md" | "lg"

export interface ButtonProps extends ComponentProps<"button"> {
  variant?: ButtonVariant
  size?: ButtonSize
  iconBefore?: ReactNode
  iconAfter?: ReactNode
  /** Ref to the button element */
  ref?: Ref<HTMLButtonElement>
}

const variantStyles: Record<ButtonVariant, string> = {
  primary: "bg-primary text-primary-foreground hover:bg-primary-hover",
  secondary: "bg-secondary text-secondary-foreground hover:bg-secondary-hover",
  ghost: "bg-transparent text-foreground hover:bg-accent",
  destructive: "bg-destructive text-destructive-foreground hover:bg-destructive-hover",
}

const sizeStyles: Record<ButtonSize, string> = {
  sm: "h-8 px-3 text-sm gap-1.5",
  md: "h-9 px-4 text-sm gap-2",
  lg: "h-10 px-5 text-base gap-2",
}

export function Button({
  variant = "primary",
  size = "md",
  className = "",
  ref,
  ...props
}: ButtonProps) {
  return (
    <button
      ref={ref}
      type="button"
      className={`
        inline-flex items-center justify-center font-medium rounded-md
        transition-colors
        focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring
        disabled:opacity-50 disabled:pointer-events-none
        ${variantStyles[variant]}
        ${sizeStyles[size]}
        ${className}
      `}
      {...props}
    />
  )
}

HTML Semantic Structure (WHATWG/W3C)

Reference: WHATWG HTML Living Standard

Sectioning Elements

Element Purpose Usage
<main> Primary content (one per page) App main content area
<nav> Navigation links Menu bar, sidebar navigation
<article> Self-contained content Cards, posts, widgets
<section> Thematic grouping with heading Content sections
<aside> Tangentially related content Sidebars, tooltips
<header> Introductory content Page/section header
<footer> Footer content Page/section footer

DOM Structure Examples

// Card with semantic structure
<article>
  <header>
    <h2>{title}</h2>
  </header>
  <div>{content}</div>
  <footer>{actions}</footer>
</article>

// Navigation menu
<nav aria-label="Main navigation">
  <ul role="menubar">
    <li role="none">
      <button role="menuitem">File</button>
    </li>
  </ul>
</nav>

// Page layout
<main>
  <h1>Page Title</h1>
  <section>
    <h2>Section Title</h2>
    {content}
  </section>
</main>
<aside aria-label="Sidebar">{sidebar}</aside>

Heading Hierarchy

Maintain logical heading structure:

// ✅ Correct hierarchy
<main>
  <h1>Application</h1>
  <section>
    <h2>Section</h2>
    <h3>Subsection</h3>
  </section>
</main>

// ❌ Skipped levels
<main>
  <h1>Application</h1>
  <h3>Section</h3>  {/* Skipped h2 */}
</main>

WAI-ARIA Patterns (W3C APG)

Reference: W3C ARIA Authoring Practices Guide

Button Pattern

Source: APG Button Pattern

// Standard button
<button type="button">Save</button>

// Icon-only button (requires aria-label)
<button type="button" aria-label="Close">
  <X aria-hidden="true" />
</button>

// Toggle button
<button type="button" aria-pressed={isPressed}>
  <Star aria-hidden="true" />
</button>

// Menu button
<button
  type="button"
  aria-haspopup="menu"
  aria-expanded={isOpen}
  aria-controls="menu-id"
>
  Options
</button>

Keyboard: Space / Enter activates button

Menu Button Pattern

Source: APG Menu Button Pattern

<div>
  <button
    aria-haspopup="menu"
    aria-expanded={isOpen}
    aria-controls="dropdown"
  >
    Options
  </button>
  {isOpen && (
    <ul id="dropdown" role="menu" aria-label="Options">
      <li role="none">
        <button role="menuitem">Copy</button>
      </li>
      <li role="none">
        <button role="menuitem">Paste</button>
      </li>
    </ul>
  )}
</div>

Keyboard:

  • Enter / Space / : Open menu, focus first item
  • : Open menu, focus last item
  • Escape: Close menu

Tabs Pattern

Source: APG Tabs Pattern

<div>
  <div role="tablist" aria-label="Settings">
    <button
      role="tab"
      id="tab-1"
      aria-selected={active === 0}
      aria-controls="panel-1"
      tabIndex={active === 0 ? 0 : -1}
    >
      General
    </button>
    <button
      role="tab"
      id="tab-2"
      aria-selected={active === 1}
      aria-controls="panel-2"
      tabIndex={active === 1 ? 0 : -1}
    >
      Advanced
    </button>
  </div>
  <div
    role="tabpanel"
    id="panel-1"
    aria-labelledby="tab-1"
    tabIndex={0}
    hidden={active !== 0}
  >
    General settings
  </div>
</div>

Keyboard:

  • Tab: Enter/exit tablist
  • / : Navigate tabs (horizontal)
  • Home / End: First/last tab

Dialog Pattern

Source: APG Dialog Pattern

<div
  role="dialog"
  aria-modal="true"
  aria-labelledby="dialog-title"
  aria-describedby="dialog-desc"
>
  <h2 id="dialog-title">Confirm</h2>
  <p id="dialog-desc">Are you sure?</p>
  <button>Cancel</button>
  <button>Confirm</button>
</div>

Keyboard:

  • Tab: Cycle focus within dialog (trapped)
  • Escape: Close dialog

Modern Web Platform APIs

Popover API (Native Popovers)

Use native popover for tooltips, menus, and dialogs without JS positioning:

// Trigger button
<button popovertarget="my-popover">Open Menu</button>

// Popover content (auto-positioned, light-dismiss)
<div id="my-popover" popover>
  <p>Popover content here</p>
</div>

Attributes:

  • popover / popover="auto": Light-dismiss (click outside closes)
  • popover="manual": Requires explicit close
  • popovertarget: Links button to popover
  • popovertargetaction: toggle (default), show, hide
// Controlled popover with React
function Menu() {
  const popoverRef = useRef<HTMLDivElement>(null)

  return (
    <>
      <button popovertarget="menu">Options</button>
      <div
        ref={popoverRef}
        id="menu"
        popover="auto"
        onToggle={(e) => console.log(e.newState)} // 'open' | 'closed'
      >
        <button onClick={() => popoverRef.current?.hidePopover()}>
          Close
        </button>
      </div>
    </>
  )
}

inert Attribute

Disable all interactions and remove from accessibility tree:

// Modal with inert background
<>
  <div inert={isModalOpen}>
    {/* Main content - disabled when modal is open */}
    <nav>...</nav>
    <main>...</main>
  </div>

  {isModalOpen && (
    <dialog open>
      {/* Only this is interactive */}
    </dialog>
  )}
</>

Use cases:

  • Modal backgrounds
  • Off-screen content in carousels
  • Collapsed accordion panels
  • Hidden drawer content

Element

Native modal with proper focus management:

function Dialog({ open, onClose, children }) {
  const dialogRef = useRef<HTMLDialogElement>(null)

  useEffect(() => {
    const dialog = dialogRef.current
    if (open) {
      dialog?.showModal()  // Traps focus, adds backdrop
    } else {
      dialog?.close()
    }
  }, [open])

  return (
    <dialog
      ref={dialogRef}
      onClose={onClose}
      onCancel={onClose}  // Escape key
    >
      {children}
    </dialog>
  )
}
/* Native backdrop styling */
dialog::backdrop {
  background: oklch(0% 0 0 / 50%);
  backdrop-filter: blur(4px);
}

A11y Requirements

Touch Targets (WCAG 2.5.8)

Minimum 24×24 CSS pixels:

const sizeStyles = {
  sm: "min-w-6 min-h-6",   // 24px minimum
  md: "min-w-8 min-h-8",   // 32px
  lg: "min-w-10 min-h-10", // 40px
}

Focus Ring (WCAG 2.4.7)

Visible focus indicator:

className="focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"

Color Contrast (WCAG 1.4.3 / 1.4.11)

Element Minimum Ratio
Normal text 4.5:1
Large text (18px+ or 14px bold) 3:1
UI components & graphics 3:1

ARIA Quick Reference

Component Required Attributes
Icon button aria-label
Toggle button aria-pressed
Expandable aria-expanded, aria-controls
Menu button aria-haspopup="menu", aria-expanded
Tab role="tab", aria-selected, aria-controls
Tablist role="tablist", aria-label
Tabpanel role="tabpanel", aria-labelledby
Dialog role="dialog", aria-modal, aria-labelledby
Menu role="menu", aria-label
Menuitem role="menuitem"

Disabled State

// Native disabled (removes from tab order)
<button disabled>Disabled</button>

// aria-disabled (stays in tab order, announced as disabled)
<button aria-disabled="true">Disabled</button>

Disabled Cursor Style

Use disabled:cursor-not-allowed instead of disabled:pointer-events-none for interactive elements.

pointer-events-none hides the cursor feedback, preventing users from understanding why the element is unclickable.

Reduced Motion (WCAG 2.3.3)

className="motion-safe:transition-all motion-reduce:transition-none"

Styling Patterns

Variant Styles Pattern

const variantStyles: Record<ButtonVariant, string> = {
  primary: "bg-primary text-primary-foreground",
  secondary: "bg-secondary text-secondary-foreground",
}

className={`${variantStyles[variant]} ${sizeStyles[size]}`}

CSS Grid for Internal Layout

<button className="grid grid-cols-[auto_1fr_auto] gap-2 items-center">
  {iconBefore}
  <span>{children}</span>
  {iconAfter}
</button>

Container Queries

<div className="@container">
  <div className="@sm:flex-row @md:grid-cols-3">
    {content}
  </div>
</div>

Export Pattern

// components/index.ts
export { Button } from "./Button"
export type { ButtonProps, ButtonVariant, ButtonSize } from "./Button"

// index.ts
export * from "./components"

Component Checklist

Design & Structure:

  • Uses design tokens (no hardcoded colors/spacing)
  • Uses semantic HTML elements
  • Maintains logical heading hierarchy
  • Accepts ref as a prop (React 19 pattern, not forwardRef)
  • Exports types alongside component

Accessibility (WAI-ARIA):

  • Follows W3C APG pattern for component type
  • Required ARIA attributes in props interface
  • Meets touch target requirements (24×24 min)
  • Has visible focus ring
  • Color contrast meets WCAG requirements
  • Works with keyboard navigation
  • Respects prefers-reduced-motion

Testing:

  • Storybook stories (variant/size/state showcases)
  • A11y tests with play functions
  • Passes a11y addon checks

References