Back to list
CeamKrier

accessibility

by CeamKrier

Browser-based tool to combine and format multiple files for optimal use with AI language models (ChatGPT, Claude, etc.).

2🍴 0📅 Jan 17, 2026

SKILL.md


name: accessibility description: Implement web accessibility (a11y) patterns including ARIA attributes, keyboard navigation, focus management, and screen reader support. Use for any UI component or interactive element.

Accessibility (a11y)

When to Use This Skill

Use when:

  • Building interactive components (modals, dropdowns, tabs)
  • Implementing keyboard navigation
  • Adding screen reader support
  • Ensuring color contrast compliance

Semantic HTML First

// ❌ Non-semantic
<div onClick={handleClick}>Click me</div>

// ✅ Semantic
<button onClick={handleClick}>Click me</button>

ARIA Attributes

Common ARIA Patterns

// Button with loading state
<button
  aria-busy={isLoading}
  aria-disabled={isLoading}
>
  {isLoading ? 'Loading...' : 'Submit'}
</button>

// Expandable section
<button
  aria-expanded={isOpen}
  aria-controls="panel-1"
>
  Toggle Panel
</button>
<div id="panel-1" hidden={!isOpen}>
  Panel content
</div>

// Live regions (for dynamic updates)
<div aria-live="polite" aria-atomic="true">
  {statusMessage}
</div>

Dialog/Modal Pattern

function Modal({ isOpen, onClose, title, children }: ModalProps) {
  const modalRef = useRef<HTMLDivElement>(null);

  useEffect(() => {
    if (isOpen) {
      modalRef.current?.focus();
    }
  }, [isOpen]);

  return isOpen ? (
    <div
      ref={modalRef}
      role="dialog"
      aria-modal="true"
      aria-labelledby="modal-title"
      tabIndex={-1}
    >
      <h2 id="modal-title">{title}</h2>
      {children}
      <button onClick={onClose}>Close</button>
    </div>
  ) : null;
}

Keyboard Navigation

Focus Trap for Modals

function useFocusTrap(isActive: boolean) {
  const containerRef = useRef<HTMLDivElement>(null);

  useEffect(() => {
    if (!isActive) return;

    const container = containerRef.current;
    if (!container) return;

    const focusableElements = container.querySelectorAll<HTMLElement>(
      'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
    );

    const firstElement = focusableElements[0];
    const lastElement = focusableElements[focusableElements.length - 1];

    const handleKeyDown = (e: KeyboardEvent) => {
      if (e.key !== 'Tab') return;

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

    container.addEventListener('keydown', handleKeyDown);
    firstElement?.focus();

    return () => container.removeEventListener('keydown', handleKeyDown);
  }, [isActive]);

  return containerRef;
}

Arrow Key Navigation

function useArrowNavigation(items: HTMLElement[]) {
  const handleKeyDown = useCallback((e: KeyboardEvent) => {
    const currentIndex = items.findIndex(
      item => item === document.activeElement
    );

    let nextIndex = currentIndex;

    switch (e.key) {
      case 'ArrowDown':
      case 'ArrowRight':
        nextIndex = (currentIndex + 1) % items.length;
        break;
      case 'ArrowUp':
      case 'ArrowLeft':
        nextIndex = (currentIndex - 1 + items.length) % items.length;
        break;
      case 'Home':
        nextIndex = 0;
        break;
      case 'End':
        nextIndex = items.length - 1;
        break;
      default:
        return;
    }

    e.preventDefault();
    items[nextIndex]?.focus();
  }, [items]);

  return handleKeyDown;
}

Focus Management

// Skip link pattern
<a href="#main-content" className="sr-only focus:not-sr-only">
  Skip to main content
</a>

// Focus visible styling (Tailwind)
<button className="focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500">
  Click me
</button>

Screen Reader Only Content

/* Tailwind: sr-only */
.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border-width: 0;
}
<button>
  <Icon name="trash" />
  <span className="sr-only">Delete item</span>
</button>

Color Contrast

  • Normal text: 4.5:1 contrast ratio minimum
  • Large text (18px+): 3:1 contrast ratio minimum
  • UI components: 3:1 contrast ratio minimum

Accessibility Checklist

  • All interactive elements are keyboard accessible
  • Focus order is logical and visible
  • Images have alt text (or alt="" for decorative)
  • Form inputs have associated labels
  • Color is not the only way to convey information
  • Modals trap focus and can be closed with Escape
  • Dynamic content updates are announced to screen readers

Score

Total Score

65/100

Based on repository quality metrics

SKILL.md

SKILL.mdファイルが含まれている

+20
LICENSE

ライセンスが設定されている

0/10
説明文

100文字以上の説明がある

+10
人気

GitHub Stars 100以上

0/15
最近の活動

1ヶ月以内に更新

+10
フォーク

10回以上フォークされている

0/5
Issue管理

オープンIssueが50未満

+5
言語

プログラミング言語が設定されている

+5
タグ

1つ以上のタグが設定されている

+5

Reviews

💬

Reviews coming soon