Token导航 LogoToken导航TokenDH.com
前端设计只读github未标认证来源可访问clear审计通过

accessibility-patterns可访问性模式

Agent Skill

用于辅助无障碍访问检查、页面可用性审计和前端可访问性改进。它适合让 Agent 检查语义标签、键盘操作、颜色对比、ARIA 属性和自动化检测结果。使用时需要结合真实页面和浏览器验证,不应只依赖静态文本判断;涉及修复建议时,应兼顾设计系统、组件复用和 WCAG 等通用无障碍规范。

总安装

364

周安装

15

GitHub Stars

8

下载量

119
CodexClaudeCursorGemini CLI

安装说明

本站只整理中文说明和来源信息,不托管安装包,也不代用户安装。

GitHub

来源数

3

许可证

MIT

最后核验

2026-05-01

来源状态

来源可访问

安装方式

通过对话安装

复制提示词发给支持本地命令或 Skills 的 AI 助手,先确认命令和权限,再让它执行。

请帮我安装这个 Agent Skill:accessibility-patterns(可访问性模式)
来源仓库:https://github.com/kaakati/rails-enterprise-dev
仓库路径:skills/accessibility-patterns
安装命令:
npx skills add https://github.com/kaakati/rails-enterprise-dev --skill accessibility-patterns
安装前请先检查当前环境是否支持对应 CLI,并向我确认将要执行的命令、安装目录、联网范围和文件读写权限;确认后再执行。

命令行安装

复制命令到本机终端执行。不同来源提供的安装方式可能略有差异;本站展示可直接复制的安装命令,安装前请核对来源页面。

skills.shnpx skills
npx skills add https://github.com/kaakati/rails-enterprise-dev --skill accessibility-patterns

简介

用于辅助无障碍访问检查、页面可用性审计和前端可访问性改进。

  • 适合检查语义标签、键盘操作、颜色对比、ARIA 属性和自动化检测结果。
  • 使用时需结合真实页面和浏览器验证,避免仅依赖静态文本判断。
  • 安装方式:通过 npx skills add 命令添加指定 GitHub 仓库路径。
  • accessibility-patterns 属于前端设计类 Skill,可作为该场景下的辅助能力补充。

SKILL.md

Accessibility Patterns — Expert Decisions

Expert decision frameworks for accessibility choices. Claude knows accessibilityLabel and VoiceOver — this skill provides judgment calls for element grouping, label strategies, and compliance trade-offs.


Decision Trees

Element Grouping Strategy

How should VoiceOver read this content?
├─ Logically related (card, cell, profile)
│  └─ Combine: .accessibilityElement(children: .combine)
│     Read as single unit
│
├─ Each part independently actionable
│  └─ Keep separate
│     User needs to interact with each
│
├─ Container with multiple actions
│  └─ Combine + custom actions
│     Single element with .accessibilityAction
│
├─ Decorative image with text
│  └─ Combine, image hidden
│     Image adds no meaning
│
└─ Image conveys different info than text
   └─ Keep separate with distinct labels
      Both need to be announced

The trap: Combining elements that have different actions. User can't interact with individual parts.

Label vs Hint Decision

What should be in label vs hint?
├─ What the element IS
│  └─ Label
│     "Play button", "Submit form"
│
├─ What happens when activated
│  └─ Hint (only if not obvious)
│     "Double tap to start playback"
│
├─ Current state
│  └─ Value
│     "50 percent", "Page 3 of 10"
│
└─ Control behavior
   └─ Traits
      .isButton, .isSelected, .isHeader

Dynamic Type Layout Strategy

How should layout adapt to larger text?
├─ Simple HStack (icon + text)
│  └─ Stay horizontal
│     Icons scale with text
│
├─ Complex HStack (image + multi-line)
│  └─ Stack vertically at xxxLarge
│     Check @Environment(\.dynamicTypeSize)
│
├─ Fixed-height cells
│  └─ Self-sizing
│     Remove height constraints
│
└─ Toolbar/navigation elements
   └─ Consider overflow menu
      Or scroll at extreme sizes

Reduce Motion Response

What happens when Reduce Motion is enabled?
├─ Transition between screens
│  └─ Instant or simple fade
│     No slide/zoom animations
│
├─ Loading indicators
│  └─ Static or minimal
│     No bouncing/spinning
│
├─ Autoplay video/animation
│  └─ Don't autoplay
│     User controls playback
│
├─ Parallax/motion effects
│  └─ Disable completely
│     Can cause vestibular issues
│
└─ Essential animation (progress)
   └─ Keep but simplify
      Linear, no bounce

NEVER Do

VoiceOver Labels

NEVER include element type in labels:

// ❌ Redundant — VoiceOver announces "Submit button, button"
Button("Submit") { }
    .accessibilityLabel("Submit button")

// ✅ VoiceOver announces "Submit, button"
Button("Submit") { }
    .accessibilityLabel("Submit")

// ❌ Redundant — "Profile image, image"
Image("profile")
    .accessibilityLabel("Profile image")

// ✅ Describe what the image shows
Image("profile")
    .accessibilityLabel("John Doe's profile photo")

NEVER use generic labels:

// ❌ User has no idea what this does
Button(action: deleteItem) {
    Image(systemName: "trash")
}
.accessibilityLabel("Button")

// ❌ Still not helpful
Button(action: deleteItem) {
    Image(systemName: "trash")
}
.accessibilityLabel("Icon")

// ✅ Describe the action
Button(action: deleteItem) {
    Image(systemName: "trash")
}
.accessibilityLabel("Delete \(item.name)")

NEVER forget to label icon-only buttons:

// ❌ VoiceOver says nothing useful
Button(action: share) {
    Image(systemName: "square.and.arrow.up")
}
// VoiceOver: "Button" (no label!)

// ✅ Always label icon buttons
Button(action: share) {
    Image(systemName: "square.and.arrow.up")
}
.accessibilityLabel("Share")

Element Visibility

NEVER hide interactive elements from accessibility:

// ❌ User can't access this control
Button("Settings") { }
    .accessibilityHidden(true)  // Why would you do this?

// ✅ Every interactive element must be accessible
// Only hide truly decorative elements
Image("decorative-pattern")
    .accessibilityHidden(true)  // This is OK — adds nothing

NEVER leave decorative images accessible:

// ❌ VoiceOver reads meaningless "image"
Image("background-gradient")
// VoiceOver: "Image"

// ✅ Hide decorative elements
Image("background-gradient")
    .accessibilityHidden(true)

Dynamic Type

NEVER use fixed font sizes for user content:

// ❌ Doesn't respect user's text size preference
Text("Hello, World!")
    .font(.system(size: 16))  // Never scales!

// ✅ Use Dynamic Type styles
Text("Hello, World!")
    .font(.body)  // Scales automatically

// ✅ Custom font with scaling
Text("Custom")
    .font(.custom("MyFont", size: 16, relativeTo: .body))

NEVER truncate text at larger sizes without alternative:

// ❌ Content disappears at larger text sizes
Text(longContent)
    .lineLimit(2)
    .font(.body)
// At xxxLarge, user sees "Lorem ips..."

// ✅ Allow expansion or provide full content path
Text(longContent)
    .lineLimit(dynamicTypeSize >= .xxxLarge ? nil : 2)
    .font(.body)

// Or use "Read more" expansion

Reduce Motion

NEVER ignore reduce motion for essential navigation:

// ❌ User with vestibular disorders feels sick
.transition(.slide)
// Reduce Motion enabled, but still slides

// ✅ Respect reduce motion
@Environment(\.accessibilityReduceMotion) var reduceMotion

.transition(reduceMotion ? .opacity : .slide)

NEVER autoplay video when reduce motion is enabled:

// ❌ Autoplay ignores user preference
VideoPlayer(player: player)
    .onAppear { player.play() }  // Always autoplays

// ✅ Check reduce motion
VideoPlayer(player: player)
    .onAppear {
        if !UIAccessibility.isReduceMotionEnabled {
            player.play()
        }
    }

Color and Contrast

NEVER convey information by color alone:

// ❌ Color-blind users can't distinguish states
Circle()
    .fill(isOnline ? .green : .red)  // Only color differs

// ✅ Use shape/icon in addition to color
HStack {
    Circle()
        .fill(isOnline ? .green : .red)
    Text(isOnline ? "Online" : "Offline")
}
// Or
Image(systemName: isOnline ? "checkmark.circle.fill" : "xmark.circle.fill")
    .foregroundColor(isOnline ? .green : .red)

Essential Patterns

Accessible Card Component

struct AccessibleCard: View {
    let item: Item
    let onTap: () -> Void
    let onDelete: () -> Void
    let onShare: () -> Void

    var body: some View {
        VStack(alignment: .leading, spacing: 8) {
            Text(item.title)
                .font(.headline)

            Text(item.description)
                .font(.body)
                .foregroundColor(.secondary)

            Text(item.date, style: .date)
                .font(.caption)
        }
        .padding()
        .background(Color(.systemBackground))
        .cornerRadius(12)

        // Combine all text for VoiceOver
        .accessibilityElement(children: .combine)
        .accessibilityLabel("\(item.title). \(item.description). \(item.date.formatted())")
        .accessibilityAddTraits(.isButton)

        // Custom actions instead of hidden buttons
        .accessibilityAction(.default) { onTap() }
        .accessibilityAction(named: "Delete") { onDelete() }
        .accessibilityAction(named: "Share") { onShare() }
    }
}

Dynamic Type Adaptive Layout

struct AdaptiveProfileView: View {
    @Environment(\.dynamicTypeSize) private var dynamicTypeSize

    let user: User

    var body: some View {
        if dynamicTypeSize.isAccessibilitySize {
            // Vertical layout for accessibility sizes
            VStack(alignment: .leading, spacing: 12) {
                profileImage
                userInfo
            }
        } else {
            // Horizontal layout for standard sizes
            HStack(spacing: 16) {
                profileImage
                userInfo
            }
        }
    }

    private var profileImage: some View {
        Image(user.avatarName)
            .resizable()
            .scaledToFill()
            .frame(width: imageSize, height: imageSize)
            .clipShape(Circle())
            .accessibilityLabel("\(user.name)'s profile photo")
    }

    private var userInfo: some View {
        VStack(alignment: .leading, spacing: 4) {
            Text(user.name)
                .font(.headline)
            Text(user.title)
                .font(.subheadline)
                .foregroundColor(.secondary)
        }
    }

    private var imageSize: CGFloat {
        dynamicTypeSize.isAccessibilitySize ? 80 : 60
    }
}

extension DynamicTypeSize {
    var isAccessibilitySize: Bool {
        self >= .accessibility1
    }
}

Reduce Motion Wrapper

struct MotionSafeAnimation<Content: View>: View {
    @Environment(\.accessibilityReduceMotion) private var reduceMotion

    let fullAnimation: Animation
    let reducedAnimation: Animation
    let content: Content

    init(
        full: Animation = .spring(),
        reduced: Animation = .linear(duration: 0.2),
        @ViewBuilder content: () -> Content
    ) {
        self.fullAnimation = full
        self.reducedAnimation = reduced
        self.content = content()
    }

    var body: some View {
        content
            .animation(reduceMotion ? reducedAnimation : fullAnimation, value: UUID())
    }
}

// Usage
struct AnimatedButton: View {
    @State private var isPressed = false
    @Environment(\.accessibilityReduceMotion) private var reduceMotion

    var body: some View {
        Button("Tap Me") { }
            .scaleEffect(isPressed ? 0.95 : 1.0)
            .animation(reduceMotion ? nil : .spring(), value: isPressed)
            .onLongPressGesture(minimumDuration: .infinity, pressing: { pressing in
                isPressed = pressing
            }, perform: {})
    }
}

Accessible Form

struct AccessibleForm: View {
    @State private var email = ""
    @State private var password = ""
    @State private var emailError: String?
    @FocusState private var focusedField: Field?

    enum Field: Hashable {
        case email, password
    }

    var body: some View {
        Form {
            Section {
                TextField("Email", text: $email)
                    .focused($focusedField, equals: .email)
                    .textContentType(.emailAddress)
                    .keyboardType(.emailAddress)
                    .accessibilityLabel("Email address")
                    .accessibilityValue(email.isEmpty ? "Empty" : email)

                if let error = emailError {
                    Text(error)
                        .font(.caption)
                        .foregroundColor(.red)
                        .accessibilityLabel("Error: \(error)")
                }

                SecureField("Password", text: $password)
                    .focused($focusedField, equals: .password)
                    .textContentType(.password)
                    .accessibilityLabel("Password")
                    .accessibilityHint("Minimum 8 characters")
            }

            Button("Sign In") {
                signIn()
            }
            .accessibilityLabel("Sign in")
            .accessibilityHint("Double tap to sign in with entered credentials")
        }
        .onSubmit {
            switch focusedField {
            case .email:
                focusedField = .password
            case .password:
                signIn()
            case nil:
                break
            }
        }
        .onChange(of: emailError) { _, error in
            if error != nil {
                // Announce error to VoiceOver
                UIAccessibility.post(notification: .announcement,
                    argument: "Error: \(error ?? "")")
            }
        }
    }
}

Quick Reference

WCAG AA Requirements

CriterionRequirementiOS Implementation
1.4.3 Contrast4.5:1 normal, 3:1 largeUse semantic colors
1.4.4 Resize Text200% without lossDynamic Type support
2.1.1 KeyboardAll functionalityVoiceOver navigation
2.4.7 Focus VisibleClear focus indicator@FocusState
2.5.5 Target Size44x44pt minimum.frame(minWidth:minHeight:)

Accessibility Traits

TraitWhen to Use
.isButtonCustom tappable views
.isHeaderSection titles
.isSelectedCurrently selected item
.isLinkNavigates to URL
.isImageMeaningful images
.playsSoundAudio triggers
.startsMediaSessionVideo/audio playback
.adjustableSwipe up/down to change value

Focus Notifications

NotificationUse Case
.screenChangedMajor UI change, new screen
.layoutChangedMinor UI update
.announcementStatus message
.pageScrolledScroll position changed

Red Flags

SmellProblemFix
"Button" in labelRedundantRemove type from label
Icon without labelInaccessibleAdd accessibilityLabel
.accessibilityHidden(true) on controlCan't interactRemove or rethink
.font(.system(size:))Doesn't scaleUse.font(.body)
Color-only statusColor-blind exclusionAdd icon or text
Animation ignores reduceMotionVestibular issuesCheck environment
Decorative image without hiddenNoisy VoiceOveraccessibilityHidden(true)
Combined elements with separate actionsCan't interact individuallyKeep separate or use custom actions

适合场景

01

用户想查找某类 Agent Skill 时

02

需要根据任务场景推荐可安装能力包时

03

需要对比不同来源的安装命令和来源信息时

04

需要参考平台分布和安装热度时

能力概览

能力 1

按任务关键词查找相关 Skills

能力 2

展示可复制的安装命令

能力 3

保留来源站点、仓库和原始说明,方便继续核验

能力 4

补充不同宿主或平台的使用分布数据

能力 5

展示第三方安全扫描或审计结果

安装后应在对应宿主中按原始 README 的触发条件使用;具体调用方式请以来源页面和 README 为准。

平台分布

Antigravity

27.53%
按下载量换算33

windsurf

19.77%
按下载量换算24

Claude Code

18.98%
按下载量换算23

OpenCode

13.42%
按下载量换算16

Gemini CLI

7.9%
按下载量换算9

Codex

3.63%
按下载量换算4

安全审计

Gen Agent Trust Hub

通过

Socket

通过

Snyk

通过

权限和风险

只读

该 Skill 主要提供规则、说明或参考内容,本身偏只读;真正读写文件、联网或执行命令仍取决于宿主 Agent 的任务。

安装前确认

本站仅展示第三方公开信息,不托管安装包,不提供自动安装或运行环境。安装前应自行审查源码、依赖和命令行为。

来源信息

继续浏览同类 Skills