Install
$ agentstack add skill-saadrahman01-claude-moodle-dev-moodle-accessibility ✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ✓ Network access No
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
Verified badge
Passed review? Show it. Paste this badge into your README, it links to the public security report.
Reliability & compatibility
Declared compatibility
Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.
We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.
How agent discovery & health will work →About
Moodle Accessibility (WCAG 2.1 AA)
Overview
Moodle targets WCAG 2.1 AA. UI in plugins must follow same standard for inclusion. Boost theme + Bootstrap 5 + Moodle's component library do most of the heavy lifting — your job is to use them correctly.
When to Use
- Building any UI (Mustache template, modal, custom form element)
- Reviewing PR for accessibility regression
- Submission to moodle.org plugin directory (a11y is reviewed)
- Responding to a user-reported a11y bug
Top rules
- Use semantic HTML: `
for actions,for navigation,–` in order - Every interactive element keyboard-reachable with visible
:focus - Every image has
alt; decorative images:alt="" - Color contrast ≥ 4.5:1 (text), 3:1 (UI components)
- Forms: `` linked to every input
- Don't use color alone to convey meaning
- Modals trap focus, restore on close
Semantic Mustache
{{! BAD }}
Save
{{! GOOD }}
{{#str}}save, core{{/str}}
Headings:
{{#str}}announcements, local_example{{/str}}
...
Skip levels = WCAG fail. ` then ` is wrong.
Forms — formslib
MoodleQuickForm outputs `` automatically. Don't bypass:
$mform->addElement('text', 'name', get_string('name', 'local_example'));
$mform->setType('name', PARAM_TEXT);
$mform->addRule('name', null, 'required', null, 'client');
// Help button — adds aria-described relationship
$mform->addHelpButton('name', 'name', 'local_example');
For required fields, addRule('required') adds aria-required="true".
Buttons / links
| Element | Use for | |---------|---------| | ` | Form submit | | | JS action | | ` | Navigation to a URL |
Never ` — use . Never ` unless you also handle keyboard (Enter + Space) and focus.
Icons
{{#pix}}t/edit, core, {{#str}}edit, core{{/str}}{{/pix}}
Pix renderer outputs `` with the title as alt. For decorative icons accompanying visible text:
{{#pix}}t/edit, core{{/pix}}
{{#str}}edit, core{{/str}}
Bootstrap 5: .visually-hidden (was .sr-only in BS4).
Color contrast
Boost defines $primary, $secondary, etc. Don't override to low-contrast values.
// theme/yourtheme/scss/post.scss
$primary: #0f6fc5; // contrast vs white = 5.13:1 ✓
$danger: #d9534f; // contrast vs white = 3.34:1 ✗ — fails AA for text
Test: https://webaim.org/resources/contrastchecker/
Don't rely on color only:
{{! BAD — color only }}
{{name}}
{{! GOOD — icon + color }}
{{#pix}}t/error, core, {{#str}}invalid, local_example{{/str}}{{/pix}}
{{name}}
Tables
{{#str}}attendance, local_example{{/str}}
{{#str}}user, core{{/str}}
{{#str}}status, core{{/str}}
{{#rows}}
{{name}}
{{status}}
{{/rows}}
html_table from lib/outputcomponents.php renders accessibly:
$table = new \html_table();
$table->head = [get_string('name'), get_string('status')];
$table->headspan = [1, 1];
$table->caption = get_string('attendance', 'local_example');
echo \html_writer::table($table);
ARIA — minimal use
Native HTML > ARIA. Only add ARIA when no native equivalent.
{{! tab pattern — needs ARIA }}
One
Two
...
Bootstrap 5 tab JS handles arrow keys. Don't reimplement.
Modals
Use core/modal — handles focus trap, ESC key, focus restore on close.
import Modal from 'core/modal';
const modal = await Modal.create({
title: await getString('confirm', 'core'),
body: await Templates.render('local_example/confirm', {}),
show: true,
removeOnClose: true,
});
// focus auto-traps inside; on close, focus returns to trigger element
Custom focus management:
import {trapFocus} from 'core/local/aria/focusmanager';
const release = trapFocus(modalEl);
// ... when closing:
release();
triggerEl.focus();
Live regions (toasts, async updates)
import {add as addToast} from 'core/toast';
addToast('Saved', {type: 'success'});
core/toast uses aria-live="polite". For urgent alerts: aria-live="assertive" (sparingly — interrupts screen readers).
Keyboard
Every interactive control:
- Focusable in source order (don't
tabindex="0"everything) - Activated with Enter / Space
- Visible focus ring (don't
outline: 0without replacement) - Custom widgets follow WAI-ARIA Authoring Practices
Screen reader testing
| OS | SR | Browser | |----|----|---------| | Windows | NVDA (free) | Firefox | | macOS | VoiceOver | Safari | | Linux | Orca | Firefox | | iOS | VoiceOver | Safari | | Android | TalkBack | Chrome |
Quick checks:
- Tab through entire UI — every interactive element reachable
- Use SR-only — does the experience make sense?
- Resize text 200% — layout still works?
- Disable CSS — content order still meaningful?
Automation
# axe-core via Puppeteer
npm install -g @axe-core/cli
axe http://localhost:8000/local/example/
# Pa11y
npm install -g pa11y
pa11y --standard WCAG2AA http://localhost:8000/local/example/
CI integration: run on PRs against staging.
Common Mistakes
| Mistake | Fix | |---------|-----| | ` | | | Missing alt on | Add — alt="" if decorative | | placeholder as label | Add real | | outline: 0 no replacement | Restore visible focus indicator | | Heading level skip | Sequential // | | Link text "click here" / "more" | Descriptive (Edit user "Alice") | | role="button" without keyboard | Use instead | | Color-only error indication | Add icon + text | | Color contrast with no role | Either add role or use text instead | | aria-hidden="true" on focusable element | Either unhide or remove from tab order |
Plugin directory review
moodle.org reviewers run a11y checks. Common rejection reasons:
- New custom modal without focus trap
- Hard-coded colors failing AA contrast
- Custom widgets without ARIA / keyboard
- Missing `` on form fields
References
- Accessibility: https://moodledev.io/general/development/policies/accessibility
- WCAG 2.1: https://www.w3.org/TR/WCAG21/
- WAI-ARIA APG: https://www.w3.org/WAI/ARIA/apg/
- Boost a11y: https://docs.moodle.org/dev/Boost-Accessibility
- pix renderer: https://moodledev.io/docs/apis/subsystems/output#pix-icons
Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: SaadRahman01
- Source: SaadRahman01/claude-moodle-dev
- License: MIT
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet, be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.