Software Guidebook: remark-kroki-a11y
This Software Guidebook follows the structure defined by Simon Brown in his book Software Architecture for Developers. It provides a comprehensive overview of the remark-kroki-a11y plugin architecture.
1. Context
The remark-kroki-a11y plugin operates in the intersection of:
- Static Site Generators (Docusaurus, mdBook, etc.) that use the unified/remark ecosystem
- Diagram-as-Code tools (Kroki, PlantUML, Mermaid) that render diagrams from text
- Web Accessibility requirements (WCAG, European Accessibility Act) that mandate alternatives for visual content
For detailed context including stakeholders and external systems, see the README page.
2. Functional Overview
The plugin provides the following core functionality:
Use Cases
Configuration Options
| Option | Use Case | Default |
|---|---|---|
showSource | Display source code tab for all diagrams | true |
hideSource | Per-diagram flag to hide the source tab while keeping A11y output | false |
hideA11y | Per-diagram flag to hide the natural language description | false |
locale | Set language for generated descriptions ('en' or 'nl') | 'en' |
a11yDescriptionOverride | Per-diagram natural language description override | - |
Extended use cases: configuration options
3. Quality Attributes
The key quality attributes for this plugin are:
Accessibility (Primary)
- All diagrams must have alternative text descriptions
- Generated descriptions must be screen reader friendly
- UI components must be keyboard navigable
Determinism
- Descriptions are generated by parsers, not AI (see ADR-0000)
- Same input always produces same output
- No external API calls for description generation
Extensibility
- New diagram types can be added via new parsers
- Support for multiple diagram-as-code formats (PlantUML, Mermaid)
Performance
- No runtime API calls (build-time only)
- Minimal impact on build performance
4. Constraints
Technical Constraints
- Must work within the unified/remark ecosystem
- Must be compatible with Docusaurus (primary target)
- Browser support limited by native
<details>element support
Organizational Constraints
- Plugin used in educational context
- Student contributions welcome for new diagram parsers
External Constraints
- Depends on Kroki service for diagram rendering
- PlantUML/Mermaid syntax defines what can be parsed
5. Principles
Core Architectural Principles
-
Deterministic over AI-generated - Descriptions are generated by parsers that understand diagram syntax, not by LLMs (see ADR-0000)
-
PlantUML as Internal Representation - PlantUML data structures serve as the canonical format, with adapters for other formats (see ADR-0006)
-
Build-time processing - All work happens during static site generation, not at runtime
-
Progressive enhancement - Diagrams work without the plugin; plugin adds accessibility layer
Development Principles
- BDD Testing - Features defined in Gherkin, tested with Cucumber
- Single Source Documentation - README/CONTRIBUTING maintained in one place (see ADR-0007)
- Eating our own dog food - This documentation site uses the plugin to render its diagrams
6. Software Architecture
High-Level Architecture
The plugin operates as a remark plugin in the unified.js processing pipeline:
Markdown → remark-kroki-a11y → remark-kroki-plugin → rehype → HTML
↓
Parse diagram source
Generate description
Add tabs/details HTML
Processing Flow
7. Code
Source Code Structure
src/
├── index.js # Main plugin entry point and parser registry (946 lines)
├── diagramTabs.js # Client-side tab switching
├── diagram-a11y.css # Styling for the tabs and accessibility UI
├── rehype-kroki-a11y-img.js # Rehype plugin linking Kroki <img> to A11y descriptions
├── mermaid-classdiagram-a11y.js # Native Mermaid class diagram A11y injection (479 lines)
├── TextToSpeechService.js # Browser Web Speech API wrapper for the "speak" button
├── parsers/ # One parser per diagram type, CommonJS (build-time, used by index.js)
│ ├── classDiagramParser.js # PlantUML and Mermaid class diagrams (1114 lines)
│ ├── stateDiagramParser.js # PlantUML state diagrams
│ ├── sequenceDiagramParser.js # PlantUML and Mermaid sequence diagrams
│ ├── activityDiagramParser.js # PlantUML activity diagrams (536 lines)
│ ├── c4DiagramParser.js # PlantUML C4 context/container/component diagrams
│ ├── pieDiagramParser.js # Mermaid pie charts
│ ├── domainStoryParser.js # PlantUML Domain Story diagrams
│ ├── classDiagramSimplifier.js # Simplifies class diagrams for the "dev mode" toggle
│ ├── unsupportedDiagramParser.js # Fallback descriptions for unsupported diagrams
│ └── *.cjs # Byte-identical duplicates of the parsers above,
│ # required by runtime/a11yRuntime.cjs (see below)
├── runtime/
│ ├── a11yRuntime.js # ESM entry used by unit tests (vitest)
│ └── a11yRuntime.cjs # CommonJS twin, exposed through index.js for tests
├── mermaid-a11y/ # Separate, self-contained sub-package (own package.json/README/ADRs)
│ ├── index.js # for native Mermaid class diagram A11y (no Kroki)
│ └── parsers/classDiagramParser.js
└── stories/ # Storybook stories for manual/visual testing
├── DiagramTabs.stories.js
└── Quizdown.stories.js
Every file in parsers/ exists twice: once as .js (CommonJS, loaded by
index.js during the remark build step) and once as a byte-identical .cjs
twin (loaded by runtime/a11yRuntime.cjs, which index.js also exposes for
tests). The two copies are kept in sync by hand — there is no
build step that generates one from the other. This is a direct violation of the
"Try to be DRY" LIFT principle below and a good target for cleanup (see
issue tracker for
an open issue about this).
The LIFT principle
Source code organization in this project is intended to follow LIFT, from John Papa's Angular Style Guide (see also Papa's article Do you LIFT? Sensible Style Guidelines):
- Locate code at a glance
- Identify code at a glance (name files after what they contain; one responsibility/component per file)
- Flat structure as long as possible (avoid deep nesting)
- Try to be DRY (Don't Repeat Yourself)
The current codebase only partially meets this bar:
| LIFT principle | Current state |
|---|---|
| Locate at a glance | Mostly OK — parsers/ and mermaid-a11y/ group related files |
| Identify by name | Mostly OK, but src/index.js (946 lines) and classDiagramParser.js (1114 lines) do far more than their name suggests |
| Flat structure | OK — nesting is shallow (src/parsers/, src/runtime/, src/mermaid-a11y/parsers/) |
| Try to be DRY | Violated — every parser is duplicated as .js/.cjs (see above), and mermaid-a11y/parsers/classDiagramParser.js overlaps with parsers/classDiagramParser.js |
Parser Architecture
Each parser:
- Receives raw diagram source code
- Parses it into a structured representation
- Generates natural language description in the requested locale
For implementation details, see src/index.js and src/parsers/. The parser registry in src/index.js is the authoritative list for currently wired diagram support.
8. Containers
As a remark plugin, remark-kroki-a11y is an npm package that runs within the static site generator's build process. In a local development setup, the Docusaurus dev server runs on the host machine and communicates with a local Kroki service running in Docker containers.
The plugin interacts with:
- remark-kroki-plugin (runtime dependency) - Handles Kroki API calls for diagram rendering
- rehype-raw (required) - Enables raw HTML in MDX output
- Kroki service - Can be local (Docker) or remote (kroki.io)
9. Components
10. Deployment
npm Package
The plugin is published to npm:
npm install remark-kroki-a11y
GitHub Pages
The documentation site is deployed automatically via GitHub Actions on push to main:
- Run tests
- Build Docusaurus site
- Deploy to GitHub Pages
See .github/workflows/ for pipeline configuration.
Local Development
# From repository root
./start-docs.sh
# Or manually
cd test-docusaurus-site
npm install
npm run start:strict # Recommended: validates broken links
11. Views and Perspectives
Diagram Type Support Matrix
| Diagram Type | PlantUML | Mermaid | A11y Status |
|---|---|---|---|
| Class diagrams | ✅ Full | ⚠️ Beta | Partial |
| State diagrams | ✅ Full | ❌ | Partial |
| Sequence diagrams | ⚠️ Beta | ⚠️ Beta | Partial |
| Activity diagrams | ⚠️ Beta | ❌ | Partial |
| C4 diagrams | ⚠️ Beta | N/A | Partial |
| Domain Story diagrams | ⚠️ Beta | N/A | Partial |
| Pie charts | N/A | ⚠️ Beta | Partial |
Accessibility Compliance
The plugin contributes to WCAG 2.1 compliance:
- 1.1.1 Non-text Content (A) - Provides text alternatives for diagrams
- 2.1.1 Keyboard (A) - Tab/details UI is keyboard accessible
- 4.1.2 Name, Role, Value (A) - Proper ARIA attributes on diagram images
12. Decisions
Architecture decisions are recorded as ADRs (Architecture Decision Records). This guidebook especially relies on the following decisions:
- ADR-0000 explains why descriptions are generated deterministically instead of with live LLM calls.
- ADR-0006 explains why PlantUML structures are used as the internal architectural representation.
- ADR-0007 explains the documentation sync approach between repository sources and the published Docusaurus site.
- ADR-0011 explains why authored source details must remain faithfully represented, including when authors use
a11yDescriptionOverride. - ADR-0012 explains the integration boundary with the upstream Kroki remark plugin.
- ADR-0013 explains the locale strategy for generated accessibility descriptions.
For the complete ADR catalog, see the source ADR index on GitHub and the published ADR index.
This Software Guidebook follows the structure from Simon Brown's book "Software Architecture for Developers".