Grav is a flat-file CMS: there is no database, content lives in Markdown files with YAML frontmatter, and pages are rendered through Twig templates supplied by a theme. That architecture puts a developer firmly in control of the HTML that reaches the browser, which is good news for accessibility - nothing forces div soup or inaccessible widgets on you - but it also means that nothing is accessible unless the theme and templates make it so. Like the static site generators it resembles, Grav gives you total control and total responsibility. The recurring issues follow from how Grav content is authored and rendered. Content is written in Markdown, so heading structure depends entirely on author discipline: it is easy to open a page with a level-two heading and skip to a level-four, or to use bold text where a heading belongs, and Markdown happily renders whatever you type. Images are added either through Markdown image syntax or in Twig, and the alt text is only as good as what the author writes in the square brackets - blank brackets ship an empty alt. Themes are where the structural decisions live: the default Quark theme is a reasonable starting point, but page-tree navigation, skip links, landmark regions, focus styles, and the markup of any partials are all defined in Twig templates you can edit and are therefore yours to get right. Interactive features usually come from plugins or your own JavaScript, so accordions, modals, and menus need the same keyboard and ARIA care you would give any hand-built component. The official Form plugin renders fields from a YAML definition, and its default output needs checking for persistent labels and properly associated errors. Finally, the optional Admin panel is a web app your editors use, with its own accessibility considerations. This checklist covers the template and authoring decisions that determine whether a Grav site passes a WCAG 2.1 AA, EN 301 549, or EAA audit.

Common Accessibility Issues

serious

Markdown Heading Structure Left to Author Discipline

WCAG 1.3.1

Because content is authored in Markdown, heading levels are whatever the writer types - and Markdown renders them faithfully, mistakes and all. Pages commonly skip levels (an H2 followed by an H4), repeat the H1 if the template also outputs the page title as an H1, or use bold text in place of a real heading. Screen reader users who navigate by heading then get a broken outline.

How to fix:

Decide where the single H1 comes from - usually the page title rendered by the Twig template - and instruct authors to start body content at H2 (## in Markdown) and never skip levels. If the template emits the title as H1, authors should not also write an H1 in the Markdown. Add a content-linting step (for example a Markdown linter or a CI check) and review pages with a headings-list tool before publishing.

Before
# Page Title

#### A subsection (skips H2 and H3)

**A bold line pretending to be a heading**
After
<!-- H1 comes from the Twig template's page title -->

## A real section heading

### A subsection in order
critical

Markdown and Twig Images Without Alt Text

WCAG 1.1.1

Images added with Markdown syntax carry only the alt text the author types between the square brackets, and empty brackets produce an empty alt attribute on an informative image. Images output in Twig templates are only as accessible as the template author makes them - a hard-coded <img> with no alt, or a loop that omits alt, ships informative images that read as nothing to screen reader users.

How to fix:

Write meaningful alt text in Markdown image syntax for every informative image, and reserve empty alt only for genuinely decorative images. In Twig templates, always output an alt attribute and source it from page frontmatter or media metadata rather than leaving it blank. Establish a convention (for example a required 'alt' key in image frontmatter) so the data exists for templates to render.

Before
![](/images/team.jpg)

{# Twig #}
<img src="{{ image.url }}">
After
![Our five-person support team at their desks](/images/team.jpg)

{# Twig #}
<img src="{{ image.url }}" alt="{{ image.meta.alt }}">
serious

Theme Templates Missing Landmarks, Skip Links, and Focus Styles

WCAG 2.4.1

The structural scaffolding of a Grav site - landmark regions (header, nav, main, footer), a skip-to-content link, and visible keyboard focus styles - lives in the theme's Twig templates and CSS. A custom theme, or a heavily modified Quark, can easily omit a skip link, wrap everything in generic divs instead of landmarks, or remove the focus outline for aesthetics, leaving keyboard and screen reader users without orientation or a way to bypass repeated navigation.

How to fix:

In the base Twig template, use semantic landmark elements (<header>, <nav>, <main>, <footer>), make the first focusable element a skip-to-content link that targets <main>, and ensure CSS preserves a clearly visible :focus-visible style. Quark provides much of this; if you build or fork a theme, port these pieces deliberately. Test by Tabbing from the very top of the page.

serious

Page-Tree Navigation Without Keyboard-Operable Submenus

WCAG 2.1.1

Grav navigation is generated in Twig from the page tree, and dropdown or multi-level menus are often built as hover-only CSS menus or with JavaScript that lacks keyboard support. Submenus that open only on mouse hover, with no aria-expanded state and no Escape-to-close, cannot be operated by keyboard or understood by screen reader users.

How to fix:

Render navigation as a real list of links inside a <nav> landmark, and build any submenus as keyboard-operable disclosures: a real button toggle with aria-expanded, open on Enter/Space, close on Escape, and manage focus. Avoid hover-only reveal as the sole mechanism. Because the menu is generated from the page tree in one template, fixing the navigation partial corrects it site-wide.

serious

Form Plugin Fields Without Persistent Labels or Error Association

WCAG 3.3.1

Grav's official Form plugin renders fields from a YAML definition, and a quick configuration tends to rely on placeholder text instead of labels and to surface validation errors in a generic block not tied to the field that failed. Placeholders are not reliable labels and disappear on focus, and an unassociated error message cannot be navigated to or reliably announced.

How to fix:

Give every form field a persistent visible <label> in the form template, associated via for/id, rather than depending on placeholders. Mark required fields in text and set aria-required; on validation failure set aria-invalid and tie each message to its field with aria-describedby, with an error summary that receives focus. Customise the Form plugin's field templates so this markup is the default output, not an afterthought.

moderate

Admin Panel and Plugin Widgets Inaccessible to Editors

WCAG 4.1.2

The optional Admin panel is a web application your editors use to manage content, and any custom interactive components you add to the front end through plugins or your own JavaScript - accordions, tabs, modals, carousels - are commonly built as click-only divs with no roles, states, or keyboard support. Editors with disabilities can struggle in the Admin, and front-end widgets become barriers for all keyboard and screen reader users.

How to fix:

Build front-end interactive components against the WAI-ARIA Authoring Practices: real buttons, correct roles and aria-expanded/aria-selected state, full keyboard operation, focus management, and visible focus. Prefer native elements (details/summary, dialog) where they fit. For the Admin panel, keep custom field types and any UI you inject labelled and keyboard-operable so editors who rely on assistive technology can work.

Grav-Specific Tips

  • Grav is developer-controlled like a static site generator: the theme's Twig templates decide landmarks, skip links, navigation, and focus styles, so accessibility lives in your templates - if you do not build it, it is not there.
  • Heading structure is the most common content problem because Markdown renders whatever authors type; decide that the single H1 comes from the template's page title, have authors start at H2 (##), and lint content so skipped levels are caught.
  • Make alt text part of your data model: require an 'alt' key in image frontmatter or media metadata so Twig templates always have something real to render and never ship an empty alt on an informative image.
  • Start from the Quark default theme where you can - it handles much of the landmark, navigation, and focus scaffolding - and when you fork or build a theme, deliberately port the skip link, landmarks, and visible :focus-visible style.
  • Navigation is generated from the page tree in one Twig partial, so build it once as an accessible nav (real list, landmark, keyboard-operable submenus with aria-expanded) and the whole site benefits.
  • Customise the Form plugin's field templates so persistent labels and associated errors are the default output, and remember the optional Admin panel is a real web app your editors use daily and should be keyboard- and screen-reader-friendly too.

axe DevTools (browser extension)

Deque's free extension runs WCAG checks on rendered Grav pages - use it after theme changes to catch missing alt text, contrast issues, and structural problems your Twig templates produce.

Pa11y

A command-line accessibility tester you can run against your built Grav URLs and wire into CI, so template regressions are caught automatically on every change.

markdownlint

A Markdown linter that can flag heading-structure problems (skipped levels, multiple H1s) in your content files before they are ever rendered.

Grav: Where Accessibility Responsibility Lives

Plugin / Tool LayerWhat Grav ProvidesWhat You Must Build
Content 1.3.1 Markdown rendering of whatever you typeHeading discipline (one H1, then H2/H3 in order)
Images 1.1.1 Markdown/Twig image outputRequired alt data and rendering it every time
Theme templates 2.4.1 Quark theme as a starting pointLandmarks, skip link, and visible focus styles
Navigation 2.1.1 Menu data from the page treeA nav landmark and keyboard-operable submenus
Forms / Admin 3.3.1 Form plugin and the optional Admin panelLabelled fields, associated errors, usable admin UI

Frequently Asked Questions

Is a Grav site accessible by default?

Grav gives you a developer's level of control, which means there is no automatic accessibility to inherit - the HTML that reaches the browser comes from your theme's Twig templates and your Markdown content, so the site is exactly as accessible as you build it. The default Quark theme is a reasonable starting point and handles much of the landmark, navigation, and focus scaffolding, but the moment you fork a theme, build your own, or author content carelessly, accessibility becomes your responsibility. The upside is total control with nothing forcing inaccessible markup on you; the catch is that nothing is accessible unless you make it so.

How do I keep heading structure correct in Markdown?

Decide where the single H1 comes from - on most Grav sites the Twig template renders the page title as the H1 - and have authors start their body content at H2 (## in Markdown), never skipping levels and never using bold text in place of a heading. The risk with Markdown is that it renders exactly what you type, so a skipped level or a stray H1 goes live unnoticed. Add a Markdown linter such as markdownlint to flag heading problems in content files, and review pages with a headings-list tool or a screen reader's headings list before publishing.

How do I make sure images in Grav have alt text?

Treat alt text as part of your content data, not an afterthought. For images written in Markdown, always fill in meaningful alt text between the square brackets and reserve empty brackets for genuinely decorative images. For images output in Twig templates, always render an alt attribute and source it from a required field - for example an 'alt' key in the image's frontmatter or media metadata - so the template never emits a blank alt on an informative image. Making the alt field required in your data model is the most reliable way to stop missing descriptions from shipping.

Does Grav meet WCAG 2.1 AA and the EAA?

Grav can be built to meet WCAG 2.1 AA, EN 301 549, and the European Accessibility Act, but conformance is a property of your theme, templates, and content, not of the CMS itself. Because you control all the markup, you can reach AA cleanly - semantic landmarks, a skip link, real heading structure, alt text on informative images, keyboard-operable navigation and widgets, and labelled forms - without fighting a page builder. The flip side is that nothing is done for you beyond what Quark provides. Validate with axe or Pa11y in CI plus a manual keyboard and screen reader pass, then record the result in an accessibility statement.

Further Reading

Other CMS Checklists