搜索全站
主题 16 分钟阅读

WordPress 主题可访问性 a11y 基础:语义标签、跳转链接与键盘可达

主题看起来没问题,键盘却无法操作菜单、读屏软件读不出导航?可访问性不是锦上添花。本文讲清语义标签、跳转到内容链接、焦点样式、表单标签与 ARIA 的正确写法,附一份可执行的自查清单。

U
UFO_PF 2019-04-14 · 阅读 16 分钟
Wo 主题
阅读提示
本文约 6,315 字,预计阅读 16 分钟。文中命令与配置均经过实测,可直接参考执行。

引言:你的主题,键盘用户可能根本用不了

试一个动作:打开你的站点,只用 Tab 键,能走到主导航吗?能看到当前焦点在哪吗?能打开二级菜单吗?很多主题在这里就露馅了——焦点框被 CSS 的 outline: none 去掉了,鼠标悬停才展开的二级菜单用键盘永远打不开,读屏软件读到的是一堆没有语义的 div。

可访问性(Accessibility,简称 a11y)在国内常被当成「无障碍验收」时才补的加分项,但它的影响面远比想象中大:政府与企业站点的合规要求、Google 的核心网页指标与 SEO 评分、以及最朴素的——老年用户、键盘用户、使用读屏软件的用户能不能正常用你的站。WordPress 官方主题目录里就有 accessibility-ready 这个标签,达标并不难,难的是从一开始就别把基础做错。

原理分析:三个层次的要求

可访问性落到主题代码上,可以拆成三层:

层次 要求 典型实现
语义 用正确的标签表达结构 <nav>、<main>、<header>、<footer>、<button>
可达 键盘能操作、焦点可见 不删 outline、跳转链接、正确的 tab 顺序
可读 读屏软件能理解 aria-label、<label>、图片的 alt、ARIA 状态

几个高频概念要分清:role="navigation" 是给读屏的语义补充,但原生 <nav> 已经隐含这个角色,不必重复加;aria-label 用于给无文字的按钮(如汉堡菜单图标)提供名称;.screen-reader-text(Underscores 主题里的经典类)是「视觉隐藏但读屏可读」的实现,跳转链接就靠它;wp.a11y.speak() 是 WordPress 提供的 JS 方法,用于动态内容变化时向读屏播报。

颜色对比度也是硬指标:正文文字与背景的对比度至少 4.5:1,大字号(18pt 以上或 14pt 粗体)可放宽到 3:1。很多浅灰配白的「高级感」配色在这一点上直接不合格。

保姆级实操

第一步:语义骨架

<?php
/**
 * header.php 片段
 */
?>
<body <?php body_class(); ?>>
<?php wp_body_open(); ?>

<a class="skip-link screen-reader-text" href="#primary"><?php esc_html_e( '跳到主要内容', 'mimangfei' ); ?></a>

<header id="masthead" class="site-header">
    <nav id="site-navigation" class="main-navigation" aria-label="<?php esc_attr_e( '主导航', 'mimangfei' ); ?>">
        <?php
        wp_nav_menu( array(
            'theme_location' => 'primary',
            'menu_id'        => 'primary-menu',
            'container'      => false,
        ) );
        ?>
    </nav>
</header>

<main id="primary" class="site-main">

要点:wp_body_open()(WP 5.2+)必须紧跟在 <body> 后,否则插件注入的内容会错位;<main> 一篇页面只能有一个;wp_nav_menu 外面包 <nav> 并加 aria-label,不要依赖 container 生成的 div。

第二步:跳转链接的样式与聚焦

.screen-reader-text {
    border: 0;
    clip-path: inset( 50% );
    height: 1px;
    margin: -1px;
    overflow: hidden;
    padding: 0;
    position: absolute;
    width: 1px;
    white-space: nowrap;
}

.skip-link:focus,
.screen-reader-text:focus {
    clip-path: none;
    display: block;
    top: 0;
    left: 0;
    z-index: 100000;
    width: auto;
    height: auto;
    padding: 12px 20px;
    background: #1a1a1a;
    color: #fff;
    text-decoration: none;
}

跳转链接必须是 <body> 后第一个可聚焦元素,且 :focus 时要可见——隐藏但不显示焦点,等于没有。

第三步:焦点样式不要删

/* 错误示范:键盘用户将彻底失去方向感 */
/* *:focus { outline: none; } */

/* 正确做法:只在鼠标点击时隐藏,键盘导航时保留 */
:focus-visible {
    outline: 2px solid #2563eb;
    outline-offset: 2px;
}

a:focus,
button:focus,
input:focus,
textarea:focus,
select:focus {
    outline: 2px solid #2563eb;
    outline-offset: 2px;
}

:focus-visible 已被现代浏览器广泛支持,能区分「键盘聚焦」与「鼠标点击」,不用再写 JS hack。

第四步:菜单按钮与二级菜单的键盘可达

wp_nav_menu( array(
    'theme_location'  => 'primary',
    'container'       => false,
    'items_wrap'      => '<ul id="%1$s" class="%2$s">%3$s</ul>',
    'fallback_cb'     => false,
) );

按钮用 <button> 而不是 <a href="#">:

<button class="menu-toggle" aria-controls="primary-menu" aria-expanded="false">
    <?php esc_html_e( '菜单', 'mimangfei' ); ?>
</button>

配套的 JS 在展开/收起时同步更新 aria-expanded,并在 Esc 键时关闭菜单、把焦点还给按钮:

const toggle = document.querySelector( '.menu-toggle' );
const menu   = document.getElementById( 'primary-menu' );

toggle.setAttribute( 'aria-expanded', 'false' );

toggle.addEventListener( 'click', () => {
    const expanded = toggle.getAttribute( 'aria-expanded' ) === 'true';
    toggle.setAttribute( 'aria-expanded', String( ! expanded ) );
    menu.classList.toggle( 'toggled' );
} );

document.addEventListener( 'keydown', ( e ) => {
    if ( e.key === 'Escape' && toggle.getAttribute( 'aria-expanded' ) === 'true' ) {
        toggle.setAttribute( 'aria-expanded', 'false' );
        menu.classList.remove( 'toggled' );
        toggle.focus();
    }
} );

二级菜单项还需支持键盘展开(focus 时也显示子菜单),CSS 里除了 :hover 要加 :focus-within:

.main-navigation ul ul { display: none; }
.main-navigation li:hover > ul,
.main-navigation li:focus-within > ul { display: block; }

第五步:表单、图片与分页

<label for="mf-search-field"><?php esc_html_e( '搜索', 'mimangfei' ); ?></label>
<input type="search" id="mf-search-field" class="search-field" name="s" value="<?php echo esc_attr( get_search_query() ); ?>">
<button type="submit"><span class="screen-reader-text"><?php esc_html_e( '提交搜索', 'mimangfei' ); ?></span></button>
if ( has_post_thumbnail() ) {
    the_post_thumbnail( 'large', array(
        'alt' => the_title_attribute( array( 'echo' => false ) ),
    ) );
}

分页用核心函数,它自带 aria-label 与 nav 语义:

the_posts_pagination( array(
    'mid_size'  => 2,
    'prev_text' => __( '上一页', 'mimangfei' ),
    'next_text' => __( '下一页', 'mimangfei' ),
) );

不要用裸的 paginate_links() 拼 HTML,会丢掉 aria-current="page" 这类状态标记。

第六步:动态播报与对比度自检

wp.a11y.speak( '文章已加载完成', 'polite' );

前提是把 wp-a11y 加入依赖:

wp_enqueue_script(
    'mimangfei-app',
    get_template_directory_uri() . '/assets/js/app.js',
    array( 'wp-a11y' ),
    wp_get_theme()->get( 'Version' ),
    true
);

对比度用浏览器 DevTools 的「颜色选择器」直接看 Contrast Ratio,或用 axe DevTools 插件跑一遍全站扫描。

常见踩坑

  1. outline: none 一刀切:最常见的错误,键盘用户完全失去焦点指示。
  2. 用 <div onclick> 当按钮:读屏读不出它是按钮,键盘也无法聚焦,必须换成 <button>。
  3. 图标按钮无 aria-label:只有放大镜图标的搜索按钮,读屏播报为「按钮」,用户不知道是干什么的。
  4. 跳转链接指向的 id 不存在:href="#content" 但 <main> 的 id 是 #primary,等于白做。
  5. 装饰图片写了 alt:纯装饰图应 alt="",否则读屏会念出一堆无用信息;反过来内容图不写 alt 又是另一类错误。
  6. 案例:某企业站改版后收到无障碍投诉:键盘 Tab 走到页脚时会「消失」。原因是页脚的「返回顶部」用了 <a href="#">,且 CSS 给所有 a:focus 设了 outline: none,用户 Tab 到那里既看不到焦点也无法触发。修复改成 <button> 加 aria-label,并恢复焦点样式,问题一次性解决。
  7. aria-label 与可见文本冲突:按钮文字是「搜索」而 aria-label 写「Submit」,读屏用户听到的与看到的对不上,会被判为不合规。
  8. title 属性当替代文本:title 在触屏和多数读屏下不可靠,alt 与 aria-label 才是正确做法。

总结

一份能直接执行的自查清单:<body> 后第一个元素是可聚焦的跳转链接;全站只有一个 <main>;导航用 <nav> 且带 aria-label;交互元素全是 <button> / <a>;:focus-visible 样式存在且清晰;表单都有 <label>;图片 alt 有意义;正文对比度 ≥ 4.5:1;只用 Tab 能走完首页到页脚并提交一次搜索。这十项过了,主题的 a11y 就已经超过大多数同类。若想申请官方目录的 accessibility-ready 标签,还要把读屏软件(NVDA 或 VoiceOver)跑一遍完整流程,并把 主题上线前自检 与 主题打包发布规范 一起走完。

U
UFO_PF作者

专注 WordPress 生态的实战作者,内容以「可复制、可验证」为第一原则。

留下你的想法

邮箱地址不会被公开。带 * 的为必填项。