Home Blog Accessibility Best Practices for Jekyll Themes (WCAG 2.2 Guide)
Tutorial

Accessibility Best Practices for Jekyll Themes (WCAG 2.2 Guide)

Build accessible Jekyll themes that meet WCAG 2.2 standards — semantic HTML, keyboard navigation, colour contrast, ARIA labels, skip links, and automated testing.

Accessibility Best Practices for Jekyll Themes (WCAG 2.2 Guide)

Accessibility is not a nice-to-have — it determines whether your content reaches everyone. An inaccessible Jekyll theme excludes users with visual, motor, and cognitive disabilities, and it signals to search engines that your site is lower quality. This guide covers the practical changes that move a Jekyll theme from unusable to WCAG 2.2 AA compliant.


Why Accessibility Matters for Jekyll Sites

  • Legal: WCAG 2.1 AA is a legal requirement for public-facing sites in the EU, UK, US (ADA), and many other countries
  • SEO: Accessible HTML is semantically rich HTML — the same practices that help screen readers help search engines
  • Reach: 15% of the global population has some form of disability; poor accessibility excludes them
  • Quality signal: Google’s page quality guidelines explicitly include accessibility as a factor

1. Semantic HTML Structure

The single most impactful accessibility change is using the right HTML elements.

Use Landmark Elements

<!-- Wrong -->
<div class="header">...</div>
<div class="nav">...</div>
<div class="main">...</div>
<div class="footer">...</div>

<!-- Right -->
<header class="site-header">...</header>
<nav class="site-nav" aria-label="Main navigation">...</nav>
<main id="main-content" class="site-main">...</main>
<footer class="site-footer">...</footer>

Screen reader users navigate by landmark. Without <header>, <nav>, <main>, <footer>, they can’t jump to sections.

Heading Hierarchy

One <h1> per page. Then <h2> for major sections, <h3> for subsections:


<!-- _layouts/default.html -->
<!-- The page title is always the H1 -->

<!-- _layouts/post.html -->
<h1 class="post-title">{{ page.title }}</h1>

<!-- Post content headings start at H2 -->
<!-- In Markdown: ## Section → <h2>, ### Subsection → <h3> -->

In _config.yml, configure kramdown to handle heading anchors:

kramdown:
  auto_ids: true

Use <article>, <section>, <aside>


<article class="post">          <!-- Self-contained content -->
  <header class="post-header">  <!-- Intro of the article -->
    <h1>{{ page.title }}</h1>
  </header>
  <section class="post-content"> <!-- Thematic section -->
    {{ content }}
  </section>
  <footer class="post-footer">  <!-- Metadata, tags -->
    ...
  </footer>
</article>

<aside class="sidebar" aria-label="Sidebar"> <!-- Supplementary content -->
  ...
</aside>


2. Keyboard Navigation

Every interactive element must be reachable and operable with a keyboard alone — no mouse required.

Add a skip link as the very first element in <body>. Users who navigate by keyboard can skip the nav and jump straight to content:

<!-- In _layouts/default.html, first element inside <body> -->
<a href="#main-content" class="skip-link">Skip to main content</a>

<header>...</header>
<main id="main-content">...</main>
.skip-link {
  position: absolute;
  top: -100%;
  left: 1rem;
  background: var(--color-primary);
  color: #fff;
  padding: 0.5rem 1rem;
  border-radius: 0 0 var(--radius-md) var(--radius-md);
  font-weight: 600;
  text-decoration: none;
  z-index: 999;
  transition: top 0.2s;

  &:focus {
    top: 0;
  }
}

This link is invisible until a keyboard user tabs to it — then it appears at the top of the screen.

Focus Styles

Never remove focus outlines:

// Wrong
*:focus { outline: none; }
*:focus-visible { outline: none; }

// Right — enhance the focus style instead of removing it
*:focus-visible {
  outline: 3px solid var(--color-primary);
  outline-offset: 2px;
  border-radius: 2px;
}

:focus-visible shows the outline only for keyboard navigation, not mouse clicks — so it doesn’t feel cluttered for mouse users.

Mobile Navigation Toggle

<button class="nav-toggle" 
        aria-controls="main-menu"
        aria-expanded="false"
        aria-label="Open navigation menu">
  <span aria-hidden="true"></span>
</button>

<nav id="main-menu" aria-label="Main navigation">
  ...
</nav>
const toggle = document.querySelector('.nav-toggle');
const menu = document.querySelector('#main-menu');

toggle.addEventListener('click', () => {
  const isOpen = toggle.getAttribute('aria-expanded') === 'true';
  toggle.setAttribute('aria-expanded', String(!isOpen));
  // Show/hide menu
});

The aria-expanded attribute tells screen readers whether the menu is open or closed.


3. Colour Contrast

WCAG 2.2 AA requires:

  • Normal text (< 18pt): minimum 4.5:1 contrast ratio against background
  • Large text (≥ 18pt or 14pt bold): minimum 3:1 contrast ratio
  • UI components (buttons, inputs, focus indicators): minimum 3:1

Check Your Colours

Use WebAIM Contrast Checker or the browser DevTools accessibility panel.

Common failures in Jekyll themes:

  • Light grey text on white: #999 on #fff = 2.85:1 (fails AA)
  • Placeholder text in forms: often too light
  • Disabled button text
// Common fixes
$color-text-muted: #6b7280;    // 4.6:1 on white — just passes AA
$color-text-muted: #595959;    // 7:1 on white — passes AAA, safer

// Never use lighter than #767676 for body text on white

Dark Mode Contrast

Check both light and dark mode. A common mistake is fixing contrast in light mode and breaking it in dark mode:

:root {
  --text-muted: #6b7280;     // 4.6:1 on #fff ✓
}

[data-theme="dark"] {
  --text-muted: #9ca3af;     // 4.6:1 on #1f2937 ✓ — check this independently
}

4. Images and Alt Text

Every <img> needs an alt attribute. Its value depends on the image’s purpose:

<!-- Meaningful image — describe what it shows -->
<img src="jekyll-setup.png" alt="Terminal showing Jekyll server starting on port 4000">

<!-- Decorative image — empty alt, so screen readers skip it -->
<img src="divider.svg" alt="" role="presentation">

<!-- Image that IS the content of a link — describe the destination -->
<a href="/themes/chirpy/">
  <img src="chirpy-preview.jpg" alt="Chirpy Jekyll theme preview">
</a>

In Jekyll templates, use front matter for alt text:


{% if post.image %}
  <img src="{{ post.image | relative_url }}" 
       alt="{{ post.image_alt | default: post.title }}"
       loading="lazy"
       width="800" height="450">
{% endif %}


5. Forms

<form>
  <!-- Every input needs a visible, programmatically-associated label -->
  <div class="form-group">
    <label for="email">Email address</label>
    <input type="email" id="email" name="email" 
           required
           aria-describedby="email-hint"
           autocomplete="email">
    <p id="email-hint" class="form-hint">We'll never share your email.</p>
  </div>

  <!-- Don't use placeholder as a label substitute -->
  <!-- Placeholders disappear when typing — users forget what the field is for -->

  <!-- Error messages -->
  <input type="email" 
         aria-invalid="true"
         aria-describedby="email-error">
  <p id="email-error" role="alert" class="form-error">
    Please enter a valid email address.
  </p>

  <button type="submit">Subscribe</button>
</form>

<!-- Links must describe their destination -->
<!-- Wrong -->
<a href="/themes/minimal-mistakes/">Click here</a>

<!-- Right -->
<a href="/themes/minimal-mistakes/">View Minimal Mistakes theme</a>

<!-- When context makes it clear, aria-label can supplement -->
<a href="/themes/chirpy/" aria-label="View Chirpy Jekyll theme details">
  Chirpy
</a>

<!-- Buttons must describe their action -->
<!-- Wrong -->
<button onclick="toggleMenu()"></button>

<!-- Right -->
<button onclick="toggleMenu()" aria-label="Open navigation menu"></button>

<!-- Don't use <a> for actions that aren't navigation -->
<!-- Wrong -->
<a href="#" onclick="submitForm()">Submit</a>

<!-- Right -->
<button type="submit">Submit</button>

7. Automated Testing

axe DevTools

Install the axe DevTools browser extension — it scans any page and reports WCAG violations with severity levels and fix guidance.

htmlproofer

Check for missing alt text and broken links as part of your build:

bundle exec htmlproofer ./_site \
  --checks Images,Links \
  --disable-external \
  --ignore-empty-alt

Lighthouse

Google’s Lighthouse (built into Chrome DevTools → Lighthouse tab) includes an Accessibility audit. Aim for 90+ score.

# Or run from CLI
npx lighthouse https://yourdomain.com --only-categories=accessibility

Manual Testing

Tools catch about 30% of accessibility issues. Also test:

  • Tab through the entire page with keyboard only — can you reach everything?
  • Test with a screen reader: NVDA (Windows, free), VoiceOver (Mac, built-in)
  • Zoom browser to 200% — does layout break?
  • Test in forced colours mode (Windows High Contrast)

Quick Wins Checklist

  • lang attribute on <html>: <html lang="en">
  • Skip link to main content
  • <main id="main-content"> landmark
  • One <h1> per page
  • All images have alt attributes
  • All form inputs have associated <label> elements
  • Focus styles visible (not outline: none)
  • Text contrast 4.5:1 minimum
  • Interactive elements reachable by keyboard
  • aria-label on icon-only buttons
  • aria-current="page" on active nav link
  • Lighthouse accessibility score 90+

Accessible themes are better themes — cleaner HTML, better SEO, and a wider audience. Browse Jekyll themes on JekyllHub and check the theme’s accessibility score before choosing.


References

Building accessibility into your Jekyll theme selection

The fastest path to an accessible Jekyll site is starting with an accessible theme. Evaluating theme accessibility before installation saves significant remediation work later. When evaluating a theme, open the live demo and test keyboard navigation — press Tab to move through interactive elements and verify that the focus ring is visible at every step. Press Enter on links and buttons to confirm they activate correctly. Navigate to the site using only the keyboard without touching the mouse; if you get stuck, the theme has accessibility problems.

Run the theme demo through Lighthouse (Accessibility tab) and the WAVE browser extension. Themes scoring below 90 in Lighthouse accessibility have structural issues — missing alt text, insufficient colour contrast, unlabelled form elements, or incorrect ARIA usage — that will require fixing in every template file that inherits from the problematic layout. Themes scoring 95 or above are strong starting points, though you will still need to ensure your content (images with meaningful alt text, descriptive link text, properly ordered headings) maintains that baseline.

Semantic HTML is the foundation of theme accessibility. Look at the theme’s source code for correct use of landmarks: <nav> for navigation, <main> for primary content, <article> for blog posts, <aside> for sidebars, <header> and <footer> for their respective roles. Screen readers use these landmarks to let users jump directly to the content they want — a site without them forces screen reader users to wade through every navigation link on every page. Themes that use generic <div> containers for everything rather than semantic HTML are a red flag.

Colour contrast and typography for all users

Colour contrast is the most commonly cited WCAG failure, and it is entirely preventable. The WCAG 2.1 AA standard requires a contrast ratio of at least 4.5:1 for normal text and 3:1 for large text (18pt or 14pt bold). Many popular design colour combinations fail this threshold — light grey text on white, muted blue links on grey backgrounds, dark text on dark images.

Check your theme’s default colour palette using the WebAIM Contrast Checker before customising. If the default colours fail, adjust them before launching. When choosing custom brand colours, test them against their backgrounds before applying them sitewide. A colour palette that passes contrast checks in both light and dark modes is a prerequisite for an accessible site, not an optional enhancement.

Typography accessibility goes beyond colour. Line height should be at least 1.5 for body text — cramped leading makes text harder to read for users with dyslexia or low vision. Line length should stay between 60 and 80 characters for comfortable reading — overly long lines increase the cognitive load of tracking from the end of one line to the beginning of the next. Font size should default to at least 16px for body text; smaller defaults are a common accessibility issue in themes optimised for visual density over readability.

Testing your Jekyll site with real assistive technology

Automated tools like Lighthouse and WAVE catch perhaps 30-40% of accessibility issues. The remainder require manual testing with assistive technology. The most widely used screen readers are NVDA (free, Windows), JAWS (paid, Windows), and VoiceOver (built into macOS and iOS). Testing with at least one of these reveals issues that automated tools miss entirely — confusing reading order, redundant link text, missing skip-to-content functionality, and form labels that are visually present but not programmatically connected.

For Jekyll sites, the most valuable manual tests are: navigate the homepage by headings only (using the screen reader’s heading navigation key) and verify that the heading hierarchy tells a coherent story; navigate to a blog post and check that the article title is the first <h1> and all subsequent headings follow a logical order; navigate the site’s main navigation by keyboard and confirm that the current page is indicated (via aria-current="page" or similar); and submit a contact form using only the keyboard, verifying that error messages are announced by the screen reader when fields are missing.

Set a goal of testing with a screen reader at least once per quarter. The experience of navigating your site with audio output only is genuinely illuminating — problems that are invisible in a visual inspection become immediately apparent when you cannot see the page. This direct experience of the screen reader user’s journey is the most effective accessibility education available, and it consistently reveals improvements worth making.

Accessibility as a continuous practice

Accessibility is not a one-time audit you complete and then forget. Every new page type, every new component added to a theme, every content update is an opportunity to introduce or fix an accessibility issue. Building accessibility checking into your regular development workflow — running Lighthouse on every pull request, reviewing keyboard navigation when adding new interactive elements — keeps the site accessible as it grows.

Document your accessibility conventions in a brief style guide: how to write alt text for different image types, how to label form fields, which ARIA attributes are used in specific components, and how to test new components before merging. This documentation serves as onboarding for any new contributor and as a reference that prevents the same mistakes from being made repeatedly.

An accessible Jekyll site is not just ethically correct — it is a better site by every measurable dimension. Semantic HTML, clear typography, fast loading, and keyboard navigability benefit every user, not just those using assistive technology. Accessibility improvements and performance improvements frequently overlap. The habits that make a site accessible make it a site that everyone, on every device, can use comfortably.

Share LinkedIn