Classic Theme Accessibility and Semantics
Use this when creating or reviewing the HTML semantics, keyboard behavior, landmarks, skip links, focus styles, forms, images, icons, and ARIA behavior of a classic PHP WordPress theme.
Accessibility is not a visual polish pass. It affects template structure, PHP output, CSS, and JavaScript behavior.
When to Use This Skill
- Writing
header.php, footer.php, index.php, single.php, page.php, or navigation templates.
- Adding a mobile menu, modal, search toggle, carousel, tabs, accordion, or interactive widget.
- Reviewing theme markup for WordPress.org-style accessibility expectations.
- Fixing keyboard navigation, missing focus states, unlabeled controls, bad heading order, or broken landmarks.
Document Skeleton
A classic theme header should preserve core hooks and language/body helpers.
<!doctype html>
<html <?php language_attributes(); ?>>
<head>
<meta charset="<?php bloginfo( 'charset' ); ?>">
<meta name="viewport" content="width=device-width, initial-scale=1">
<?php wp_head(); ?>
</head>
<body <?php body_class(); ?>>
<?php wp_body_open(); ?>
Rules:
- Use
language_attributes() on <html>.
- Use
bloginfo( 'charset' ) for the charset.
- Use
wp_head() before closing </head>.
- Use
body_class() on <body>.
- Call
wp_body_open() immediately after the opening body tag.
- Add
add_theme_support( 'title-tag' ) in theme setup; do not hardcode <title>.
Landmarks
Use semantic landmarks so users can navigate the page structure.
<header id="masthead" class="site-header">
<nav id="site-navigation" class="main-navigation" aria-label="<?php esc_attr_e( 'Primary menu', 'textdomain' ); ?>">
...
</nav>
</header>
<main id="main" class="site-main">
...
</main>
<footer id="colophon" class="site-footer">
...
</footer>
Rules:
- Each page should have a clear main landmark.
- Major navigation landmarks need accessible names when there is more than one
nav.
- Do not put the entire page in generic
<div> elements when semantic elements fit.
- Do not create multiple unlabeled
main elements.
Skip Links
Add a skip link that becomes visible on focus and targets real content.
<a class="skip-link screen-reader-text" href="#main">
<?php esc_html_e( 'Skip to content', 'textdomain' ); ?>
</a>
The target must exist. If focus needs to move reliably in JavaScript-assisted layouts, make the target focusable:
<main id="main" class="site-main" tabindex="-1">
Rules:
- The skip link must be the first meaningful focusable element after
wp_body_open().
- It must become visible on keyboard focus.
- The
href must point to an ID that exists on every template.
Screen Reader Text CSS
Include a standard visually-hidden utility and restore it on focus.
.screen-reader-text {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(1px, 1px, 1px, 1px);
word-wrap: normal;
border: 0;
}
.screen-reader-text:focus {
top: 5px;
left: 5px;
z-index: 100000;
display: block;
width: auto;
height: auto;
padding: 15px 23px 14px;
clip: auto;
background: #fff;
color: #000;
font-size: 1rem;
font-weight: 700;
text-decoration: none;
}
Rules:
- Do not use
display: none for content that screen readers need.
- Make skip links visible on focus, not only available to assistive tech.
- Keep focus styles visible above sticky headers.
Headings
Headings describe document structure.
Rules:
- Use one clear
h1 for the primary page title.
- Archive/search pages usually use the archive/search title as
h1.
- Posts in archive cards should usually use
h2.
- Do not choose heading levels based only on font size.
- Do not skip levels to achieve visual styling; style with CSS.
Links, Buttons, and Toggles
Use elements by behavior:
- Links navigate to URLs.
- Buttons perform actions on the current page.
- Form controls collect input.
Mobile menu toggle:
<button
class="menu-toggle"
type="button"
aria-controls="primary-menu"
aria-expanded="false"
>
<?php esc_html_e( 'Menu', 'textdomain' ); ?>
</button>
Rules:
- Do not use
<a href="#"> as a button.
- Keep visible labels or accessible names for icon-only buttons.
- Update
aria-expanded when a controlled region opens/closes.
aria-controls must reference an existing ID.
- Avoid adding ARIA when native HTML already communicates the behavior correctly.
Focus and Keyboard
Rules:
- Never remove
outline without a visible replacement.
- Use
:focus-visible where appropriate, with a fallback if supporting older browsers.
- Hover-only menus are not enough. Keyboard users must be able to open and traverse navigation.
- Modals and off-canvas panels need focus management and Escape behavior. If the theme cannot implement that correctly, avoid shipping the interaction.
- Do not trap focus in ordinary dropdown navigation.
Forms and Search
Rules:
- Every input needs a real
<label> or an equivalent accessible name.
- Placeholder text is not a label.
- Error messages should identify the field and be programmatically connected when possible.
- Use
get_search_form() unless the theme has a specific accessible custom search form.
- Nonces and validation are security concerns, but accessible errors are still required for user correction.
Images and Icons
Rules:
- Informative images need useful alt text.
- Decorative images should use empty alt text.
- Do not stuff keywords into alt text.
- For featured images, rely on WordPress image functions where possible and ensure attachment alt text is maintained.
- SVG icons used only as decoration should have
aria-hidden="true" and focusable="false".
- Icon-only buttons/links need an accessible name via visible text,
.screen-reader-text, or aria-label.
Motion and Visual Requirements
Respect reduced motion preferences.
@media (prefers-reduced-motion: reduce) {
*,
*::before,
*::after {
scroll-behavior: auto !important;
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
}
}
Rules:
- Ensure text contrast is sufficient in normal, hover, and focus states.
- Do not rely on color alone to communicate state.
- Keep line heights and spacing readable.
- Avoid auto-playing motion-heavy UI. If used, provide pause/stop controls.
WordPress Template Helpers
Preserve helpers that contribute useful classes and core integration:
body_class() on <body>.
post_class() on post wrappers.
wp_body_open() after <body>.
wp_head() and wp_footer().
language_attributes() on <html>.
the_custom_logo() when using core custom logo support.
get_search_form() for baseline search form behavior.
Review Checklist
- Header uses
language_attributes(), wp_head(), body_class(), and wp_body_open().
- Page has one clear main landmark.
- Skip link exists, is focus-visible, and points to a real ID.
- Navigation landmarks have accessible names.
- Mobile toggles are buttons and update
aria-expanded.
- Keyboard focus is visible everywhere.
- Headings follow document structure.
- Forms have labels and understandable errors.
- Images and SVG icons have appropriate alt/ARIA behavior.
- Reduced motion is respected for animations/transitions.
- Theme does not rely on hover-only interactions.
Common Mistakes
- Hiding skip links with
display: none.
- Removing outlines globally.
- Using a fake link for menu/search toggles.
- Adding
role="button" to an anchor instead of using a button.
- Creating several unlabeled navigation landmarks.
- Outputting icon-only buttons with no accessible name.
- Making dropdown menus impossible to use with a keyboard.
- Forgetting
wp_body_open() in header.php.
References
1---2name: classic-theme-accessibility-semantics3description: Build or audit accessibility and semantic HTML in classic PHP WordPress themes on WP 7.1. Covers `language_attributes()`, `wp_head()`, `body_class()`, `wp_body_open()`, landmarks, skip links, focus management, `screen-reader-text`, heading order, nav/button semantics, `aria-controls` and `aria-expanded`, forms and labels, image alt handling, icon accessibility, reduced motion CSS, search forms, and common mistakes such as hover-only menus, hidden focus outlines, fake buttons, missing main landmarks, or unlabeled controls.4---56# Classic Theme Accessibility and Semantics78Use this when creating or reviewing the HTML semantics, keyboard behavior, landmarks, skip links, focus styles, forms, images, icons, and ARIA behavior of a classic PHP WordPress theme.910Accessibility is not a visual polish pass. It affects template structure, PHP output, CSS, and JavaScript behavior.1112## When to Use This Skill1314- Writing `header.php`, `footer.php`, `index.php`, `single.php`, `page.php`, or navigation templates.15- Adding a mobile menu, modal, search toggle, carousel, tabs, accordion, or interactive widget.16- Reviewing theme markup for WordPress.org-style accessibility expectations.17- Fixing keyboard navigation, missing focus states, unlabeled controls, bad heading order, or broken landmarks.1819## Document Skeleton2021A classic theme header should preserve core hooks and language/body helpers.2223```php24<!doctype html>25<html <?php language_attributes(); ?>>26<head>27 <meta charset="<?php bloginfo( 'charset' ); ?>">28 <meta name="viewport" content="width=device-width, initial-scale=1">29 <?php wp_head(); ?>30</head>3132<body <?php body_class(); ?>>33<?php wp_body_open(); ?>34```3536Rules:3738- Use `language_attributes()` on `<html>`.39- Use `bloginfo( 'charset' )` for the charset.40- Use `wp_head()` before closing `</head>`.41- Use `body_class()` on `<body>`.42- Call `wp_body_open()` immediately after the opening body tag.43- Add `add_theme_support( 'title-tag' )` in theme setup; do not hardcode `<title>`.4445## Landmarks4647Use semantic landmarks so users can navigate the page structure.4849```php50<header id="masthead" class="site-header">51 <nav id="site-navigation" class="main-navigation" aria-label="<?php esc_attr_e( 'Primary menu', 'textdomain' ); ?>">52 ...53 </nav>54</header>5556<main id="main" class="site-main">57 ...58</main>5960<footer id="colophon" class="site-footer">61 ...62</footer>63```6465Rules:6667- Each page should have a clear main landmark.68- Major navigation landmarks need accessible names when there is more than one `nav`.69- Do not put the entire page in generic `<div>` elements when semantic elements fit.70- Do not create multiple unlabeled `main` elements.7172## Skip Links7374Add a skip link that becomes visible on focus and targets real content.7576```php77<a class="skip-link screen-reader-text" href="#main">78 <?php esc_html_e( 'Skip to content', 'textdomain' ); ?>79</a>80```8182The target must exist. If focus needs to move reliably in JavaScript-assisted layouts, make the target focusable:8384```php85<main id="main" class="site-main" tabindex="-1">86```8788Rules:8990- The skip link must be the first meaningful focusable element after `wp_body_open()`.91- It must become visible on keyboard focus.92- The `href` must point to an ID that exists on every template.9394## Screen Reader Text CSS9596Include a standard visually-hidden utility and restore it on focus.9798```css99.screen-reader-text {100 position: absolute;101 width: 1px;102 height: 1px;103 padding: 0;104 margin: -1px;105 overflow: hidden;106 clip: rect(1px, 1px, 1px, 1px);107 word-wrap: normal;108 border: 0;109}110111.screen-reader-text:focus {112 top: 5px;113 left: 5px;114 z-index: 100000;115 display: block;116 width: auto;117 height: auto;118 padding: 15px 23px 14px;119 clip: auto;120 background: #fff;121 color: #000;122 font-size: 1rem;123 font-weight: 700;124 text-decoration: none;125}126```127128Rules:129130- Do not use `display: none` for content that screen readers need.131- Make skip links visible on focus, not only available to assistive tech.132- Keep focus styles visible above sticky headers.133134## Headings135136Headings describe document structure.137138Rules:139140- Use one clear `h1` for the primary page title.141- Archive/search pages usually use the archive/search title as `h1`.142- Posts in archive cards should usually use `h2`.143- Do not choose heading levels based only on font size.144- Do not skip levels to achieve visual styling; style with CSS.145146## Links, Buttons, and Toggles147148Use elements by behavior:149150- Links navigate to URLs.151- Buttons perform actions on the current page.152- Form controls collect input.153154Mobile menu toggle:155156```php157<button158 class="menu-toggle"159 type="button"160 aria-controls="primary-menu"161 aria-expanded="false"162>163 <?php esc_html_e( 'Menu', 'textdomain' ); ?>164</button>165```166167Rules:168169- Do not use `<a href="#">` as a button.170- Keep visible labels or accessible names for icon-only buttons.171- Update `aria-expanded` when a controlled region opens/closes.172- `aria-controls` must reference an existing ID.173- Avoid adding ARIA when native HTML already communicates the behavior correctly.174175## Focus and Keyboard176177Rules:178179- Never remove `outline` without a visible replacement.180- Use `:focus-visible` where appropriate, with a fallback if supporting older browsers.181- Hover-only menus are not enough. Keyboard users must be able to open and traverse navigation.182- Modals and off-canvas panels need focus management and Escape behavior. If the theme cannot implement that correctly, avoid shipping the interaction.183- Do not trap focus in ordinary dropdown navigation.184185## Forms and Search186187Rules:188189- Every input needs a real `<label>` or an equivalent accessible name.190- Placeholder text is not a label.191- Error messages should identify the field and be programmatically connected when possible.192- Use `get_search_form()` unless the theme has a specific accessible custom search form.193- Nonces and validation are security concerns, but accessible errors are still required for user correction.194195## Images and Icons196197Rules:198199- Informative images need useful alt text.200- Decorative images should use empty alt text.201- Do not stuff keywords into alt text.202- For featured images, rely on WordPress image functions where possible and ensure attachment alt text is maintained.203- SVG icons used only as decoration should have `aria-hidden="true"` and `focusable="false"`.204- Icon-only buttons/links need an accessible name via visible text, `.screen-reader-text`, or `aria-label`.205206## Motion and Visual Requirements207208Respect reduced motion preferences.209210```css211@media (prefers-reduced-motion: reduce) {212 *,213 *::before,214 *::after {215 scroll-behavior: auto !important;216 animation-duration: 0.01ms !important;217 animation-iteration-count: 1 !important;218 transition-duration: 0.01ms !important;219 }220}221```222223Rules:224225- Ensure text contrast is sufficient in normal, hover, and focus states.226- Do not rely on color alone to communicate state.227- Keep line heights and spacing readable.228- Avoid auto-playing motion-heavy UI. If used, provide pause/stop controls.229230## WordPress Template Helpers231232Preserve helpers that contribute useful classes and core integration:233234- `body_class()` on `<body>`.235- `post_class()` on post wrappers.236- `wp_body_open()` after `<body>`.237- `wp_head()` and `wp_footer()`.238- `language_attributes()` on `<html>`.239- `the_custom_logo()` when using core custom logo support.240- `get_search_form()` for baseline search form behavior.241242## Review Checklist243244- Header uses `language_attributes()`, `wp_head()`, `body_class()`, and `wp_body_open()`.245- Page has one clear main landmark.246- Skip link exists, is focus-visible, and points to a real ID.247- Navigation landmarks have accessible names.248- Mobile toggles are buttons and update `aria-expanded`.249- Keyboard focus is visible everywhere.250- Headings follow document structure.251- Forms have labels and understandable errors.252- Images and SVG icons have appropriate alt/ARIA behavior.253- Reduced motion is respected for animations/transitions.254- Theme does not rely on hover-only interactions.255256## Common Mistakes257258- Hiding skip links with `display: none`.259- Removing outlines globally.260- Using a fake link for menu/search toggles.261- Adding `role="button"` to an anchor instead of using a button.262- Creating several unlabeled navigation landmarks.263- Outputting icon-only buttons with no accessible name.264- Making dropdown menus impossible to use with a keyboard.265- Forgetting `wp_body_open()` in `header.php`.266267## References268269- Official documentation: <https://developer.wordpress.org/themes/functionality/accessibility/>270- Official documentation: <https://developer.wordpress.org/themes/classic-themes/basics/template-tags/>271- Verified source paths:272 - `wp-includes/general-template.php`273 - `wp-includes/post-template.php`274 - `wp-includes/nav-menu-template.php`275 - `wp-includes/class-walker-nav-menu.php`276 - `wp-content/themes/storefront/inc/structure/header.php`277 - `wp-content/themes/generatepress/header.php`