Quill: Toolbar items are not accessible

Created on 9 Dec 2016  路  2Comments  路  Source: quilljs/quill

The items in the toolbar are not accessible for screen readers (eg use CMND + F5 to toggle voice over on macs).

Tabing through the items it just reads 'buton', 'button', 'button' etc and gives the user no context on what the button's do.

These buttons probably should havearia-label attributes for example:

<button class="qr-underline" aria-label="Underline selection"><svg /></button>.

Additionally, the drop downs are being completely ignored.

Steps for Reproduction

  1. Visit https://quilljs.com/playground/ on a mac
  2. Turn on voice over with CMND + F5
  3. Click the toolbar to add focus
  4. Press tab to shift focus on the toolbar elements

Expected behavior:

  • The buttons should indicate to screen readers what they do
  • The select drop downs should be operational with the keyboard.

Actual behavior:

  • The screen reader voices only 'button' for ALL of the buttons
  • The select drop downs cannot be operated (Eg sizes)

Platforms:
Chrome 54.0.2840.98 (64-bit)
Mac OSX El Capitan 10.11.6

Version:
1.1.6

enhancement

Most helpful comment

For anyone else with this issue.. here was our hack work around for now.

/**
 * Applies accessibility to a quill editor
 * TODO: Deprecate this method once this issue is resolved (https://github.com/quilljs/quill/issues/1173)
 * @param {object}      editor      - A Quill editor instance
 */
applyAccessibilityHacks(editor) {

    // Get ref to the toolbar, its not available through the quill api ughh
    const query = editor.container.parentElement.getElementsByClassName('ql-toolbar');
    if (query.length !== 1) {
        // No toolbars found OR multiple which is not what we expect either
        return;
    }

    const toolBar = query[0];

    // apply aria labels to base buttons
    const buttons = toolBar.getElementsByTagName('button');
    for (let i = 0; i < buttons.length; i++) {
        const button = buttons[i];
        const className = button.getAttribute('class').toLowerCase();

        if (className.indexOf('bold') >= 0) {
            button.setAttribute('aria-label', 'Toggle bold text');
        } else if (className.indexOf('italic') >= 0) {
            button.setAttribute('aria-label', 'Toggle italic text');
        } else if (className.indexOf('underline') >= 0) {
            button.setAttribute('aria-label', 'Toggle underline text');
        } else if (className.indexOf('blockquote') >= 0) {
            button.setAttribute('aria-label', 'Toggle blockquote text');
        } else if (className.indexOf('list') >= 0 && button.value === 'ordered') {
            button.setAttribute('aria-label', 'Toggle ordered list');
        } else if (className.indexOf('list') >= 0 && button.value === 'bullet') {
            button.setAttribute('aria-label', 'Toggle bulleted list');
        }
    }

    // Make pickers work with keyboard and apply aria labels
    //FIXME: When you open a submenu with the keyboard and close it with the mouse by click somewhere else, the menu aria-hidden value is incorrectly left to `false`
    const pickers = toolBar.getElementsByClassName('ql-picker');
    for (let i = 0; i < pickers.length; i++) {
        const picker = pickers[i];

        const label = picker.getElementsByClassName('ql-picker-label')[0];
        const optionsContainer = picker.getElementsByClassName('ql-picker-options')[0];
        const options = optionsContainer.getElementsByClassName('ql-picker-item');

        label.setAttribute('role', 'button');
        label.setAttribute('aria-haspopup', 'true');
        label.setAttribute('tabindex', '0');

        if (i === 0) {
            // HACK ALERT
            // This is our size select box.. Works for us as we only have the one drop box
            label.setAttribute('aria-label', 'Font Size');
        }

        optionsContainer.setAttribute('aria-hidden', 'true');
        optionsContainer.setAttribute('aria-label', 'submenu');

        for (let x = 0; x < options.length; x++) {
            const item = options[x];
            item.setAttribute('tabindex', '0');
            item.setAttribute('role', 'button');

            // Read the css 'content' values and generate aria labels
            const size = window.getComputedStyle(item, ':before').content.replace('\"', '');
            item.setAttribute('aria-label', size);
            item.addEventListener('keyup', (e) => {
                if (e.keyCode === 13) {
                    item.click();
                    optionsContainer.setAttribute('aria-hidden', 'true');
                }
            });
        }

        label.addEventListener('keyup', (e) => {
            if (e.keyCode === 13) {
                label.click();
                optionsContainer.setAttribute('aria-hidden', 'false');
            }
        });

    }

}

All 2 comments

For anyone else with this issue.. here was our hack work around for now.

/**
 * Applies accessibility to a quill editor
 * TODO: Deprecate this method once this issue is resolved (https://github.com/quilljs/quill/issues/1173)
 * @param {object}      editor      - A Quill editor instance
 */
applyAccessibilityHacks(editor) {

    // Get ref to the toolbar, its not available through the quill api ughh
    const query = editor.container.parentElement.getElementsByClassName('ql-toolbar');
    if (query.length !== 1) {
        // No toolbars found OR multiple which is not what we expect either
        return;
    }

    const toolBar = query[0];

    // apply aria labels to base buttons
    const buttons = toolBar.getElementsByTagName('button');
    for (let i = 0; i < buttons.length; i++) {
        const button = buttons[i];
        const className = button.getAttribute('class').toLowerCase();

        if (className.indexOf('bold') >= 0) {
            button.setAttribute('aria-label', 'Toggle bold text');
        } else if (className.indexOf('italic') >= 0) {
            button.setAttribute('aria-label', 'Toggle italic text');
        } else if (className.indexOf('underline') >= 0) {
            button.setAttribute('aria-label', 'Toggle underline text');
        } else if (className.indexOf('blockquote') >= 0) {
            button.setAttribute('aria-label', 'Toggle blockquote text');
        } else if (className.indexOf('list') >= 0 && button.value === 'ordered') {
            button.setAttribute('aria-label', 'Toggle ordered list');
        } else if (className.indexOf('list') >= 0 && button.value === 'bullet') {
            button.setAttribute('aria-label', 'Toggle bulleted list');
        }
    }

    // Make pickers work with keyboard and apply aria labels
    //FIXME: When you open a submenu with the keyboard and close it with the mouse by click somewhere else, the menu aria-hidden value is incorrectly left to `false`
    const pickers = toolBar.getElementsByClassName('ql-picker');
    for (let i = 0; i < pickers.length; i++) {
        const picker = pickers[i];

        const label = picker.getElementsByClassName('ql-picker-label')[0];
        const optionsContainer = picker.getElementsByClassName('ql-picker-options')[0];
        const options = optionsContainer.getElementsByClassName('ql-picker-item');

        label.setAttribute('role', 'button');
        label.setAttribute('aria-haspopup', 'true');
        label.setAttribute('tabindex', '0');

        if (i === 0) {
            // HACK ALERT
            // This is our size select box.. Works for us as we only have the one drop box
            label.setAttribute('aria-label', 'Font Size');
        }

        optionsContainer.setAttribute('aria-hidden', 'true');
        optionsContainer.setAttribute('aria-label', 'submenu');

        for (let x = 0; x < options.length; x++) {
            const item = options[x];
            item.setAttribute('tabindex', '0');
            item.setAttribute('role', 'button');

            // Read the css 'content' values and generate aria labels
            const size = window.getComputedStyle(item, ':before').content.replace('\"', '');
            item.setAttribute('aria-label', size);
            item.addEventListener('keyup', (e) => {
                if (e.keyCode === 13) {
                    item.click();
                    optionsContainer.setAttribute('aria-hidden', 'true');
                }
            });
        }

        label.addEventListener('keyup', (e) => {
            if (e.keyCode === 13) {
                label.click();
                optionsContainer.setAttribute('aria-hidden', 'false');
            }
        });

    }

}

Sweet, came here to say just this, and find code ready to go! Any chance this will be incorporated into the official build? Inaccessibility is a blocker for using libraries in my project, so I'd discarded Quill for this specific issue.

And, as an aside, was this the only access issue you hit? I haven't done a deeper evaluation, this was just what I encountered in 30 seconds of use.

Thanks again.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

CHR15- picture CHR15-  路  3Comments

emanuelbsilva picture emanuelbsilva  路  3Comments

Kivylius picture Kivylius  路  3Comments

ouhman picture ouhman  路  3Comments

splacentino picture splacentino  路  3Comments