Jekyll is the static site generator that ships as the default engine behind GitHub Pages, which means a meaningful share of personal blogs, project documentation sites, conference websites, open-source landing pages, and small-business marketing sites in 2026 are still rendered by Jekyll templates that were forked from a Minima theme written in 2014. The Liquid templating language, the Rouge syntax highlighter, and the include partials that most Jekyll sites rely on were not designed with WCAG 2.1 AA in mind, and the layered nature of Jekyll themes (gem-based parent theme + local overrides + custom includes) means accessibility regressions can be hidden three directories deep. Real-world Jekyll sites repeatedly fail the same set of issues: the default Minima link colour fails 4.5:1 contrast on white, the skip-link is rendered visually but never gains focus on Tab because of an off-screen positioning bug, post pagination uses arrow glyphs without an accessible name, the Rouge default theme produces unreadable comments in code blocks, and the post-list page wraps everything in a single h1 so subsequent post titles render as h2 with no clear hierarchy. With the European Accessibility Act enforceable since 28 June 2025 against any business that distributes digital content into the EU, and the rapid expansion of ADA demand letters into technical blogs that drive course or SaaS sales, even hobbyist Jekyll blogs with a paid newsletter or Patreon funnel are squarely in scope. This checklist walks the writer-maintainer combo through the issues they can fix in Liquid layouts, _config.yml, _includes partials, and the Rouge or Pygments stylesheet that ships with most Jekyll themes.

Common Accessibility Issues

critical

Minima Default Link Colour Fails 4.5:1 Contrast on White Background

WCAG 1.4.3

The Minima theme that ships as Jekyll's default uses a brand-blue link colour (#828282 hover, #2a7ae2 base) that meets 4.5:1 only when the link sits next to its underline. When a Minima-based theme removes underlines for visual cleanliness (a common 'modernization' override) the colour-only differentiation fails 1.4.1 and the contrast often slips below 4.5:1 in the visited state, which Minima sets to #757575 on white (3.5:1).

How to fix:

Override the link colours in your assets/main.scss or assets/css/style.scss. Set $brand-color to a darker tone such as #1c5fc0 (5.6:1 on white) and ensure the visited link colour is no lighter than #595959 (7:1 on white). If you must remove underlines, restore them on hover and focus AND keep them present on links inside paragraph text. Audit with axe DevTools after running bundle exec jekyll serve.

Before
// _sass/minima/_base.scss
$brand-color: #2a7ae2;
a { color: $brand-color; text-decoration: none; }
a:visited { color: #757575; }
After
// _sass/minima/_base.scss override in assets/main.scss
$brand-color: #1c5fc0;
a { color: $brand-color; text-decoration: underline; text-underline-offset: 2px; }
a:visited { color: #595959; }
a:hover, a:focus-visible { text-decoration-thickness: 2px; }
critical

Skip-Link Is Visually Hidden but Never Gains Focus on Tab

WCAG 2.4.1

Many Jekyll themes include a 'Skip to main content' link in the header partial but position it with position: absolute; left: -9999px and never restore it on :focus. Keyboard users press Tab as their first action expecting to jump past the navigation, and instead they tab through every navigation link before reaching content, which fails WCAG 2.4.1 Bypass Blocks because the bypass mechanism exists in markup but is not operable.

How to fix:

Add a focus-visible rule to your skip-link CSS so the link slides into view when keyboard focus reaches it. The skip-link href must point at an id that exists on the main element (typically id='main-content'). Test by loading the home page, pressing Tab once, and confirming the skip-link appears at the top of the viewport with a visible focus indicator. Pressing Enter should jump focus to the main element.

Before
/* _sass/minima/_layout.scss */
.skip-link { position: absolute; left: -9999px; }
/* No focus rule */
After
/* _sass/minima/_layout.scss */
.skip-link { position: absolute; top: -40px; left: 0; padding: 8px 16px; background: #fff; color: #1c5fc0; z-index: 100; }
.skip-link:focus-visible { top: 0; outline: 3px solid #1c5fc0; }
serious

Rouge Default Theme Comments Fail Contrast in Light Mode

WCAG 1.4.3

The default Rouge stylesheet generated by rougify style base16.light uses a comment colour of #998 on a #f8f8f8 background that measures roughly 3.1:1, well below 4.5:1. Code is content in technical blogs, and the comments inside code blocks frequently carry the explanatory load (the code shows the syntax, the comment explains the why). Unreadable comments break the value proposition of a developer blog.

How to fix:

Regenerate the Rouge stylesheet with rougify style monokai.sublime > assets/syntax.css for dark themes, or rougify style github > assets/syntax.css for light themes. Both pass 4.5:1 across token types. If you must keep base16.light, override the .c, .c1, .cm, .cs token classes to a darker grey such as #57606a which measures 4.6:1 on #f8f8f8. Verify after rebuilding the site by inspecting a code block that contains a comment.

Before
/* assets/syntax.css generated by rougify style base16.light */
.highlight .c, .highlight .c1 { color: #998; font-style: italic; }
After
/* assets/syntax.css */
.highlight .c, .highlight .c1 { color: #57606a; font-style: italic; }
.highlight .cm, .highlight .cs { color: #57606a; }
serious

Pagination Uses '<' and '>' Glyphs Without Accessible Names

WCAG 2.4.4

Jekyll's jekyll-paginate plugin renders previous and next links using bare '<' and '>' characters or Unicode arrow glyphs in many community themes. Screen readers announce 'less-than link' or 'right-pointing-triangle link' which is not a meaningful link name. WCAG 2.4.4 requires every link to have a purpose that can be determined from the link text alone or together with its programmatically determined context.

How to fix:

Edit _layouts/home.html or your custom paginator partial to wrap the glyph in a span with aria-hidden='true' and add an accompanying visually-hidden label. The link text becomes 'Previous page' or 'Next page' for assistive technology while keyboard and pointer users still see the glyph.

Before
<!-- _includes/paginator.html -->
<a href='{{ paginator.previous_page_path }}'>&laquo;</a>
<a href='{{ paginator.next_page_path }}'>&raquo;</a>
After
<!-- _includes/paginator.html -->
<a href='{{ paginator.previous_page_path }}'><span aria-hidden='true'>&laquo;</span><span class='visually-hidden'>Previous page</span></a>
<a href='{{ paginator.next_page_path }}'><span aria-hidden='true'>&raquo;</span><span class='visually-hidden'>Next page</span></a>
serious

Post Archive Page Uses h1 for Site Title and h2 for Each Post Title

WCAG 1.3.1

Minima's home.html layout renders the site title in the header as h1 on every page, then loops the post archive as h2 entries. On the archive page itself this means the page has no top-level heading describing what the page is. The pattern fails 1.3.1 Info and Relationships because the heading hierarchy does not reflect the document structure.

How to fix:

Override _layouts/home.html and _layouts/default.html so the site title renders as a span inside the header, and the page-level h1 is reserved for the page title or the archive heading ('All Posts'). Each post in the archive becomes h2, which is correct. The single h1 per page rule is enforced by markdownlint MD025 if you add it to CI.

Before
<!-- _includes/header.html -->
<header>
  <h1 class='site-title'><a href='{{ "/" | relative_url }}'>{{ site.title }}</a></h1>
  <nav>...</nav>
</header>
After
<!-- _includes/header.html -->
<header>
  <p class='site-title'><a href='{{ "/" | relative_url }}'>{{ site.title }}</a></p>
  <nav>...</nav>
</header>
<!-- _layouts/home.html -->
<main id='main-content'>
  <h1>{{ page.title | default: "All Posts" }}</h1>
  ...
</main>
moderate

Disqus Comments Iframe Has No Title Attribute

WCAG 4.1.2

The Disqus comments include that ships with many Jekyll themes embeds an iframe with no title attribute. Screen readers fall back to announcing the iframe URL or just 'frame', which gives no indication that the region contains user comments. WCAG 4.1.2 Name, Role, Value requires every iframe to have an accessible name.

How to fix:

Edit _includes/disqus_comments.html (or your equivalent comments partial) to add title='Reader comments' to the iframe wrapper. If you use the Disqus official embed script, wrap the comments div in a section with role='region' and aria-labelledby pointing at a visually present h2 'Comments' heading.

Before
<!-- _includes/disqus_comments.html -->
<div id='disqus_thread'></div>
<script>...</script>
After
<!-- _includes/disqus_comments.html -->
<section aria-labelledby='comments-heading'>
  <h2 id='comments-heading'>Comments</h2>
  <div id='disqus_thread'></div>
  <script>...</script>
</section>
moderate

RSS Feed Link Is an Icon-Only Anchor With No Accessible Name

WCAG 2.4.4

Most Jekyll themes include an RSS feed link in the footer or social-icons section rendered as an SVG or icon font without surrounding text. The anchor has no aria-label and the SVG has no title element, so screen readers announce only 'link' or read out the SVG file name.

How to fix:

Add aria-label='Subscribe via RSS' to the anchor, and either add a title element inside the SVG or set role='img' aria-label on the SVG itself. The icon remains visually present, but screen-reader users now know the link's purpose.

Before
<a href='/feed.xml'>
  <svg viewBox='0 0 16 16'>...</svg>
</a>
After
<a href='/feed.xml' aria-label='Subscribe via RSS'>
  <svg role='img' aria-hidden='true' viewBox='0 0 16 16'>...</svg>
</a>
moderate

Liquid {% include %} Partials Inject Duplicate Landmarks

WCAG 1.3.1

When a Jekyll theme is composed of a default layout that wraps content in <main>, plus an include partial (such as a series-navigation include) that also contains a <main>, the rendered page contains two main elements. Screen readers and landmark navigation tools (NVDA's D key, VoiceOver's rotor) become confused because there should be exactly one main per page.

How to fix:

Audit every _includes file for nested landmark elements (header, nav, main, footer, aside). Use semantic but non-landmark tags like section or div within partials, and reserve landmark elements for the layout file. A grep for '<main' across _includes and _layouts surfaces the duplicates quickly: grep -rn '<main' _includes _layouts.

Jekyll-Specific Tips

  • If you publish via GitHub Pages, GitHub builds your site with a fixed list of plugins. jekyll-accessibility is not on the safelist, so you must run accessibility checks in GitHub Actions before deploy rather than as a Jekyll plugin. The actions/setup-ruby + pa11y-ci pattern is well documented.
  • Avoid the github-pages gem if you need a newer Liquid feature or plugin. Switching to Jekyll 4.x and deploying via a custom Actions workflow gives you access to remark-lint, jekyll-redirect-from with proper status codes, and modern Rouge themes.
  • The site.lang config value sets the html lang attribute on every page. If you publish content in multiple languages, override it per page in front matter (lang: ja) and verify the rendered HTML has the correct lang attribute on a sample of localized pages.
  • Run jekyll build with the JEKYLL_ENV=production flag in CI, then point pa11y-ci at the contents of _site by serving them with http-server on a port. This catches accessibility regressions at the rendered HTML stage, which is more reliable than scanning Liquid templates.
  • If you use jekyll-archives to generate category and tag pages, audit the archive layouts separately. Many archive layouts skip the page-level h1 and start at h2, which fails 1.3.1.

pa11y-ci

Command-line accessibility tester that consumes a sitemap.xml and runs WCAG checks against every URL. Pair with the jekyll-sitemap plugin to test your full site on every push.

axe-core CLI

Node-based axe runner that produces violation reports per URL. Run after jekyll build against http-served _site directory inside GitHub Actions for free CI accessibility.

html-proofer

Ruby gem that validates rendered HTML for broken links, missing alt text, malformed images, and bad mailto links. Originally built for Jekyll sites and remains the fastest way to catch alt-text regressions in CI.

Further Reading

Other CMS Checklists