smithery.ai

ui-component

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.

First seen Apr 5, 2026

Installation

$ npx skills add https://smithery.ai

Similar popular skills

Related neighbors and high-traction skills in the same topics — useful to compare before installing.

Also in this package

Other skills from smithery.ai · top by installs.

npx skills add https://smithery.ai

Browse all from smithery.ai

More details

Agent compatibility

Declared targets from SKILL.md / docs. Unmarked agents are not listed — the skill may still install via the CLI.

Claude Code Not declared
Cursor Not declared
Codex Not declared
GitHub Copilot Not declared
Windsurf Not declared
Gemini CLI Not declared
Cline Not declared
OpenCode Not declared

Package contents

Files included with this skill beyond the listing page.

  • skill md SKILL.md 12,776 B
  • docs SUMMARY.md 230 B

History

  1. First seen on skills.sh
  2. First recorded snapshot · 1 installs

SKILL.md

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

<dialog> 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