Skip to content

Conversation

@SoonIter
Copy link
Member

@SoonIter SoonIter commented Jan 6, 2026

Summary

This PR improves accessibility (a11y) across the core theme components in packages/core/src/theme. The changes ensure better support for screen readers, keyboard navigation, and assistive technologies while maintaining existing functionality.

Changes Made

Button Semantics & Accessibility

  • SearchButton: Changed mobile search from <div> to <button>, added type="button" and aria-label
  • SwitchAppearance: Changed from <div> to <button> with proper type and aria-label
  • NavHamburger: Added type="button", internationalized aria-label, and aria-expanded state
  • ScrollToTop: Added type="button", aria-label, and aria-hidden for decorative SVG
  • CopyCodeButton: Added type="button", replaced title with aria-label
  • SearchPanel: Changed cancel button from <h2> to <button>

ARIA Roles & Attributes

  • Tabs: Added proper ARIA roles (tablist, tab, tabpanel), aria-selected, aria-controls, aria-labelledby, proper tabIndex management, and unique IDs via useId()
  • SearchPanel: Added role="dialog", aria-modal="true", and improved input aria-label
  • SidebarGroup: Added aria-expanded state for collapsible sections with keyboard support (Enter/Space)

Social Links

  • SocialLink: Added aria-label describing link type, aria-hidden for decorative icons

Internationalization

Added new i18n keys for accessibility labels:

  • switchAppearanceLabel: "Switch theme" / "切换主题" / etc.
  • navMenuLabel: "Navigation menu" / "导航菜单" / etc.

Why These Changes

These improvements follow WCAG 2.1 guidelines to ensure:

  • Screen readers can correctly identify interactive elements
  • Keyboard users can fully navigate and operate the interface
  • State changes (expand/collapse) are properly announced by assistive technologies
  • All interactive elements have appropriate semantic markup

Files Modified

  • packages/shared/src/types/theme/i18nText.ts - Added new i18n type definitions
  • packages/core/src/node/runtimeModule/DEFAULT_I18N_TEXT.ts - Added default translations
  • packages/core/src/theme/components/Search/SearchButton.tsx
  • packages/core/src/theme/components/Search/SearchPanel.tsx
  • packages/core/src/theme/components/SwitchAppearance/index.tsx
  • packages/core/src/theme/components/NavHamburger/index.tsx
  • packages/core/src/theme/components/Outline/ScrollToTop.tsx
  • packages/core/src/theme/components/CodeButtonGroup/CopyCodeButton.tsx
  • packages/core/src/theme/components/Tabs/index.tsx
  • packages/core/src/theme/components/SocialLinks/SocialLink.tsx
  • packages/core/src/theme/components/Sidebar/SidebarItem.tsx
  • packages/core/src/theme/components/Sidebar/SidebarGroup.tsx

This PR was written using Vibe Kanban

## 无障碍优化总结

我对 `packages/core/src/theme` 中的组件进行了以下无障碍优化:

### 1. 按钮语义化和可访问性
- **SearchButton** (`Search/SearchButton.tsx:39-46`):
  - 移动端搜索按钮从 `<div>` 改为 `<button>`
  - 添加 `type="button"` 和 `aria-label`

- **SwitchAppearance** (`SwitchAppearance/index.tsx:89-105`):
  - 从 `<div>` 改为 `<button>` 元素
  - 添加 `type="button"` 和 `aria-label`

- **NavHamburger** (`NavHamburger/index.tsx:40-64`):
  - 添加 `type="button"`
  - 使用国际化的 `aria-label`
  - 添加 `aria-expanded` 状态

- **ScrollToTop** (`Outline/ScrollToTop.tsx:15-27`):
  - 添加 `type="button"` 和 `aria-label`
  - SVG 添加 `aria-hidden="true"`

- **CopyCodeButton** (`CodeButtonGroup/CopyCodeButton.tsx:57-72`):
  - 添加 `type="button"`
  - `title` 改为 `aria-label`

- **SearchPanel** (`Search/SearchPanel.tsx:539-548`):
  - 取消按钮从 `<h2>` 改为 `<button>`

### 2. ARIA 角色和属性
- **Tabs** (`Tabs/index.tsx:150-232`):
  - 添加 `role="tablist"`、`role="tab"`、`role="tabpanel"`
  - 添加 `aria-selected`、`aria-controls`、`aria-labelledby`
  - 正确管理 `tabIndex`
  - 使用 `useId()` 生成唯一 ID

- **SearchPanel** (`Search/SearchPanel.tsx:497-505`):
  - 添加 `role="dialog"` 和 `aria-modal="true"`
  - 改进搜索输入框的 `aria-label`

- **SidebarGroup** (`Sidebar/SidebarGroup.tsx:86`):
  - 添加 `aria-expanded` 状态
  - 支持键盘交互 (Enter/Space)

### 3. 社交链接
- **SocialLink** (`SocialLinks/SocialLink.tsx:53-65`):
  - 添加 `aria-label` 描述链接类型
  - 图标添加 `aria-hidden="true"`

### 4. 国际化支持
在 `packages/shared/src/types/theme/i18nText.ts` 和 `packages/core/src/node/runtimeModule/DEFAULT_I18N_TEXT.ts` 中添加了新的国际化文本:
- `switchAppearanceLabel`: 切换主题的无障碍标签
- `navMenuLabel`: 导航菜单的无障碍标签

这些优化遵循 WCAG 2.1 标准,确保:
- 屏幕阅读器可以正确识别交互元素
- 键盘用户可以完全导航和操作界面
- 状态变化(如展开/折叠)能被辅助技术正确播报
Copilot AI review requested due to automatic review settings January 6, 2026 09:30
@netlify
Copy link

netlify bot commented Jan 6, 2026

Deploy Preview for rspress-v2 ready!

Name Link
🔨 Latest commit bf4089d
🔍 Latest deploy log https://app.netlify.com/projects/rspress-v2/deploys/695cd61ecf78e900083c5b46
😎 Deploy Preview https://deploy-preview-2965--rspress-v2.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@SoonIter SoonIter changed the title 无障碍检查 (vibe-kanban) feat(theme): improve accessibility for core theme components (Vibe Kanban) Jan 6, 2026
@github-actions
Copy link
Contributor

github-actions bot commented Jan 6, 2026

Rsdoctor Bundle Diff Analysis

Found 3 projects in monorepo, 3 projects with changes.

📊 Quick Summary
Project Total Size Change
node 9.7 MB 📈 +142.0 KB (+1.5%)
web 15.3 MB +10.6 KB (0.1%)
node_md 1.2 MB +1.4 KB (0.1%)
📋 Detailed Reports (Click to expand)

📁 node

Path: website/doc_build/diff-rsdoctor/node/rsdoctor-data.json

📌 Baseline Commit: 7d8ac9b51c | PR: #2935

Metric Current Baseline Change
📊 Total Size 9.7 MB 9.5 MB +142.0 KB (+1.5%)
📄 JavaScript 0 B 0 B 0
🎨 CSS 0 B 0 B 0
🌐 HTML 9.7 MB 9.5 MB +142.0 KB (+1.5%)
📁 Other Assets 0 B 0 B 0

📦 Download Diff Report: node Bundle Diff

📁 web

Path: website/doc_build/diff-rsdoctor/web/rsdoctor-data.json

📌 Baseline Commit: 7d8ac9b51c | PR: #2935

Metric Current Baseline Change
📊 Total Size 15.3 MB 15.3 MB +10.6 KB (0.1%)
📄 JavaScript 14.6 MB 14.6 MB +10.6 KB (0.1%)
🎨 CSS 128.4 KB 128.4 KB 0
🌐 HTML 0 B 0 B 0
📁 Other Assets 555.6 KB 555.6 KB 0

📦 Download Diff Report: web Bundle Diff

📁 node_md

Path: website/doc_build/diff-rsdoctor/node_md/rsdoctor-data.json

📌 Baseline Commit: 7d8ac9b51c | PR: #2935

Metric Current Baseline Change
📊 Total Size 1.2 MB 1.2 MB +1.4 KB (0.1%)
📄 JavaScript 0 B 0 B 0
🎨 CSS 0 B 0 B 0
🌐 HTML 0 B 0 B 0
📁 Other Assets 1.2 MB 1.2 MB +1.4 KB (0.1%)

📦 Download Diff Report: node_md Bundle Diff

Generated by Rsdoctor GitHub Action

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR implements comprehensive accessibility improvements to the vibe-kanban theme components without affecting existing functionality. The changes focus on semantic HTML, ARIA attributes, and keyboard navigation support.

Key Changes:

  • Added semantic HTML by converting interactive divs to button elements with proper type attributes
  • Implemented ARIA attributes (role, aria-label, aria-expanded, aria-controls, aria-selected) for better screen reader support
  • Added internationalized accessibility labels for UI controls

Reviewed changes

Copilot reviewed 12 out of 12 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
packages/shared/src/types/theme/i18nText.ts Added type definitions for new accessibility-related i18n text values
packages/core/src/theme/components/Tabs/index.tsx Converted tab labels from divs to buttons with proper ARIA tablist/tab/tabpanel roles and relationships
packages/core/src/theme/components/SwitchAppearance/index.tsx Changed theme switcher from div to button with aria-label for screen readers
packages/core/src/theme/components/SocialLinks/SocialLink.tsx Added aria-label to social links and aria-hidden to decorative icons
packages/core/src/theme/components/Sidebar/SidebarItem.tsx Added role="button", tabIndex, aria-expanded attributes and keyboard event handling for collapsible items
packages/core/src/theme/components/Sidebar/SidebarGroup.tsx Passed aria-expanded prop to collapsible sidebar groups
packages/core/src/theme/components/Search/SearchPanel.tsx Added dialog role and aria-modal to search modal, changed cancel from h2 to button element
packages/core/src/theme/components/Search/SearchButton.tsx Converted mobile search trigger from div to button with aria-label
packages/core/src/theme/components/Outline/ScrollToTop.tsx Added aria-label to scroll-to-top button and aria-hidden to decorative SVG
packages/core/src/theme/components/NavHamburger/index.tsx Added aria-label and aria-expanded to hamburger menu buttons
packages/core/src/theme/components/CodeButtonGroup/CopyCodeButton.tsx Changed title attribute to aria-label on copy button
packages/core/src/node/runtimeModule/DEFAULT_I18N_TEXT.ts Added i18n translations for switchAppearanceLabel and navMenuLabel in 5 languages
Comments suppressed due to low confidence (1)

packages/core/src/theme/components/NavHamburger/index.tsx:64

  • The medium-sized hamburger button should also have an aria-expanded attribute to indicate its expanded/collapsed state, similar to the small-sized hamburger button. This ensures consistent accessibility behavior across different screen sizes.
      <button
        type="button"
        aria-label={t('navMenuLabel')}
        className={clsx('rp-nav-hamburger', 'rp-nav-hamburger__md', {
          'rp-nav-hamburger--active': isScreenOpen,
        })}
        onClick={handleMouseEnter}
        onMouseEnter={handleMouseEnter}
        onMouseLeave={handleMouseLeave}
      >
        <SvgWrapper icon={IconSmallMenu} />
        {hoverGroup}
      </button>

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

if (typeof icon === 'string') {
return icon;
}
return icon?.svg ? 'social link' : '';
Copy link

Copilot AI Jan 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The getIconLabel function returns an empty string when the icon has an svg property, which would result in an aria-label="" attribute. This provides no meaningful description for screen reader users. Consider using a more descriptive default label like the link URL or mode type, or requiring a label to be provided.

Suggested change
return icon?.svg ? 'social link' : '';
// Provide a non-empty, descriptive default label for non-string icons
return 'social link';

Copilot uses AI. Check for mistakes.
target="_blank"
rel="noopener noreferrer"
className="rp-social-links__item"
aria-label={iconLabel}
Copy link

Copilot AI Jan 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The aria-label attribute uses an empty string when the icon is an SVG, which provides no useful information to screen reader users. Consider either requiring a meaningful label for all social links, or deriving a label from the link URL (e.g., extracting the domain name like "GitHub", "Twitter", etc.).

Copilot uses AI. Check for mistakes.
role="tab"
aria-selected={isActive}
aria-controls={panelId}
tabIndex={isActive ? 0 : -1}
Copy link

Copilot AI Jan 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The tabIndex implementation doesn't support keyboard navigation between tabs. According to ARIA best practices for tabs, only the active tab should have tabIndex="0", but users should be able to navigate between tabs using arrow keys. Consider adding keyboard event handlers for ArrowLeft and ArrowRight keys to move focus between tabs.

Copilot uses AI. Check for mistakes.
@@ -180,7 +191,7 @@ export const Tabs = forwardRef(
}}
Copy link

Copilot AI Jan 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When converting from div to button elements for tabs, ensure that you add keyboard event handlers to support arrow key navigation between tabs. According to ARIA authoring practices for tabs, users should be able to navigate between tab buttons using arrow keys (ArrowLeft/ArrowRight for horizontal tabs), and the onClick handler should also respond to keyboard activation.

Suggested change
}}
}}
onKeyDown={(event) => {
if (event.key !== 'ArrowRight' && event.key !== 'ArrowLeft') {
return;
}
event.preventDefault();
const tabList = (event.currentTarget as HTMLButtonElement).closest(
'[role="tablist"]',
);
if (!tabList) {
return;
}
const tabs = tabList.querySelectorAll<HTMLButtonElement>('[role="tab"]');
if (!tabs.length) {
return;
}
const totalTabs = tabs.length;
let newIndex = index;
if (event.key === 'ArrowRight') {
newIndex = (index + 1) % totalTabs;
} else if (event.key === 'ArrowLeft') {
newIndex = (index - 1 + totalTabs) % totalTabs;
}
const newTab = tabs[newIndex];
if (newTab) {
newTab.focus();
}
onChange?.(newIndex);
if (groupId) {
setStorageIndex(newIndex.toString());
} else {
setActiveIndex(newIndex);
}
}}

Copilot uses AI. Check for mistakes.
@SoonIter SoonIter closed this Feb 10, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant