mcpField
T
typo3-accessibility

typo3-accessibility

github.com/dirnbauer设计与 UI3348 次安装13 周前更新
代码仓库

关于 typo3-accessibility

TYPO3 Accessibility 是面向 TYPO3 v14 网站的技能,用于依据 WCAG 2.2 AA 审查和改进无障碍性。它结合上线检查清单、审计流程与 Fluid 模板模式,并区分自动化证据和仍需人工确认的事项。

主要功能

  1. 01WCAG 2.2 AA 上线检查清单
  2. 02axe-core 审计流程
  3. 03键盘操作与语义结构检查
  4. 04无障碍 Fluid 模板模式
  5. 05审计证据及后续事项报告

适用场景

  1. 01上线前检查无障碍性
  2. 02审计渲染后的 TYPO3 页面
  3. 03检查键盘和表单无障碍性
  4. 04记录审计发现及待确认事项

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/dirnbauer/webconsulting-skills --skill typo3-accessibility

安装后在 Claude Code 中输入/typo3-accessibility确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

references/
03-php-accessibility-middleware-and-helpers.md
04-javascript-accessible-widgets.md
05-css-focus-and-contrast-essentials.md
06-typo3-specific-configuration.md
07-testing-and-tools.md
08-accessibility-extensions.md
09-wcag-2-2-quick-reference.md
10-anti-patterns-flag-these-in-reviews.md
credits-and-attribution.md
full-guide.md
related-skills.md
v14-only-accessibility-changes.md
scripts/
wcag22_audit.cjs

详细介绍

TYPO3 无障碍访问(WCAG 2.2 AA)

来源:https://github.com/dirnbauer/webconsulting-skills

**兼容性:**仅适用于 TYPO3 v14.x。本集合不涵盖更早版本的核心。

**TYPO3 API 优先:**始终优先使用 TYPO3 内置 API、Fluid ViewHelpers 和核心功能,然后再添加自定义标记。请确认相关方法在 TYPO3 v14 中存在。

**优先使用 PHP 和 JS,而非 TypoScript:**此技能提供 PHP 中间件、Fluid 局部模板和原生 JavaScript 解决方案。避免使用 TypoScript 示例;请采用基于 PHP 的方案。

1. 无障碍检查清单(上线门槛)

每次部署前都要逐项检查此清单。修复项目后请勾选。

语义结构
  • 每个页面恰好有一个 <h1>
  • 标题层级依次递进(h1 > h2 > h3,不得跳级)
  • 使用地标元素:<main>、<nav>、<header>、<footer>、<aside>
  • 跳转到内容的链接是第一个可聚焦元素
  • <html lang="..."> 与页面语言一致
  • 内容中的语言变更使用容器元素上的 lang 属性
图像和媒体
  • 所有 <img> 都有 alt 属性(装饰性图像使用空的 alt="")
  • 所有 <img> 都显式设置 width 和 height(防止 CLS)
  • 复杂图像(图表、信息图)提供详细描述
  • 视频配有字幕
  • 音频内容提供文字稿
  • 不自动播放带声音的媒体
颜色和对比度
  • 文本对比度 >= 4.5:1(普通文本)/ 3:1(大号文本 >= 24px / 18.66px 粗体)
  • UI 组件和图形对象的对比度 >= 3:1
  • 不单独依靠颜色传达信息(添加图标、文本或图案)
  • 深色模式(如已实现)符合对比度要求
键盘和焦点
  • 所有交互元素都可通过 Tab 键访问
  • 焦点顺序与视觉顺序一致
  • 所有交互元素都有可见的焦点指示器(:focus-visible)
  • 不得出现焦点陷阱(模态框除外——模态框必须正确限制焦点)
  • 按 Escape 键可关闭模态框/覆盖层,并将焦点返回触发元素
  • 自定义组件具有正确的键盘事件处理程序
表单
  • 每个输入框都有程序化关联的 <label>(显式使用 for/id 或直接包裹)
  • 不得仅使用占位符作为标签
  • 必填字段有视觉提示,并设置 required 属性
  • 错误消息通过 aria-describedby + aria-invalid 与输入框关联
  • 表单分组使用 <fieldset> + <legend>
  • 输入框设置正确的 type 和 autocomplete 属性
ARIA 和动态内容
  • 仅含图标的按钮设置 aria-label
  • 装饰性图标设置 aria-hidden="true"
  • 动态更新使用 aria-live="polite" 或 role="status"
  • 错误提示使用 role="alert"
  • 当前导航链接设置 aria-current="page"
  • 可展开元素设置 aria-expanded
动效和交互
  • 遵循 prefers-reduced-motion 设置(CSS 和 JS)
  • 内容每秒闪烁不超过 3 次
  • 触控目标符合 WCAG 2.2 AA 2.5.8(最小尺寸为 24×24 CSS 像素);条件允许时采用 44×44 作为更宽松的舒适目标
  • 视口 meta 中不得设置 user-scalable=no
TYPO3 特定项
  • 后端:文件列表中所有图像均已填写替代文本字段
  • 后端:内容元素均有描述性标题(隐藏标题时使用 header_layout = 100)
  • 后端:页面属性包含合适的 <title> 和 description
  • Fluid 模板使用 <f:link.page> / <f:link.typolink>(而非手动设置 href 的 <a>)
  • 内容块 / 内容元素模板遵循下文的无障碍模式

2. WCAG 2.2 一致性审计工作流

收到 WCAG 2.2 审计请求时,应在交付前完成 Agent 能够完成的工作:

  1. 确定要审计的 URL、语言、视口、表单、对话框、菜单、媒体、下载内容和第三方嵌入内容。
  2. 对渲染后的页面运行 scripts/wcag22_audit.cjs,并使用 axe-core 标签检查 WCAG 2.0/2.1/2.2 A/AA。
  3. 检查所有自动化发现的问题和 incomplete 项,并修复或记录。审计脚本会尽可能计算 color-contrast 不完整节点的对比度;升级问题前请先参考这些证据。不要将未解决的 incomplete 项视为通过。
  4. 验证语义结构、页面语言、地标、唯一的 <h1>、标题顺序、跳过链接、图像 alt、图像尺寸、视口缩放、页脚/页眉导航、可聚焦性、目标尺寸候选项,以及内联文本链接的目标尺寸例外。
  5. 尽可能通过真实的 Tab 导航检查键盘顺序。立即修复明确的模板/组件缺陷。
  6. 编写包含两个部分的审计报告:
    • Agent 已完成的证据: 命令、URL、视口、工具版本、问题、修复内容、剩余技术风险。
    • 仅需人工/用户跟进的事项: 屏幕阅读器确认、法律声明审批,以及仅适用的设备专属、第三方/PDF/媒体或用户测试任务。

收到一致性审计请求时,请遵循 测试与工具 中的详细流程。自动化运行成功并不代表符合 WCAG;它只能为自动化和代码检查所能覆盖的部分提供证据。

3. 流式模板模式

3.1 使用地标的页面布局


<f:render section="Main" />

<f:layout name="Default" />
<f:section name="Main">

<a href="#main-content" class="skip-link">
    <f:translate key="LLL:EXT:site_package/Resources/Private/Language/locallang.xlf:skip_to_content" />
</a>

<header>
    
    <f:cObject typoscriptObjectPath="lib.mainNavigation" />
</header>

<main id="main-content">
    <f:render section="Content" />
</main>

<footer>
    <nav aria-label="{f:translate(key: 'LLL:EXT:site_package/Resources/Private/Language/locallang.xlf:nav.footer')}">
        <f:cObject typoscriptObjectPath="lib.footerNavigation" />
    </nav>
</footer>

</f:section>
3.2 跳过链接 CSS
.skip-link {
    position: absolute;
    top: -100%;
    left: 0;
    z-index: 10000;
    padding: 0.75rem 1.5rem;
    background: var(--color-primary, #2563eb);
    color: var(--color-on-primary, #fff);
    font-weight: 600;
    text-decoration: none;
}
.skip-link:focus {
    top: 0;
}
3.3 无障碍图像渲染

<f:if condition="{image}">
    <figure>
        <f:image
            image="{image}"
            alt="{image.alternative}"
            title="{image.title}"
            width="{dimensions.width}"
            height="{dimensions.height}"
            loading="{f:if(condition: lazyLoad, then: 'lazy', else: 'eager')}"
            additionalAttributes="{decoding: 'async'}"
        />
        <f:if condition="{image.description}">
            <figcaption>{image.description}</figcaption>
        </f:if>
    </figure>
</f:if>

对于装饰性图像(不传达信息):

<f:image image="{image}" alt="" />
3.4 无障碍内容元素包装器

<f:if condition="{data.header} && {data.header_layout} != '100'">
    <f:switch expression="{data.header_layout}">
        <f:case value="1"><h1>{data.header}</h1></f:case>
        <f:case value="2"><h2>{data.header}</h2></f:case>
        <f:case value="3"><h3>{data.header}</h3></f:case>
        <f:case value="4"><h4>{data.header}</h4></f:case>
        <f:case value="5"><h5>{data.header}</h5></f:case>
        <f:defaultCase><h2>{data.header}</h2></f:defaultCase>
    </f:switch>
</f:if>
3.5 无障碍导航局部模板

<nav aria-label="{f:translate(key: 'LLL:EXT:site_package/Resources/Private/Language/locallang.xlf:nav.main')}">
    
    <ul role="list">
        <f:for each="{menu}" as="item">
            <li>
                <f:if condition="{item.active}">
                    <f:then>
                        <a href="{item.link}" aria-current="page">{item.title}</a>
                    </f:then>
                    <f:else>
                        <a href="{item.link}">{item.title}</a>
                    </f:else>
                </f:if>

                <f:if condition="{item.children}">
                    <ul>
                        <f:for each="{item.children}" as="child">
                            <li>
                                <a href="{child.link}"
                                   {f:if(condition: child.active, then: 'aria-current="page"')}>
                                    {child.title}
                                </a>
                            </li>
                        </f:for>
                    </ul>
                </f:if>
            </li>
        </f:for>
    </ul>
</nav>
3.6 无障碍折叠面板(内容块 / 自定义 CE)
<div class="accordion" data-accordion>
    <f:for each="{items}" as="item" iteration="iter">
        <div class="accordion__item">
            
            <div role="heading" aria-level="{f:if(condition: headingLevel, then: headingLevel, else: '3')}">
                <button
                    type="button"
                    class="accordion__trigger"
                    aria-expanded="false"
                    aria-controls="accordion-panel-{data.uid}-{iter.index}"
                    id="accordion-header-{data.uid}-{iter.index}"
                    data-accordion-trigger
                >
                    {item.header}
                </button>
            </div>
            <div
                id="accordion-panel-{data.uid}-{iter.index}"
                role="region"
                aria-labelledby="accordion-header-{data.uid}-{iter.index}"
                class="accordion__panel"
                hidden
            >
                <f:format.html>{item.bodytext}</f:format.html>
            </div>
        </div>
    </f:for>
</div>
3.7 无障碍标签页组件
<div class="tabs" data-tabs>
    <div role="tablist" aria-label="{f:translate(key: 'tabs.label')}">
        <f:for each="{items}" as="item" iteration="iter">
            <button
                type="button"
                role="tab"
                id="tab-{data.uid}-{iter.index}"
                aria-controls="tabpanel-{data.uid}-{iter.index}"
                aria-selected="{f:if(condition: iter.isFirst, then: 'true', else: 'false')}"
                tabindex="{f:if(condition: iter.isFirst, then: '0', else: '-1')}"
            >
                {item.header}
            </button>
        </f:for>
    </div>

    <f:for each="{items}" as="item" iteration="iter">
        <div
            role="tabpanel"
            id="tabpanel-{data.uid}-{iter.index}"
            aria-labelledby="tab-{data.uid}-{iter.index}"
            tabindex="0"
            {f:if(condition: iter.isFirst, then: '', else: 'hidden')}
        >
            <f:format.html>{item.bodytext}</f:format.html>
        </div>
    </f:for>
</div>

详细参考

如果任务需要详细示例、长模板、故障排查矩阵、附录或上文未包含的章节,请阅读完整指南。对于范围较窄的任务,请勿加载此文件,以便该技能遵循渐进式披露。

评论

支持 Markdown