007
無料Security audit, hardening, threat modeling (STRIDE/PASTA), Red/Blue Team, OWASP checks, code review, incident response, and infrastructure security for any project.
日本語の概要は準備中です。原文の説明を表示しています。
Expert in web accessibility (WCAG 2.1/2.2 AA/AAA compliance), ARIA patterns, keyboard navigation, screen reader testing, color contrast, focus management, and automated accessibility testing
インストールする前に、エージェントに与えられる指示の中身を確認できます。
Expert skill for building accessible web applications that comply with WCAG 2.1/2.2 standards. Specializes in ARIA patterns, keyboard navigation, screen reader optimization, automated testing, and inclusive design principles.
Define Requirements
Audit Existing Code
Create Action Plan
Semantic HTML
Keyboard Support
Screen Reader Support
Visual Accessibility
Automated Testing
Manual Testing
Documentation
// AccessibleButton.tsx
import { forwardRef, ButtonHTMLAttributes } from 'react'
interface AccessibleButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
/**
* Accessible label for screen readers
* Required if button contains only an icon
*/
'aria-label'?: string
/**
* ID of element that labels this button
*/
'aria-labelledby'?: string
/**
* ID of element that describes this button
*/
'aria-describedby'?: string
/**
* Loading state (shows spinner, disables interaction)
*/
loading?: boolean
}
export const AccessibleButton = forwardRef<HTMLButtonElement, AccessibleButtonProps>(
({ children, loading, disabled, 'aria-label': ariaLabel, ...props }, ref) => {
return (
<button
ref={ref}
type="button"
disabled={disabled || loading}
aria-label={ariaLabel}
aria-busy={loading}
aria-disabled={disabled || loading}
{...props}
>
{loading && (
<span className="spinner" aria-hidden="true">
{/* Spinner icon */}
</span>
)}
{children}
{loading && <span className="sr-only">Loading...</span>}
</button>
)
}
)
AccessibleButton.displayName = 'AccessibleButton'
// Usage
<AccessibleButton onClick={handleClick}>
Save Changes
</AccessibleButton>
// Icon-only button (MUST have aria-label)
<AccessibleButton aria-label="Close dialog" onClick={onClose}>
<XIcon aria-hidden="true" />
</AccessibleButton>
// AccessibleModal.tsx
import { useEffect, useRef, ReactNode } from 'react'
import { createPortal } from 'react-dom'
import { useFocusTrap } from './hooks/useFocusTrap'
import { useEscapeKey } from './hooks/useEscapeKey'
interface AccessibleModalProps {
isOpen: boolean
onClose: () => void
title: string
children: ReactNode
/**
* ID for the modal title (for aria-labelledby)
*/
titleId?: string
/**
* ID for the modal description (for aria-describedby)
*/
descriptionId?: string
}
export function AccessibleModal({
isOpen,
onClose,
title,
children,
titleId = 'modal-title',
descriptionId,
}: AccessibleModalProps) {
const modalRef = useRef<HTMLDivElement>(null)
const previousActiveElement = useRef<HTMLElement | null>(null)
// Trap focus inside modal
useFocusTrap(modalRef, isOpen)
// Close on Escape
useEscapeKey(onClose, isOpen)
useEffect(() => {
if (isOpen) {
// Store currently focused element
previousActiveElement.current = document.activeElement as HTMLElement
// Prevent body scroll
document.body.style.overflow = 'hidden'
// Focus modal
modalRef.current?.focus()
} else {
// Restore body scroll
document.body.style.overflow = ''
// Restore focus to previous element
previousActiveElement.current?.focus()
}
return () => {
document.body.style.overflow = ''
}
}, [isOpen])
if (!isOpen) return null
return createPortal(
<div
className="modal-overlay"
onClick={onClose}
role="presentation"
>
<div
ref={modalRef}
role="dialog"
aria-modal="true"
aria-labelledby={titleId}
aria-describedby={descriptionId}
className="modal-content"
onClick={(e) => e.stopPropagation()}
tabIndex={-1}
>
<div className="modal-header">
<h2 id={titleId}>{title}</h2>
<button
onClick={onClose}
aria-label="Close dialog"
className="modal-close"
>
<span aria-hidden="true">×</span>
</button>
</div>
<div className="modal-body" id={descriptionId}>
{children}
</div>
</div>
</div>,
document.body
)
}
// Usage
<AccessibleModal
isOpen={isOpen}
onClose={() => setIsOpen(false)}
title="Confirm Action"
descriptionId="modal-desc"
>
<p id="modal-desc">Are you sure you want to delete this item?</p>
<button onClick={handleConfirm}>Confirm</button>
<button onClick={() => setIsOpen(false)}>Cancel</button>
</AccessibleModal>
// hooks/useFocusTrap.ts
import { useEffect, RefObject } from 'react'
const FOCUSABLE_ELEMENTS = [
'a[href]',
'button:not([disabled])',
'textarea:not([disabled])',
'input:not([disabled])',
'select:not([disabled])',
'[tabindex]:not([tabindex="-1"])',
].join(', ')
export function useFocusTrap(ref: RefObject<HTMLElement>, isActive: boolean) {
useEffect(() => {
if (!isActive) return
const element = ref.current
if (!element) return
const focusableElements = element.querySelectorAll<HTMLElement>(FOCUSABLE_ELEMENTS)
const firstFocusable = focusableElements[0]
const lastFocusable = focusableElements[focusableElements.length - 1]
const handleTabKey = (e: KeyboardEvent) => {
if (e.key !== 'Tab') return
if (e.shiftKey) {
// Shift + Tab
if (document.activeElement === firstFocusable) {
lastFocusable?.focus()
e.preventDefault()
}
} else {
// Tab
if (document.activeElement === lastFocusable) {
firstFocusable?.focus()
e.preventDefault()
}
}
}
element.addEventListener('keydown', handleTabKey)
return () => {
element.removeEventListener('keydown', handleTabKey)
}
}, [ref, isActive])
}
// AccessibleForm.tsx
import { useState, useId, FormEvent } from 'react'
export function AccessibleForm() {
const [email, setEmail] = useState('')
const [error, setError] = useState('')
const [success, setSuccess] = useState('')
const emailId = useId()
const errorId = useId()
const successId = useId()
const validateEmail = (value: string) => {
if (!value) {
setError('Email is required')
return false
}
if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value)) {
setError('Please enter a valid email address')
return false
}
setError('')
return true
}
const handleSubmit = (e: FormEvent) => {
e.preventDefault()
if (validateEmail(email)) {
setSuccess('Form submitted successfully!')
// Submit form
}
}
return (
<form onSubmit={handleSubmit} noValidate>
<div className="form-field">
<label htmlFor={emailId}>
Email Address <span aria-label="required">*</span>
</label>
<input
id={emailId}
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
onBlur={() => validateEmail(email)}
aria-invalid={!!error}
aria-describedby={error ? errorId : undefined}
aria-required="true"
autoComplete="email"
/>
{error && (
<div
id={errorId}
role="alert"
aria-live="polite"
className="error-message"
>
{error}
</div>
)}
</div>
{success && (
<div
id={successId}
role="status"
aria-live="polite"
className="success-message"
>
{success}
</div>
)}
<button type="submit">Submit</button>
</form>
)
}
// AccessibleTabs.tsx
import { useState, useRef, useEffect, KeyboardEvent } from 'react'
interface Tab {
id: string
label: string
content: React.ReactNode
}
interface AccessibleTabsProps {
tabs: Tab[]
defaultTab?: string
}
export function AccessibleTabs({ tabs, defaultTab }: AccessibleTabsProps) {
const [activeTab, setActiveTab] = useState(defaultTab || tabs[0].id)
const tabRefs = useRef<Map<string, HTMLButtonElement>>(new Map())
const handleKeyDown = (e: KeyboardEvent, currentIndex: number) => {
let newIndex = currentIndex
switch (e.key) {
case 'ArrowLeft':
newIndex = currentIndex > 0 ? currentIndex - 1 : tabs.length - 1
break
case 'ArrowRight':
newIndex = currentIndex < tabs.length - 1 ? currentIndex + 1 : 0
break
case 'Home':
newIndex = 0
break
case 'End':
newIndex = tabs.length - 1
break
default:
return
}
e.preventDefault()
const newTab = tabs[newIndex]
setActiveTab(newTab.id)
tabRefs.current.get(newTab.id)?.focus()
}
return (
<div className="tabs">
{/* Tab List */}
<div role="tablist" aria-label="Content tabs">
{tabs.map((tab, index) => {
const isActive = activeTab === tab.id
return (
<button
key={tab.id}
ref={(el) => {
if (el) tabRefs.current.set(tab.id, el)
}}
role="tab"
id={`tab-${tab.id}`}
aria-selected={isActive}
aria-controls={`panel-${tab.id}`}
tabIndex={isActive ? 0 : -1}
onClick={() => setActiveTab(tab.id)}
onKeyDown={(e) => handleKeyDown(e, index)}
className={isActive ? 'active' : ''}
>
{tab.label}
</button>
)
})}
</div>
{/* Tab Panels */}
{tabs.map((tab) => (
<div
key={tab.id}
role="tabpanel"
id={`panel-${tab.id}`}
aria-labelledby={`tab-${tab.id}`}
hidden={activeTab !== tab.id}
tabIndex={0}
>
{tab.content}
</div>
))}
</div>
)
}
/* sr-only.css */
.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;
}
.sr-only-focusable:focus,
.sr-only-focusable:active {
position: static;
width: auto;
height: auto;
overflow: visible;
clip: auto;
white-space: normal;
}
// Usage
<button>
<TrashIcon aria-hidden="true" />
<span className="sr-only">Delete item</span>
</button>
// SkipLinks.tsx
export function SkipLinks() {
return (
<div className="skip-links">
<a href="#main-content" className="skip-link">
Skip to main content
</a>
<a href="#navigation" className="skip-link">
Skip to navigation
</a>
<a href="#footer" className="skip-link">
Skip to footer
</a>
</div>
)
}
/* Skip link styles */
.skip-link {
position: absolute;
top: -40px;
left: 0;
background: #000;
color: #fff;
padding: 8px;
text-decoration: none;
z-index: 100;
}
.skip-link:focus {
top: 0;
}
// Button.test.tsx
import { render } from '@testing-library/react'
import { axe, toHaveNoViolations } from 'jest-axe'
import { Button } from './Button'
expect.extend(toHaveNoViolations)
describe('Button Accessibility', () => {
it('should not have accessibility violations', async () => {
const { container } = render(<Button>Click me</Button>)
const results = await axe(container)
expect(results).toHaveNoViolations()
})
it('should have accessible name', () => {
const { getByRole } = render(<Button>Click me</Button>)
expect(getByRole('button', { name: 'Click me' })).toBeInTheDocument()
})
it('icon-only button should have aria-label', async () => {
const { container } = render(
<Button aria-label="Close">
<XIcon />
</Button>
)
const results = await axe(container)
expect(results).toHaveNoViolations()
})
it('disabled button should have aria-disabled', () => {
const { getByRole } = render(<Button disabled>Click me</Button>)
expect(getByRole('button')).toHaveAttribute('aria-disabled', 'true')
})
})
// Form.test.tsx
import { render, screen } from '@testing-library/react'
import { userEvent } from '@testing-library/user-event'
describe('Form Accessibility', () => {
it('should have accessible form fields', () => {
render(<LoginForm />)
// Use accessible queries (getByRole, getByLabelText)
const emailInput = screen.getByLabelText(/email/i)
const passwordInput = screen.getByLabelText(/password/i)
const submitButton = screen.getByRole('button', { name: /log in/i })
expect(emailInput).toBeInTheDocument()
expect(passwordInput).toBeInTheDocument()
expect(submitButton).toBeInTheDocument()
})
it('should show validation errors with proper ARIA', async () => {
const user = userEvent.setup()
render(<LoginForm />)
const submitButton = screen.getByRole('button', { name: /log in/i })
await user.click(submitButton)
// Error should be announced to screen readers
const errorAlert = screen.getByRole('alert')
expect(errorAlert).toHaveTextContent(/email is required/i)
})
})
# .lighthouserc.yml
ci:
collect:
numberOfRuns: 3
startServerCommand: 'npm run start'
url:
- 'http://localhost:3000'
assert:
preset: 'lighthouse:recommended'
assertions:
# Accessibility score must be >= 90
'categories:accessibility':
- error
- minScore: 0.9
# Specific accessibility checks
'aria-allowed-attr': 'error'
'aria-required-attr': 'error'
'aria-valid-attr': 'error'
'button-name': 'error'
'color-contrast': 'error'
'document-title': 'error'
'html-has-lang': 'error'
'image-alt': 'error'
'label': 'error'
'link-name': 'error'
// ❌ BAD - Div soup
<div onClick={handleClick}>Click me</div>
// ✅ GOOD - Use button
<button onClick={handleClick}>Click me</button>
// ❌ BAD - Unnecessary ARIA
<button role="button" aria-label="Submit">Submit</button>
// ✅ GOOD - Native semantics
<button>Submit</button>
// ❌ BAD - Icon without label
<button><XIcon /></button>
// ✅ GOOD - Icon with label
<button aria-label="Close"><XIcon aria-hidden="true" /></button>
// ✅ Ensure all interactive elements are keyboard accessible
<div
role="button"
tabIndex={0}
onClick={handleClick}
onKeyDown={(e) => {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault()
handleClick()
}
}}
>
Custom button
</div>
// ✅ Manage focus in SPAs
useEffect(() => {
// Focus heading when page changes
headingRef.current?.focus()
}, [page])
Activate this skill when you need to:
// Agent Explore → Scan all components for a11y issues
// a11y-specialist → Apply fixes automatically
// Example workflow:
1. Agent finds: 50 buttons without accessible names
2. a11y-specialist adds aria-label to all
3. Agent verifies: All buttons now accessible
When implementing accessibility, provide:
Always build interfaces that are usable by everyone, regardless of ability.
まだレビューはありません。使ってみた感想をお寄せください。
概要と使いどころ
Security audit, hardening, threat modeling (STRIDE/PASTA), Red/Blue Team, OWASP checks, code review, incident response, and infrastructure security for any project.
日本語の概要は準備中です。原文の説明を表示しています。
Guides the creation of agile user stories and Gherkin feature files. Use when the user wants to create a user story, write acceptance criteria, define Gherkin scenarios, or author BDD feature files. This should trigger for requests such as Create a user story; Write a user story; I need to write a user story. Part of cursor-rules-java project
日本語の概要は準備中です。原文の説明を表示しています。
Facilitates conversational discovery to create Architectural Decision Records (ADRs) for non-functional requirements using the ISO/IEC 25010:2023 quality model. Use when the user wants to document quality attributes, NFR decisions, security/performance/scalability architecture, or design systems with measurable quality criteria. This should trigger for requests such as Create ADR for Non-functional requirements; Document Non-functional requirements; Capture Non-functional requirements; Generate Non-functional requirements in an ADR. Part of cursor-rules-java project
日本語の概要は準備中です。原文の説明を表示しています。
Run a health check on an existing project: dependency audit, security scan, test runner detection, CI/CD evaluation, and missing configuration analysis. Maps the three execution gates (pre/in/post) from /10x-bootstrapper to an assessment framework for existing codebases. Reads optional context/foundation/stack-assessment.md from /10x-stack-assess to focus checks on identified gaps. Writes context/foundation/health-check.md with findings, prioritized fixes, and an agent-readiness verdict. Use when the user has an existing project and wants to verify its health before working with an agent. Trigger phrases: "health check", "check my project", "audit my project", "is my project healthy", "sprawdź projekt", "audyt projektu", "health-check", "project health". Use AFTER /10x-stack-assess (brownfield chain), BEFORE agent onboarding (m1-l4).
日本語の概要は準備中です。原文の説明を表示しています。
You MUST use this when building projects end-to-end. Orchestrates all 12 team roles — automatically switches between CTO, architect, PM, engineers, SRE, security, DBA, QA, and EM based on the current phase of work. Starts with brainstorming before any implementation.
日本語の概要は準備中です。原文の説明を表示しています。
Use when you need to add or configure Maven plugins in your pom.xml — including quality tools (enforcer, surefire, failsafe, jacoco, pitest, spotbugs, pmd), security scanning (OWASP), code formatting (Spotless), version management, container image build (Jib), build information tracking, and benchmarking (JMH) — through a consultative, modular step-by-step approach that only adds what you actually need. This should trigger for requests such as Add Maven plugins in pom.xml; Improve Maven plugins in pom.xml. Part of cursor-rules-java project
日本語の概要は準備中です。原文の説明を表示しています。