Replies: 1 comment
-
Just came across this off of Google. I am working on something that may be relevant. Here's a bit of it: import {ARIAAttributeMap} from './attribute';
import {ARIAAbstractRole, ARIARole} from './role';
import {NegativeInfinity, PositiveInfinity} from 'type-fest';
type ARIARoleDefinition<
ABSTRACT extends boolean,
SUPERCLASS_ROLES extends (ARIARole | ARIAAbstractRole)[],
SUBCLASS_ROLES extends (ARIARole | ARIAAbstractRole)[],
SUPPORTED_ATTRIBUTES extends (keyof ARIAAttributeMap)[],
REQUIRED_ATTRIBUTES extends (keyof ARIAAttributeMap)[],
PROHIBITED_ATTRIBUTES extends (keyof ARIAAttributeMap)[],
REQUIRED_OWNED_ROLES extends ARIARole[][],
REQUIRED_OWNER_ROLES extends ARIARole[],
IMPLICIT_VALUES extends Partial<ARIAAttributeMap> // TODO: Partial<typeof ATTRIBUTES[number]>
> = {
abstract: ABSTRACT;
superRoles: SUPERCLASS_ROLES;
subRoles: SUBCLASS_ROLES;
attributes: SUPPORTED_ATTRIBUTES;
requiredAttributes: REQUIRED_ATTRIBUTES;
prohibitedAttributes: PROHIBITED_ATTRIBUTES;
requiredOwnedRoles: REQUIRED_OWNED_ROLES;
requiredOwnerRoles: REQUIRED_OWNER_ROLES;
implicitValues: IMPLICIT_VALUES;
}
// TODO: Review `separator` role.
// There are two cases (focusable / not).
interface ARIAAbstractRoleMap {
'command': ARIARoleDefinition<
true,
['widget'],
[
'button',
'link',
'menuitem'
],
[],
[],
[],
[],
[],
{}
>;
'composite': ARIARoleDefinition<
true,
['widget'],
[
'grid',
'select',
'spinbutton',
'tablist'
],
[
'aria-activedescendant',
'aria-disabled'
],
[],
[],
[],
[],
{}
>;
'input': ARIARoleDefinition<
true,
['widget'],
[
'checkbox',
'combobox',
'option',
'radio',
'slider',
'spinbutton',
'textbox'
],
['aria-disabled'],
[],
[],
[],
[],
{}
>;
'landmark': ARIARoleDefinition<
true,
['section'],
[
'banner',
'complementary',
'contentinfo',
'form',
'main',
'navigation',
'region',
'search'
],
[],
[],
[],
[],
[],
{}
>;
'range': ARIARoleDefinition<
true,
['structure'],
[
'meter',
'progressbar',
'scrollbar',
'slider',
'spinbutton'
],
[
'aria-valuemax',
'aria-valuemin',
'aria-valuenow',
'aria-valuetext'
],
[],
[],
[],
[],
{}
>;
'roletype': ARIARoleDefinition<
true,
[],
[
'structure',
'widget',
'window'
],
[
'aria-atomic',
'aria-busy',
'aria-controls',
'aria-current',
'aria-describedby',
'aria-details',
'aria-disabled',
'aria-dropeffect',
'aria-errormessage',
'aria-flowto',
'aria-grabbed',
'aria-haspopup',
'aria-hidden',
'aria-invalid',
'aria-keyshortcuts',
'aria-label',
'aria-labelledby',
'aria-live',
'aria-owns',
'aria-relevant',
'aria-roledescription'
],
[],
[],
[],
[],
{}
>;
'section': ARIARoleDefinition<
true,
['structure'],
[
'alert',
'blockquote',
'caption',
'cell',
// TODO: 'code',
'definition',
'deletion',
'emphasis',
'figure',
'group',
'img',
'insertion',
'landmark',
'list',
'listitem',
'log',
'marquee',
'math',
'note',
'paragraph',
'status',
'strong',
'subscript',
'superscript',
'table',
'tabpanel',
'term',
'time',
'tooltip'
],
[],
[],
[],
[],
[],
{}
>;
'sectionhead': ARIARoleDefinition<
true,
['structure'],
[
'columnheader',
'heading',
'rowheader',
'tab'
],
[],
[],
[],
[],
[],
{}
>;
'select': ARIARoleDefinition<
true,
[
'composite',
'group'
],
[
'listbox',
'menu',
'radiogroup',
'tree',
],
['aria-orientation'],
[],
[],
[],
[],
{}
>;
'structure': ARIARoleDefinition<
true,
['roletype'],
[
'application',
'document',
'generic',
'presentation',
'range',
'rowgroup',
'section',
'sectionhead',
'separator'
],
[],
[],
[],
[],
[],
{}
>;
'widget': ARIARoleDefinition<
true,
['roletype'],
[
'command',
'composite',
'gridcell',
'input',
'progressbar',
'row',
'scrollbar',
'separator',
'tab'
],
[],
[],
[],
[],
[],
{}
>;
'window': ARIARoleDefinition<
true,
['roletype'],
['dialog'],
['aria-modal'],
[],
[],
[],
[],
{}
>;
}
interface ARIAWidgetRoleMap {
'button': ARIARoleDefinition<
false,
['command'],
[],
[
'aria-disabled',
'aria-haspopup',
'aria-expanded',
'aria-pressed'
],
[],
[],
[],
[],
{}
>;
'checkbox': ARIARoleDefinition<
false,
['input'],
['switch'],
[
'aria-errormessage',
'aria-expanded',
'aria-invalid',
'aria-readonly',
'aria-required'
],
[
'aria-checked'
],
[],
[],
[],
{}
>;
'gridcell': ARIARoleDefinition<
false,
[
'cell',
'widget'
],
[
'columnheader',
'rowheader'
],
[
'aria-disabled',
'aria-errormessage',
'aria-expanded',
'aria-haspopup',
'aria-invalid',
'aria-readonly',
'aria-required',
'aria-selected'
],
[],
[],
[],
['row'],
{}
>;
'link': ARIARoleDefinition<
false,
['command'],
[],
[
'aria-disabled',
'aria-expanded',
'aria-haspopup'
],
[],
[],
[],
[],
{}
>;
'menuitem': ARIARoleDefinition<
false,
['command'],
['menuitemcheckbox'],
[
'aria-disabled',
'aria-expanded',
'aria-haspopup',
'aria-posinset',
'aria-setsize'
],
[],
[],
[],
[
'group',
'menu',
'menubar'
],
{}
>;
'menuitemcheckbox': ARIARoleDefinition<
false,
['menuitem'],
['menuitemradio'],
[],
[
'aria-checked'
],
[],
[],
[
'group',
'menu',
'menubar'
],
{}
>;
'menuitemradio': ARIARoleDefinition<
false,
['menuitemcheckbox'],
[],
[],
[],
[],
[],
[
'group',
'menu',
'menubar'
],
{}
>;
'option': ARIARoleDefinition<
false,
['input'],
['treeitem'],
[
'aria-checked',
'aria-posinset',
'aria-selected',
],
[
'aria-selected'
],
[],
[],
[
'group',
'listbox'
],
{'aria-selected': false}
>;
'progressbar': ARIARoleDefinition<
false,
[
'range',
'widget'
],
[],
[],
[],
[],
[],
[],
{
'aria-valuemin': 0,
'aria-valuemax': 100,
}
>;
'radio': ARIARoleDefinition<
false,
['input'],
[],
[
'aria-posinset',
'aria-setsize'
],
[
'aria-checked'
],
[],
[],
[],
{}
>;
'scrollbar': ARIARoleDefinition<
false,
[
'range',
'widget'
],
[],
[
'aria-disabled',
'aria-orientation',
'aria-valuemax',
'aria-valuemin'
],
[
'aria-controls',
'aria-valuenow',
],
[],
[],
[],
{
'aria-orientation': 'vertical',
'aria-valuemin': 0,
'aria-valuemax': 100
}
>;
'searchbox': ARIARoleDefinition<
false,
['textbox'],
[],
[],
[],
[],
[],
[],
{}
>;
// 'separator': ARIARoleDefinition<
// false,
// 'widget',
// [],
// [
// 'aria-disabled',
// 'aria-orientation',
// 'aria-valuemax',
// 'aria-valuemin',
// 'aria-valuetext'
// ],
// 'aria-valuenow',
// [],
// [],
// [],
// {
// 'aria-orientation': 'horizontal',
// 'aria-valuemin': 0,
// 'aria-valuemax': 100
// }
// >;
'slider': ARIARoleDefinition<
false,
[
'input',
'range',
],
[],
[
'aria-errormessage',
'aria-haspopup',
'aria-invalid',
'aria-orientation',
'aria-readonly',
'aria-valuemax',
'aria-valuemin'
],
[
'aria-valuenow'
],
[],
[],
[],
{
'aria-orientation': 'horizontal',
'aria-valuemin': 0,
'aria-valuemax': 100
}
>;
'spinbutton': ARIARoleDefinition<
false,
[
'composite',
'input',
'range',
],
[],
[
'aria-errormessage',
'aria-invalid',
'aria-readonly',
'aria-required',
'aria-valuemax',
'aria-valuemin',
'aria-valuenow',
'aria-valuetext'
],
[],
[],
[],
[],
{
'aria-valuemin': NegativeInfinity,
'aria-valuemax': PositiveInfinity,
'aria-valuenow': 0,
}
>;
'switch': ARIARoleDefinition<
false,
['checkbox'],
[],
[],
['aria-checked'],
[],
[],
[],
{}
>;
'tab': ARIARoleDefinition<
false,
[
'sectionhead',
'widget'
],
[],
[
'aria-disabled',
'aria-expanded',
'aria-haspopup',
'aria-posinset',
'aria-selected',
'aria-setsize'
],
[],
[],
[],
['tablist'],
{'aria-selected': false}
>;
'tabpanel': ARIARoleDefinition<
false,
['section'],
[],
[],
[],
[],
[],
[],
{}
>;
'textbox': ARIARoleDefinition<
false,
['input'],
['searchbox'],
[
'aria-activedescendant',
'aria-autocomplete',
'aria-errormessage',
'aria-haspopup',
'aria-invalid',
'aria-multiline',
'aria-placeholder',
'aria-readonly',
'aria-required',
],
[],
[],
[],
[],
{}
>;
'treeitem': ARIARoleDefinition<
false,
[
'listitem',
'option',
],
[],
[
'aria-expanded',
'aria-haspopup'
],
[],
[],
[],
[
'group',
'tree'
],
{}
>;
}
interface ARIACompositeWidgetRoleMap {
'combobox': ARIARoleDefinition<
false,
['input'],
[],
[
'aria-activedescendant',
'aria-autocomplete',
'aria-errormessage',
'aria-haspopup',
'aria-invalid',
'aria-readonly',
'aria-required'
],
[
'aria-controls',
'aria-expanded'
],
[],
[],
[],
{'aria-haspopup': 'listbox'}
>;
'grid': ARIARoleDefinition<
false,
[
'composite',
'table'
],
['treegrid'],
[
'aria-multiselectable',
'aria-readonly'
],
[],
[],
[
['row'],
['rowgroup', 'row']
],
[],
{}
>;
'listbox': ARIARoleDefinition<
false,
['select'],
[],
[
'aria-errormessage',
'aria-expanded',
'aria-invalid',
'aria-multiselectable',
'aria-readonly',
'aria-required'
],
[],
[],
[
['group', 'option'],
['option']
],
[],
{'aria-orientation': 'vertical'}
>;
'menu': ARIARoleDefinition<
false,
['select'],
['menubar'],
[],
[],
[],
[
['group', 'menuitem'],
['group', 'menuitemradio'],
['group', 'menuitemcheckbox'],
['menuitem'],
['menuitemcheckbox'],
['menuitemradio']
],
[],
{'aria-orientation': 'vertical'}
>;
'menubar': ARIARoleDefinition<
false,
['menu'],
[],
[],
[],
[],
[
['group', 'menuitem'],
['group', 'menuitemradio'],
['group', 'menuitemcheckbox'],
['menuitem'],
['menuitemcheckbox'],
['menuitemradio']
],
[],
{'aria-orientation': 'horizontal'}
>;
'radiogroup': ARIARoleDefinition<
false,
['select'],
[],
[
'aria-errormessage',
'aria-invalid',
'aria-readonly',
'aria-required'
],
[],
[],
[['radio']],
[],
{}
>;
'tablist': ARIARoleDefinition<
false,
['composite'],
[],
[
'aria-multiselectable',
'aria-orientation'
],
[],
[],
[['tab']],
[],
{'aria-orientation': 'horizontal'}
>;
'tree': ARIARoleDefinition<
false,
['select'],
['treegrid'],
[
'aria-errormessage',
'aria-invalid',
'aria-multiselectable',
'aria-readonly',
],
[],
[],
[
['group', 'treeitem'],
['treeitem']
],
[],
{'aria-orientation': 'vertical'}
>;
'treegrid': ARIARoleDefinition<
false,
[
'grid',
'tree'
],
[],
[],
[],
[],
[
['row'],
['rowgroup', 'row']
],
[],
{}
>;
}
interface ARIADocumentStructureRoleMap {
'application': ARIARoleDefinition<
false,
['structure'],
[],
[
'aria-activedescendant',
'aria-disabled',
'aria-errormessage',
'aria-expanded',
'aria-haspopup',
'aria-invalid',
],
[],
[],
[],
[],
{}
>;
'article': ARIARoleDefinition<
false,
['document'],
[],
[
'aria-posinset',
'aria-setsize'
],
[],
[],
[],
[],
{}
>;
'blockquote': ARIARoleDefinition<
false,
['section'],
[],
[],
[],
[],
[],
[],
{}
>;
'caption': ARIARoleDefinition<
false,
['section'],
[],
[],
[],
[
'aria-label',
'aria-labelledby'
],
[],
[
'figure',
'grid',
'table',
'treegrid'
],
{}
>;
'cell': ARIARoleDefinition<
false,
['section'],
[
'columnheader',
'gridcell',
'rowheader'
],
[
'aria-colindex',
'aria-colspan',
'aria-rowindex',
'aria-rowspan'
],
[],
[],
[],
['row'],
{}
>;
'columnheader': ARIARoleDefinition<
false,
[
'cell',
'gridcell',
'sectionhead'
],
[],
['aria-sort'],
[],
[],
[],
['row'],
{}
>;
'definition': ARIARoleDefinition<
false,
['section'],
[],
[],
[],
[],
[],
[],
{}
>;
'deletion': ARIARoleDefinition<
false,
['section'],
[],
[],
[],
[
'aria-label',
'aria-labelledby'
],
[],
[],
{}
>;
'directory': ARIARoleDefinition<
false,
['list'],
[],
[],
[],
[],
[],
[],
{}
>;
'document': ARIARoleDefinition<
false,
['structure'],
['article'],
[],
[],
[],
[],
[],
{}
>;
'emphasis': ARIARoleDefinition<
false,
['section'],
[],
[],
[],
[
'aria-label',
'aria-labelledby'
],
[],
[],
{}
>;
'feed': ARIARoleDefinition<
false,
['list'],
[],
[],
[],
[],
[['article']],
[],
{}
>;
'figure': ARIARoleDefinition<
false,
['section'],
[],
[],
[],
[],
[],
[],
{}
>;
'generic': ARIARoleDefinition<
false,
['structure'],
[],
[],
[],
[
'aria-label',
'aria-labelledby',
'aria-roledescription'
],
[],
[],
{}
>;
'group': ARIARoleDefinition<
false,
['section'],
[
'row',
'select',
'toolbar'
],
[
'aria-activedescendant',
'aria-disabled'
],
[],
[],
[],
[],
{}
>;
'heading': ARIARoleDefinition<
false,
['sectionhead'],
[],
[],
['aria-level'],
[],
[],
[],
{}
>;
'img': ARIARoleDefinition<
false,
['section'],
[],
[],
[],
[],
[],
[],
{}
>;
'insertion': ARIARoleDefinition<
false,
['section'],
[],
[],
[],
[
'aria-label',
'aria-labelledby'
],
[],
[],
{}
>;
'list': ARIARoleDefinition<
false,
['section'],
[
'directory',
],
[],
[],
[],
[['listitem']],
[],
{}
>;
'listitem': ARIARoleDefinition<
false,
['section'],
['treeitem'],
[
'aria-level',
'aria-posinset',
'aria-setsize'
],
[],
[],
[],
[
'directory',
'list'
],
{}
>;
'math': ARIARoleDefinition<
false,
['section'],
[],
[],
[],
[],
[],
[],
{}
>;
'meter': ARIARoleDefinition<
false,
['range'],
[],
[],
['aria-valuenow'],
[],
[],
[],
{
'aria-valuemin': 0,
'aria-valuemax': 1
}
>;
'none': ARIARoleDefinition<
false,
['section'],
[],
[],
[],
[],
[],
[],
{}
>;
'note': ARIARoleDefinition<
false,
['section'],
[],
[],
[],
[],
[],
[],
{}
>;
'paragraph': ARIARoleDefinition<
false,
['section'],
[],
[],
[],
[
'aria-label',
'aria-labelledby'
],
[],
[],
{}
>;
'presentation': ARIARoleDefinition<
false,
['structure'],
[],
[],
[],
[
'aria-label',
'aria-labelledby',
],
[],
[],
{}
>;
'row': ARIARoleDefinition<
false,
[
'group',
'widget'
],
[],
[
'aria-colindex',
'aria-expanded',
'aria-level',
'aria-posinset',
'aria-rowindex',
'aria-setsize',
'aria-selected'
],
[],
[],
[
['cell'],
['columnheader'],
['gridcell'],
['rowheader']
],
[
'grid',
'rowgroup',
'table',
'treegrid'
],
{}
>;
'rowgroup': ARIARoleDefinition<
false,
['structure'],
[],
[],
[],
[],
[['row']],
[
'grid',
'table',
'treegrid'
],
{}
>;
'rowheader': ARIARoleDefinition<
false,
[
'cell',
'gridcell',
'sectionhead'
],
[],
[
'aria-expanded',
'aria-sort'
],
[],
[],
[],
['row'],
{}
>;
'separator': ARIARoleDefinition<
false,
['structure'],
[],
['aria-orientation'],
[],
[],
[],
[],
{'aria-orientation': 'horizontal'}
>;
'strong': ARIARoleDefinition<
false,
['section'],
[],
[],
[],
[
'aria-label',
'aria-labelledby'
],
[],
[],
{}
>;
'subscript': ARIARoleDefinition<
false,
['section'],
[],
[],
[],
[
'aria-label',
'aria-labelledby'
],
[],
[],
{}
>;
'superscript': ARIARoleDefinition<
false,
['section'],
[],
[],
[],
[
'aria-label',
'aria-labelledby'
],
[],
[],
{}
>;
'table': ARIARoleDefinition<
false,
['section'],
['grid'],
[
'aria-colcount',
'aria-rowcount'
],
[],
[],
[
['row'],
['rowgroup', 'row']
],
[],
{}
>;
'term': ARIARoleDefinition<
false,
['section'],
[],
[],
[],
[],
[],
[],
{}
>;
'time': ARIARoleDefinition<
false,
['section'],
[],
[],
[],
[],
[],
[],
{}
>;
'toolbar': ARIARoleDefinition<
false,
['group'],
[],
['aria-orientation'],
[],
[],
[],
[],
{'aria-orientation': 'horizontal'}
>;
'tooltip': ARIARoleDefinition<
false,
['section'],
[],
[],
[],
[],
[],
[],
{}
>;
}
interface ARIALandmarkRoleMap {
'banner': ARIARoleDefinition<
false,
['landmark'],
[],
[],
[],
[],
[],
[],
{}
>;
'complementary': ARIARoleDefinition<
false,
['landmark'],
[],
[],
[],
[],
[],
[],
{}
>;
'contentinfo': ARIARoleDefinition<
false,
['landmark'],
[],
[],
[],
[],
[],
[],
{}
>;
'form': ARIARoleDefinition<
false,
['landmark'],
[],
[],
[],
[],
[],
[],
{}
>;
'main': ARIARoleDefinition<
false,
['landmark'],
[],
[],
[],
[],
[],
[],
{}
>;
'navigation': ARIARoleDefinition<
false,
['landmark'],
[],
[],
[],
[],
[],
[],
{}
>;
'region': ARIARoleDefinition<
false,
['landmark'],
[],
[],
[],
[],
[],
[],
{}
>;
'search': ARIARoleDefinition<
false,
['landmark'],
[],
[],
[],
[],
[],
[],
{}
>;
}
interface ARIALiveRegionRoleMap {
'alert': ARIARoleDefinition<
false,
['section'],
['alertdialog'],
[],
[],
[],
[],
[],
{
'aria-live': 'assertive',
'aria-atomic': true,
}
>;
'log': ARIARoleDefinition<
false,
['section'],
[],
[],
[],
[],
[],
[],
{'aria-live': 'polite'}
>;
'marquee': ARIARoleDefinition<
false,
['section'],
[],
[],
[],
[],
[],
[],
{'aria-live': 'off'}
>;
'status': ARIARoleDefinition<
false,
['section'],
['timer'],
[],
[],
[],
[],
[],
{
'aria-live': 'polite',
'aria-atomic': true,
}
>;
'timer': ARIARoleDefinition<
false,
['status'],
[],
[],
[],
[],
[],
[],
{}
>;
}
interface ARIAWindowRoleMap {
'alertdialog': ARIARoleDefinition<
false,
[
'alert',
'dialog'
],
[],
[],
[],
[],
[],
[],
{}
>;
'dialog': ARIARoleDefinition<
false,
['window'],
['alertdialog'],
[],
[],
[],
[],
[],
{}
>;
}
interface ARIARoleMap
extends ARIAAbstractRoleMap,
ARIAWidgetRoleMap,
ARIACompositeWidgetRoleMap,
ARIADocumentStructureRoleMap,
ARIALandmarkRoleMap,
ARIALiveRegionRoleMap,
ARIAWindowRoleMap {
}
// TODO: Other similar types.
// type ARIARoleAttributes<ROLE extends keyof ARIARoleMap> = ARIARoleMap[ROLE]['attributes'];
// TODO: Figure this out.
// The type worked before I added roles with multiple super roles.
// Make sure to export it.
// TODO: This also needs to combine required attributes.
// /**
// * Type that gets all attributes, including those from super roles.
// */
// type ARIARoleAllAttributes<ROLE extends keyof ARIARoleMap> =
// | ARIARoleAttributes<ROLE>[number]
// | ARIARoleAttributes<ARIARoleSuperRoles<ROLE>[number]>[number];
export default ARIARoleDefinition;
export {
ARIARoleDefinition,
ARIAAbstractRoleMap,
ARIAWidgetRoleMap,
ARIACompositeWidgetRoleMap,
ARIADocumentStructureRoleMap,
ARIALandmarkRoleMap,
ARIALiveRegionRoleMap,
ARIAWindowRoleMap,
ARIARoleMap
}; |
Beta Was this translation helpful? Give feedback.
0 replies
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
-
Description
Not a simple task, more of an epic, but potentially worthwhile:
Implementing accessibility is not trivial, though it follows rather clear defined rules (see https://www.w3.org/TR/using-aria/). Developing against storybook helps a lot, but there may be some rules that could be directly baked into chakra components with typescript.
Problem Statement/Justification
There are many inter-dependencies / more or less complex rules that are not obvious when trying to add a little 11y to your app, unless of course you are an expert and know the spec like the back of your hand.
For instance, regarding
aria-label
the spec says:A developer may be thinking: "Well i'm going to add an aria-label to this div that holds important visual information, which i'm going to represent it in the label text, that will surely increase a11y of my application". Now they added an aria-label in good faith, but it won't ever be useful unless a
role
is added to that div.Proposed Solution or API
This surely is possible using typescript, i believe via union interfaces for example (see this excellent article). Another, probably less complex but noisier approach would be using function overloads.
The current
aria-*
props resolve toAriaAttributes
in the@types/react
package. These would need to beAs i am realizing the aria-props are not implemented in Chakra itself, i assume the breaking down of rules and grouping into separate interface is an out-of-scope project – however at the same time i believe somebody must have done this already somewhere. If there is an full-blown engine in storybook that can validate via javascript runtime code, somewhere out there a project must be working on implementing these rules for compile-time usage. That would solve task 1.
Not sure if there isn't also way to automate the annotation process described by task 2.
Alternatives
As mentioned, storybook provides excellent a11y feedback – but only after the code has been typed and rendered.
Also, there must be a multitude of linters / CI-tools that can verify generated markup for a11y rules, but again, that comes too late.
I am yet to discover a solution that can do this at compile / parsing time via typescript or some other form of tokenization mechanism while writing the code in the IDE.
Beta Was this translation helpful? Give feedback.
All reactions