obsidian-plugin-accessibility
Obsidian Plugin Accessibility
Use this skill when a plugin adds interactive UI and you need to verify keyboard, focus, ARIA, tooltip, and screen-reader behavior before shipping.
Related skills
obsidian-plugin-devobsidian-plugin-boilerplateobsidian-devobsidian-ops
Accessibility is MANDATORY for Obsidian plugins. All users, regardless of ability, must be able to use your plugin.
Table of Contents
- Keyboard Navigation
- ARIA Labels and Roles
- Tooltips and Accessibility
- Focus Management
- Focus Visible Styles
- Screen Reader Support
- Mobile and Touch Accessibility
- Accessibility Checklist
Keyboard Navigation
Keyboard Navigation
Rule: Accessibility best practice - MANDATORY
All interactive elements must be keyboard accessible. Users should be able to navigate and interact with your plugin using only the keyboard.
✅ CORRECT:
// Make custom interactive elements keyboard accessible
element.setAttribute('tabindex', '0');
element.setAttribute('role', 'button');
element.setAttribute('aria-label', 'Click to change active profile');
element.addEventListener('keydown', (e) => {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
performAction();
}
});
Key Requirements:
- Add
tabindex="0"to make elements keyboard focusable - Support both Enter and Space keys for button-like elements
- Use arrow keys for navigation within lists/menus
- Call
e.preventDefault()to prevent default scroll behavior on Space
ARIA Labels and Roles
ARIA Labels and Roles
Rule: Accessibility best practice - MANDATORY
Provide proper ARIA labels and roles for all interactive elements, especially icons and buttons without visible text.
❌ INCORRECT:
// Icon button with no accessible label
const button = containerEl.createEl('button');
button.innerHTML = '⚙️';
✅ CORRECT:
// Icon button with proper ARIA label
const button = containerEl.createEl('button', {
attr: {
'aria-label': 'Open settings',
'type': 'button'
}
});
button.setText('⚙️');
Common ARIA attributes:
aria-label: Accessible name for elements without visible textaria-description: Additional context beyond the labelrole: Semantic role (button, dialog, menu, listbox, etc.)aria-expanded: For collapsible/expandable elementsaria-selected: For selectable items in listsaria-disabled: For disabled but visible elements
Tooltips and Accessibility
Tooltips and Accessibility
Rule: Obsidian API best practice
Use Obsidian's built-in tooltip system with proper positioning and accessibility attributes.
✅ CORRECT:
const button = containerEl.createEl('button', {
attr: {
'aria-label': 'Open settings',
'data-tooltip-position': 'top' // Position tooltip above element
}
});
setTooltip(button, 'Open settings');
// Alternative using setAttr
button.setAttr('aria-label', 'Refresh view');
button.setAttr('data-tooltip-position', 'bottom');
Tooltip Position Options:
top: Above the element (default for many cases)bottom: Below the elementleft: To the left of the elementright: To the right of the element
Important: Always provide aria-label in addition to tooltips, as tooltips may not be accessible to screen readers.
Focus Management
Focus Management
Rule: Accessibility best practice - MANDATORY
Manage focus appropriately when opening modals, menus, or changing context.
✅ CORRECT:
export class MyModal extends Modal {
onOpen() {
const { contentEl } = this;
// Create focusable content
const input = contentEl.createEl('input', {
attr: { 'aria-label': 'Enter value' }
});
// Focus the first interactive element
input.focus();
}
onClose() {
const { contentEl } = this;
contentEl.empty();
// Focus returns to trigger element automatically
}
}
Focus Best Practices:
- Focus the first interactive element when opening modals
- Trap focus within modals (prevent Tab from leaving modal)
- Return focus to the trigger element when closing modals
- Define focus styles in CSS using
:focus-visible
Focus Visible Styles
Focus Visible Styles
Rule: Accessibility best practice - MANDATORY
Define clear focus indicators using CSS :focus-visible pseudo-class.
✅ CORRECT:
/* Option 1: Using outline (standard approach) */
.my-plugin-button:focus-visible {
outline: 2px solid var(--interactive-accent);
outline-offset: 2px;
}
/* Option 2: Using Obsidian's focus box-shadow pattern */
.my-plugin-input:focus,
.my-plugin-input:focus-visible {
box-shadow: 0 0 0 3px var(--background-modifier-border-focus);
border-radius: var(--input-radius);
}
/* Option 3: Alternative focus indicator */
.my-plugin-list-item:focus-visible {
background: var(--background-modifier-hover);
box-shadow: inset 0 0 0 2px var(--interactive-accent);
}
Focus Indicator Guidelines:
- Always provide visible focus indicators
- Use
:focus-visible(not:focus) to show only for keyboard navigation - Use Obsidian's CSS variables:
--background-modifier-border-focusfor focus borders/shadows--interactive-accentfor accent color--input-radiusfor input border radius
- The
box-shadowpattern is preferred for inputs and form elements - Don't remove outlines without providing alternative indicators
Screen Reader Support
Screen Reader Support
Rule: Accessibility best practice - MANDATORY
Ensure your plugin works well with screen readers.
✅ CORRECT:
// Announce dynamic changes to screen readers
const statusEl = containerEl.createEl('div', {
attr: {
'role': 'status',
'aria-live': 'polite',
'aria-atomic': 'true'
}
});
statusEl.setText('File saved successfully');
// Use semantic HTML when possible
const list = containerEl.createEl('ul', {
attr: { 'role': 'list' }
});
const item = list.createEl('li', {
attr: { 'role': 'listitem' }
});
Screen Reader Considerations:
- Use
aria-liveregions for dynamic content updates - Provide meaningful
aria-labelfor icons and images - Use semantic HTML roles when available
- Test with actual screen readers (NVDA, JAWS, VoiceOver)
Mobile and Touch Accessibility
Mobile and Touch Accessibility
Rule: Platform compatibility - MANDATORY
Ensure touch targets are appropriately sized and spaced for mobile users.
✅ CORRECT:
.my-plugin-button {
/* Minimum touch target size: 44x44px */
min-width: 44px;
min-height: 44px;
/* Adequate spacing between touch targets */
margin: var(--size-4-2);
/* Padding for visual size vs. touch target */
padding: var(--size-4-2) var(--size-4-4);
}
Touch Target Guidelines:
- Minimum touch target size: 44×44 pixels (iOS), 48×48 pixels (Android)
- Provide adequate spacing between interactive elements
- Support both click and touch events
- Test on actual mobile devices (iOS)
Accessibility Checklist
IMPORTANT: Accessibility is NOT optional. When implementing UI elements, ensure:
- All interactive elements are keyboard accessible (Enter/Space support)
- Tab order is logical and intuitive
- Focus indicators are visible and clear (
:focus-visible) - ARIA labels provided for icon-only buttons
- ARIA roles defined for custom controls
- Focus managed properly in modals/dialogs
- Dynamic content changes announced to screen readers (
aria-live) - Touch targets meet minimum size requirements (44×44px minimum)
- Color contrast meets WCAG AA standards (use Obsidian CSS variables)
- No information conveyed by color alone
- Tooltips positioned appropriately (
data-tooltip-position) - Tested with keyboard navigation (Tab, Enter, Space, Arrow keys)
- Tested with screen reader (if possible)
More from zpankz/obsidian-skills
viva-llm
Use VIVA LLM for multi-provider chat, voice calls, terminal integration, assistants, skills, MCP tools, and agent mode inside Obsidian. Trigger when the user mentions VIVA LLM, voice chat, realtime voice, LLM providers in Obsidian, or vault-integrated AI chat.
1tasks
Create and query tasks using the Tasks plugin syntax including due dates, recurrence, priorities, and task queries. Use when the user mentions Tasks plugin, recurring tasks, task queries, or advanced task management in Obsidian.
1dataview
Create Dataview queries using DQL (Dataview Query Language), inline queries, and DataviewJS. Use when the user mentions Dataview, DQL, querying notes, listing notes by metadata, or creating dynamic views of vault content.
1defuddle
Extract clean markdown from web pages using Defuddle CLI, removing clutter to save tokens. Use when the user provides a URL to read or analyze.
1datacore
Create Datacore views using JSX/React syntax and the dc.* API. Use when the user mentions Datacore, dc.useQuery, JSX views, or React-based vault queries. Datacore is the successor to Dataview with better performance and interactive views.
1obc
Run OBC vault commands for AI-augmented PKM: daily planning, vault analysis, idea connections, and structured thinking workflows. Trigger on /today, /connect, /map, or any vault interaction command.
1