mindfulmarkup / mindfula11y
Find and fix accessibility issues in TYPO3: viewhelpers for semantic headings and landmarks, missing image alt detection and generation with AI (ChatGPT). A backend module for quick remediation. More features to come.
Installs: 246
Dependents: 0
Suggesters: 0
Security: 0
Stars: 3
Watchers: 0
Forks: 0
Open Issues: 0
Type:typo3-cms-extension
pkg:composer/mindfulmarkup/mindfula11y
Requires
- php: >=8.2 <=8.4
- typo3/cms-backend: ~13.4.0
- typo3/cms-core: ~13.4.0
- typo3/cms-extbase: ~13.4.0
- typo3/cms-fluid: ~13.4.0
- typo3/cms-frontend: ~13.4.0
README
The WFA Accessibility Toolkit is a TYPO3 extension that integrates accessibility tools directly into the TYPO3 backend, helping editors and integrators improve the accessibility of their content.
Installation
Install via Composer:
composer require mindfulmarkup/mindfula11y
Features
-
Alternative Text Checker: Backend module that lists all
sys_file_referencerecords (e.g., images) without alternative text, making it easy to find and fix missing alt attributes. -
AI-Powered Alt Text Generation: Supports generating alternative texts for images using ChatGPT.
-
Viewhelpers and TCA columns for heading types and landmarks: Various viewhelpers and a new accessibility tab for content elements to make it easy for editors to use accessible heading types and create ARIA landmarks.
-
Heading Structure Overview: Backend module that visualizes the heading structure of the selected page and allows editors to easily identify issues and edit heading types for records using the custom ViewHelper.
-
Landmark Structure Overview: Backend module that displays ARIA landmarks on the selected page. Editors can review landmark structure, identify accessibility issues, and edit landmark roles directly from the module to improve page navigation and semantic structure.
Planned Features
- Automated Accessibility Scanners: Integration of remote accessibility testing tools to review more issues directly in the backend.
Extension Settings
You can configure Mindful A11y in the extension settings:
- OpenAI API Key: Set your OpenAI API key for ChatGPT-powered features.
- Chat Model: Choose the OpenAI model for generating alternative text (e.g.,
gpt-4o-mini,gpt-4o). - Image Detail: Set the detail level for image analysis (
loworhigh). - Disable Alt Text Generation: Option to disable the AI-powered alternative text generation. Also inactive if no OpenAI API key is set.
Page TSconfig Options
Configure module behavior per page using Page TSconfig (Configuration/page.tsconfig):
mod {
mindfula11y_accessibility {
missingAltText {
enable = 1
ignoreColumns {
# Do not include column `image` from table `tt_content` in the alternative text check.
tt_content = image
}
}
headingStructure {
enable = 1
}
landmarkStructure {
enable = 1
}
}
web_layout {
mindfula11y {
# Hide mindfula11y accessibility info in page module
hideInfo = 0
}
}
}
Finding images with missing alternative text
The Accessibility module provides a list of sys_file_reference records that do not have alternative text set. This helps editors identify and fix images that are missing accessibility information.
Backend Module Integration
The Missing Alt Text checker is integrated into the main Accessibility backend module and provides filtering options based on:
- Current Page: Results are shown for the currently selected page in the page tree
- Record Type: Filter by specific tables (e.g.,
tt_content,pages) or show all record types - Language: Filter results by specific languages
- Recursive Depth: Choose how many page levels to scan (1, 5, 10, or 99 levels deep)
Inline Editing
Each missing alternative text entry provides:
- Image Preview: Thumbnail display with link to view the full-size image
- Alternative Text Input: Multi-line text area for entering descriptive text
- AI Generation: When ChatGPT is configured, a "Generate" button automatically creates alternative text
- Save Functionality: Individual saving for each image
- Record Link: Direct access to edit the original record containing the file reference
Configuration Options
The module can be configured using Page TSconfig to exclude specific columns:
mod {
mindfula11y_missingalttext {
enable = 1
ignoreColumns {
# Exclude specific columns from the alternative text check
tt_content = image,media
pages = media
}
}
}
Heading Types
The Mindful A11y extension adds a tx_mindfula11y_headingtype column to the tt_content table, allowing editors (with appropriate permissions) to set the semantic heading type for content elements. This enables precise control over the document's heading hierarchy for better accessibility.
What are Heading Types?
Heading types define the semantic level and HTML element used to render headings in your content. Proper heading structure is crucial for accessibility, as screen readers and other assistive technologies use headings to navigate and understand the page structure.
Available Heading Types
The extension provides the following heading type options:
- H1-H6: Semantic heading levels (
<h1>through<h6>) for proper document structure - Paragraph (p): For text that should be rendered as a paragraph rather than a heading (
<p>) - Generic div (div): For content that needs custom styling without any semantic meaning (
<div>)
Backend Module Integration
Headings are displayed in a tree structure within the Accessibility backend module, providing editors with a clear overview of the page's heading hierarchy. The module identifies accessibility issues such as missing H1 elements or skipped heading levels, and allows for easy editing of heading types directly from the tree view.
Using the Heading ViewHelpers in Fluid Templates
The Mindful A11y extension provides three ViewHelpers for rendering accessible, semantically correct headings in your Fluid templates:
Arguments for Heading and Landmark ViewHelpers
1. HeadingViewHelper (<mindfula11y:heading>) – Main/Standalone Headings
Arguments:
recordUid(int, optional): The UID of the record with the heading.recordTableName(string, optional, default:tt_content): Database table name of the record with the heading.recordColumnName(string, optional, default:tx_mindfula11y_headingtype): Name of field that stores the heading type.type(string, optional): The heading type to use (h1,h2, ...,h6,p,div, etc.). If not provided, the value will be fetched from the database record or set to"h2".relationId(string, optional): The relation identifier for this heading (used for referencing in sibling/descendant headings).
Renders a heading element (e.g., <h2>, <h3>, <p>) for a content record or static content. The tag is determined by the stored heading type, a provided type, or a default. Optionally, you can provide a relationId to allow descendant or sibling headings to reference this heading's type.
Basic usage for tt_content records:
<html xmlns:f="http://typo3.org/ns/TYPO3/CMS/Fluid/ViewHelpers" xmlns:mindfula11y="http://typo3.org/ns/MindfulMarkup/MindfulA11y/ViewHelpers" data-namespace-typo3-fluid="true" > <mindfula11y:heading recordUid="{data.uid}" recordTableName="tt_content" recordColumnName="tx_mindfula11y_headingtype" type="{data.tx_mindfula11y_headingtype}"> {data.header} </mindfula11y:heading> </html>
Note: If the
typeparameter is provided to the viewhelper, it uses this value directly and avoids an additional database lookup. This can improve performance by reducing unnecessary queries.
Static heading (no editing, no DB lookup):
<mindfula11y:heading type="h2">Static heading</mindfula11y:heading>
2. DescendantViewHelper (<mindfula11y:heading.descendant>) – Child/Incremented Headings
Arguments:
ancestorId(string, required): TherelationIdof the heading ancestor.levels(int, optional, default:1): How many levels to increment the heading type.type(string, optional): The heading type to use. If provided, overrides the computed tag and addslevels.recordUid,recordTableName,recordColumnName(optional): These arguments should only be used if the referenced ancestor (byancestorId) appears after this ViewHelper in the template, or if the cache lookup does not work. In normal usage, prefer usingancestorId.
Renders a heading as a descendant of a referenced ancestor heading, incrementing the heading level as needed. The ancestor is referenced by ancestorId (which must match a relationId set on a parent heading). The tag is computed by incrementing the ancestor's heading level by the levels argument (default: 1).
Usage:
<html xmlns:f="http://typo3.org/ns/TYPO3/CMS/Fluid/ViewHelpers" xmlns:mindfula11y="http://typo3.org/ns/MindfulMarkup/MindfulA11y/ViewHelpers" data-namespace-typo3-fluid="true" > <mindfula11y:heading recordUid="{data.uid}" recordTableName="tt_content" recordColumnName="tx_mindfula11y_headingtype" type="{data.tx_mindfula11y_headingtype}" relationId="mainHeading"> Parent heading </mindfula11y:heading> <mindfula11y:heading.descendant ancestorId="mainHeading" levels="1"> Child heading </mindfula11y:heading.descendant> </html>
Behavior notes:
- If the ancestor is a semantic heading (
h1–h6), the descendant will be the incremented heading level (e.g.,h2→h3). If the increment would exceedh6, the descendant becomes a paragraph (p). - You may override the computed tag by supplying a
typeargument to the descendant ViewHelper. - The ancestor must appear before the descendant in the template for the reference to work. If not, provide the
typeorrecordXarguments directly.
3. SiblingViewHelper (<mindfula11y:heading.sibling>) – Sibling Headings at the Same Level
Arguments:
siblingId(string, required): TherelationIdof the heading sibling.type(string, optional): The heading type to use. If provided, overrides the computed tag.recordUid,recordTableName,recordColumnName(optional): These arguments should only be used if the referenced sibling (bysiblingId) appears after this ViewHelper in the template, or if the cache lookup does not work. In normal usage, prefer usingsiblingId.
Renders a heading at the same level as a referenced sibling heading. The sibling is referenced by siblingId (which must match a relationId set on another heading). The tag is determined by the cached heading type of the sibling.
Usage:
<html xmlns:f="http://typo3.org/ns/TYPO3/CMS/Fluid/ViewHelpers" xmlns:mindfula11y="http://typo3.org/ns/MindfulMarkup/MindfulA11y/ViewHelpers" data-namespace-typo3-fluid="true" > <mindfula11y:heading recordUid="{data.uid}" recordTableName="tt_content" recordColumnName="tx_mindfula11y_headingtype" type="{data.tx_mindfula11y_headingtype}" relationId="mainHeading"> First heading </mindfula11y:heading> <mindfula11y:heading.sibling siblingId="mainHeading"> Sibling at same level </mindfula11y:heading.sibling> </html>
Behavior notes:
- The referenced sibling must appear before this ViewHelper in the template for the reference to work. If not, provide the
typeorrecordXarguments. - You may override the computed tag by supplying a
typeargument to the sibling ViewHelper. - If the referenced sibling is not a heading, the same tag is used.
Extending Custom Record Types
To add heading type support to custom record types, follow these steps:
1. Add TCA Column Definition
Create a TCA override file for your custom table (e.g., Configuration/TCA/Overrides/tx_myext_records.php):
<?php declare(strict_types=1); use MindfulMarkup\MindfulA11y\Enum\HeadingType; use TYPO3\CMS\Core\Utility\ExtensionManagementUtility; defined('TYPO3') or die(); // Add the heading type column to your custom table ExtensionManagementUtility::addTCAcolumns( 'tx_myext_records', [ 'headingtype' => [ 'exclude' => true, 'label' => 'LLL:EXT:mindfula11y/Resources/Private/Language/Database.xlf:ttContent.columns.mindfula11y.headingType', 'config' => [ 'type' => 'select', 'renderType' => 'selectSingle', 'default' => HeadingType::H2->value, 'items' => [ ['label' => HeadingType::H1->getLabelKey(), 'value' => HeadingType::H1->value], ['label' => HeadingType::H2->getLabelKey(), 'value' => HeadingType::H2->value], ['label' => HeadingType::H3->getLabelKey(), 'value' => HeadingType::H3->value], ['label' => HeadingType::H4->getLabelKey(), 'value' => HeadingType::H4->value], ['label' => HeadingType::H5->getLabelKey(), 'value' => HeadingType::H5->value], ['label' => HeadingType::H6->getLabelKey(), 'value' => HeadingType::H6->value], ['label' => HeadingType::P->getLabelKey(), 'value' => HeadingType::P->value], ['label' => HeadingType::DIV->getLabelKey(), 'value' => HeadingType::DIV->value], ], ], ], ] ); // Add the field to your record type's interface ExtensionManagementUtility::addToAllTCAtypes( 'tx_myext_records', 'headingtype', '', 'after:title' );
2. Update Database Schema
Add the database column to your ext_tables.sql:
CREATE TABLE tx_myext_records ( headingtype varchar(10) DEFAULT 'h2' NOT NULL );
Note: In TYPO3 13+, database schema updates are handled automatically based on TCA configuration, so manual
ext_tables.sqldefinitions are no longer required for TCA-defined fields.
3. Use in Fluid Templates
In your Fluid templates, use the ViewHelper with your custom table:
<html xmlns:f="http://typo3.org/ns/TYPO3/CMS/Fluid/ViewHelpers" xmlns:mindfula11y="http://typo3.org/ns/MindfulMarkup/MindfulA11y/ViewHelpers" data-namespace-typo3-fluid="true" > <mindfula11y:heading recordUid="{record.uid}" recordTableName="tx_myext_records" recordColumnName="headingtype" type="{record.headingtype}"> {record.title} </mindfula11y:heading> </html>
This integration allows your custom records to benefit from the same heading structure analysis and inline editing capabilities provided by the Accessibility backend module.
Landmarks
The Mindful A11y extension adds landmark-related columns to the tt_content table, allowing editors to define ARIA landmarks for better page navigation. These landmarks help screen reader users understand the structure and purpose of different page sections.
What are Landmarks?
ARIA landmarks identify the purpose of different sections of a page, making it easier for assistive technology users to navigate content. They provide semantic meaning to regions of a page and allow users to quickly jump between different sections.
Available Landmark Types
The extension provides the following landmark options:
- None: No landmark role applied
- Region: Important Section (only if none of the other options apply)
- Navigation: Navigation Menu (
<nav>) - Complementary: Sidebar / Related Content (
<aside>) - Main: Main Content Area (
<main>) - Banner: Site Header (
<header>) - Contentinfo: Site Footer (
<footer>) - Search: Search Function (
<search>) - Form: Form Section (
<form>)
Landmark Columns
The extension adds three columns to support landmarks:
tx_mindfula11y_landmark: The landmark type/roletx_mindfula11y_arialabelledby: Checkbox to use the content element's header as the landmark nametx_mindfula11y_arialabel: Custom landmark name (when not using the header)
Backend Module Integration
Landmarks are displayed in a hierarchical layout within the Accessibility backend module, providing editors with a clear overview of the page's landmark structure. The module identifies many accessibility issues and allows for easy editing of landmark roles and names directly from the structure view.
Using the Landmark ViewHelper
Arguments:
recordUid(int, optional): The UID of the record that is being rendered.recordTableName(string, optional, default:tt_content): Database table name of the record being rendered.recordColumnName(string, optional, default:tx_mindfula11y_landmark): Name of field that stores the role.role(string, optional): The landmark role value.tagName(string, optional): Override the HTML tag name regardless of the role. The role attribute will still be applied.
To apply landmarks in the frontend, use the provided LandmarkViewHelper. The ViewHelper renders the appropriate HTML element based on the landmark type and integrates with the backend module for inline editing capabilities.
The ViewHelper automatically selects semantic HTML elements for each landmark role:
- navigation →
<nav> - main →
<main> - banner →
<header> - contentinfo →
<footer> - complementary →
<aside> - search →
<search> - form →
<form> - region →
<section>
You can override the automatically selected tag using the optional tagName argument. When tagName is provided, the ViewHelper will use that element and add the appropriate role attribute.
Basic Usage for tt_content Records
<html xmlns:f="http://typo3.org/ns/TYPO3/CMS/Fluid/ViewHelpers" xmlns:mindfula11y="http://typo3.org/ns/MindfulMarkup/MindfulA11y/ViewHelpers" data-namespace-typo3-fluid="true" > <!-- If aria-labelledby is active and a header is set prefer over aria-label. Change based on your requirements. --> <f:if condition="{data.tx_mindfula11y_arialabelledby} && {data.header}"> <f:then> <f:variable name="ariaAttributes" value="{labelledby: 'c{data.uid}-heading'}" /> </f:then> <f:else if="{data.tx_mindfula11y_arialabel"> <f:variable name="ariaAttributes" value="{label: data.tx_mindfula11y_arialabel}" /> </f:else> <f:else> <f:variable name="ariaAttributes" value="{}" /> </f:else> </f:if> <mindfula11y:landmark recordUid="{data.uid}" recordTableName="tt_content" recordColumnName="tx_mindfula11y_landmark" role="{data.tx_mindfula11y_landmark}" aria="{ariaAttributes}"> {data.bodytext} </mindfula11y:landmark> </html>
Extending Custom Record Types
To add landmark support to custom record types, follow these steps:
1. Add TCA Column Definitions
Create a TCA override file for your custom table (e.g., Configuration/TCA/Overrides/tx_myext_records.php):
<?php declare(strict_types=1); use MindfulMarkup\MindfulA11y\Enum\AriaLandmark; use TYPO3\CMS\Core\Utility\ExtensionManagementUtility; defined('TYPO3') or die(); // Add the landmark columns to your custom table ExtensionManagementUtility::addTCAcolumns( 'tx_myext_records', [ 'landmark' => [ 'exclude' => true, 'label' => 'LLL:EXT:mindfula11y/Resources/Private/Language/Database.xlf:ttContent.columns.mindfula11y.landmark', 'description' => 'LLL:EXT:mindfula11y/Resources/Private/Language/Database.xlf:ttContent.columns.mindfula11y.landmark.description', 'onChange' => 'reload', 'config' => [ 'type' => 'select', 'renderType' => 'selectSingle', 'default' => AriaLandmark::NONE->value, 'items' => [ ['label' => AriaLandmark::NONE->getLabelKey(), 'value' => AriaLandmark::NONE->value], ['label' => AriaLandmark::REGION->getLabelKey(), 'value' => AriaLandmark::REGION->value], ['label' => AriaLandmark::NAVIGATION->getLabelKey(), 'value' => AriaLandmark::NAVIGATION->value], ['label' => AriaLandmark::COMPLEMENTARY->getLabelKey(), 'value' => AriaLandmark::COMPLEMENTARY->value], ['label' => AriaLandmark::MAIN->getLabelKey(), 'value' => AriaLandmark::MAIN->value], ['label' => AriaLandmark::BANNER->getLabelKey(), 'value' => AriaLandmark::BANNER->value], ['label' => AriaLandmark::CONTENTINFO->getLabelKey(), 'value' => AriaLandmark::CONTENTINFO->value], ['label' => AriaLandmark::SEARCH->getLabelKey(), 'value' => AriaLandmark::SEARCH->value], ['label' => AriaLandmark::FORM->getLabelKey(), 'value' => AriaLandmark::FORM->value], ], ], ], 'aria_labelledby' => [ 'exclude' => true, 'label' => 'LLL:EXT:mindfula11y/Resources/Private/Language/Database.xlf:ttContent.columns.mindfula11y.ariaLabelledby', 'description' => 'LLL:EXT:mindfula11y/Resources/Private/Language/Database.xlf:ttContent.columns.mindfula11y.ariaLabelledby.description', 'displayCond' => 'FIELD:landmark:!=:', 'onChange' => 'reload', 'config' => [ 'type' => 'check', 'renderType' => 'checkboxToggle', 'default' => 1, 'items' => [['label' => '']], ], ], 'aria_label' => [ 'exclude' => true, 'label' => 'LLL:EXT:mindfula11y/Resources/Private/Language/Database.xlf:ttContent.columns.mindfula11y.ariaLabel', 'description' => 'LLL:EXT:mindfula11y/Resources/Private/Language/Database.xlf:ttContent.columns.mindfula11y.ariaLabel.description', 'displayCond' => 'FIELD:landmark:!=:', 'config' => [ 'type' => 'input', 'size' => 50, 'max' => 255, 'eval' => 'trim', 'required' => true, ], ], ] ); // Add landmark palette $GLOBALS['TCA']['tx_myext_records']['palettes']['landmarks'] = [ 'label' => 'LLL:EXT:mindfula11y/Resources/Private/Language/Database.xlf:ttContent.palettes.landmarks', 'showitem' => 'landmark, --linebreak--, aria_labelledby, aria_label' ]; // Add the accessibility tab with landmarks palette ExtensionManagementUtility::addToAllTCAtypes( 'tx_myext_records', '--div--;LLL:EXT:mindfula11y/Resources/Private/Language/Database.xlf:ttContent.tabs.accessibility, --palette--;LLL:EXT:mindfula11y/Resources/Private/Language/Database.xlf:ttContent.palettes.landmarks;landmarks' );
2. Update Database Schema
Add the database columns to your ext_tables.sql:
CREATE TABLE tx_myext_records ( landmark varchar(15) DEFAULT '' NOT NULL, aria_labelledby tinyint(1) DEFAULT 1 NOT NULL, aria_label varchar(255) DEFAULT '' NOT NULL );
Note: In TYPO3 13+, database schema updates are handled automatically based on TCA configuration, so manual
ext_tables.sqldefinitions are no longer required for TCA-defined fields.
3. Use in Fluid Templates
In your Fluid templates, use the ViewHelper with your custom table:
<html xmlns:f="http://typo3.org/ns/TYPO3/CMS/Fluid/ViewHelpers" xmlns:mindfula11y="http://typo3.org/ns/MindfulMarkup/MindfulA11y/ViewHelpers" data-namespace-typo3-fluid="true" > <!-- If aria-labelledby is active and a header is set prefer over aria-label. Change based on your requirements. --> <f:if condition="{record.aria_labelledby} && {record.title}"> <f:then> <f:variable name="ariaAttributes" value="{labelledby: 'record-{record.uid}-title'}" /> </f:then> <f:else if="{record.aria_label"> <f:variable name="ariaAttributes" value="{label: record.aria_label}" /> </f:else> <f:else> <f:variable name="ariaAttributes" value="{}" /> </f:else> </f:if> <mindfula11y:landmark recordUid="{record.uid}" recordTableName="tx_myext_records" recordColumnName="landmark" role="{record.landmark}" aria="{ariaAttributes}"> {record.content} </mindfula11y:landmark> </html>
This integration allows your custom records to benefit from the same landmark structure analysis and inline editing capabilities provided by the Accessibility backend module.
License
This project is licensed under the GNU General Public License v2.0 (GPL-2.0). See the LICENSE file for details.


