.agents/skills/accessibility/references/shared_principles.md
These principles apply to every Kibana accessibility decision — when writing new code, refactoring, or fixing a lint error. The component guides under components/ are component-specific extensions of this document.
Precedence on conflict:
components/*.md) or ESLint table (eslint.md)Whether you are writing a new component or fixing existing code, work top-down and stop at the first level that resolves the need:
label, htmlFor, aria-label, aria-labelledby, aria-describedby, roles).id + aria-labelledby) instead of duplicating strings into hidden labels.useEffect for focus or announcements is a fallback — only when no declarative alternative exists.aria-labelledby + a stable id rather than duplicating into aria-label.aria-label and aria-labelledby.title, alt, aria-label, or aria-labelledby unless replacing with a stronger alternative.alt; decorative images use alt="" or aria-hidden="true".Visible and assistive-tech strings (aria-label, tableCaption, tooltip content, label, title, error messages, body copy) must be localized — never raw literals. Programmatic tokens (name on radios, internal ids) stay as plain strings.
For i18n APIs, message id conventions, and validation, follow the kibana-i18n skill. Component guides and examples in this skill assume that pattern.
When a file already exposes a shared object (e.g. i18nTexts.modalTitle), follow that local pattern for new strings instead of adding inline i18n.translate calls.
Use EUI's id generators for any id / aria-labelledby / titleProps.id wiring. Call once and store in a descriptive variable (e.g. modalTitleId, fieldLabelId); reuse an existing id variable when it already targets the same element.
Function components — useGeneratedHtmlId from @elastic/eui, called before the first return:
import { useGeneratedHtmlId } from '@elastic/eui';
const labelId = useGeneratedHtmlId();
Class components — htmlIdGenerator from @elastic/eui, called inside render() with a stable suffix:
import { htmlIdGenerator } from '@elastic/eui';
render() {
const labelId = htmlIdGenerator()('myLabel');
}
<button>, <a>, <input>) over div + onClick + tabIndex.string → any) or suppress errors (@ts-ignore, as any).Stop and flag for human review when:
{...props} on the component and you cannot trace whether aria-labelledby, aria-label, name, etc. are already supplied.aria-label would duplicate a title, but removing title breaks another consumer).