Accessible Drupal 8 modals when Javascript is disabled

In the previous tutorial, you learnt how to create a modal in a custom module. If you missed it, you can check it out here.

There is just one problem with this: if a user doesn't have Javascript enabled, the modal won't work. While the vast majority of people do have JavaScript enabled, it is still important to gracefully work for those that don't for accessibility reasons (for example, visually impaired people might access the site with a screen reader).

In this case, the best option is to display the contents of the modal on a page, rather than the modal, for those that don't have JavaScript enabled.

Fortunately for us, Drupal handles this out of the box. All you need is a few tweaks to the code that we wrote in the previous tutorial.

The Steps

Start with the module from the previous tutorial -
custom_modal. You can clone from github:

You are going to need to change three files in this modal:

  • custom_modal.routing.yml
  • CustomModalController.php
  • ModalBlock.php

1/ custom_modal.routing.yml

In the routing file, add a slug to the path which will change dynamically if Javascript is enabled or disabled:

path: 'modal-example/modal/{js}'

If Javascript is enabled, Drupal will change this to:

modal-example/modal/ajax

Otherwise, it will be:

modal-example/modal/nojs

This (ajax or nojs) will be passed to the controller, which you will see shortly.

You also need to add js: 'nojs|ajax' to the requirements.

The full code for the route is:

custom_modal.modal:
  path: 'modal-example/modal/{js}'
  defaults:
    _title: 'Modal'
    _controller: '\Drupal\custom_modal\Controller\CustomModalController::modal'
  requirements:
    _permission: 'access content'
    js: 'nojs|ajax'

2/ CustomModalController.php

In the modal method, add $js as an argument. This will be passed from the route and will be either ajax or nojs. This will allow you to change what is returned depending on whether or not Javascript is enabled. If nothing is passed at all, we'll use nojs as the default.

public function modal($js = 'nojs') {

And add the following use statement:

 use Symfony\Component\HttpFoundation\Response;

The full code for CustomModalController.php is now:

<?php

/**
 * @file
 * CustomModalController class.
 */

namespace Drupal\custom_modal\Controller;

use Drupal\Core\Ajax\AjaxResponse;
use Drupal\Core\Ajax\OpenModalDialogCommand;
use Drupal\Core\Controller\ControllerBase;
use Symfony\Component\HttpFoundation\Response;

class CustomModalController extends ControllerBase {

  public function modal($js = 'nojs') {
    if ($js == 'ajax') {
      $options = [
        'dialogClass' => 'popup-dialog-class',
        'width' => '50%',
      ];
      $response = new AjaxResponse();
      $response->addCommand(new OpenModalDialogCommand(t('Modal title'), t('The modal text'), $options));
    }
    else {
      $response = new Response();
      $response->setContent('The modal text (Javascript is not enabled)');
    }

    return $response;
  }
}

3/ ModalBlock.php

Change $link_url to:

$link_url = Url::fromRoute('custom_modal.modal', ['js' => 'nojs']);"

The full code for ModalBlock.php is now:

<?php
/**
 * @file
 * Contains \Drupal\custom_modal\Plugin\Block\ModalBlock.
 */

namespace Drupal\custom_modal\Plugin\Block;
use Drupal\Core\Block\BlockBase;
use Drupal\Core\Url;
use Drupal\Core\Link;
use Drupal\Component\Serialization\Json;

/**
 * Provides a 'Modal' Block
 *
 * @Block(
 *   id = "modal_block",
 *   admin_label = @Translation("Modal block"),
 * )
 */
class ModalBlock extends BlockBase {
  /**
   * {@inheritdoc}
   */
  public function build() {
    $link_url = Url::fromRoute('custom_modal.modal', ['js' => 'nojs']);
    $link_url->setOptions([
      'attributes' => [
        'class' => ['use-ajax', 'button', 'button--small'],
        'data-dialog-type' => 'modal',
        'data-dialog-options' => Json::encode(['width' => 400]),
      ]
    ]);

    return array(
      '#type' => 'markup',
      '#markup' => Link::fromTextAndUrl(t('Open modal'), $link_url)->toString(),
      '#attached' => ['library' => ['core/drupal.dialog.ajax']]
    );
  }
}

Testing it without Javascript

You can test this by turning off Javascript in your browser. You can do this in Chrome with the following steps:

  • click on Settings
  • Advanced (right at the bottom of the settings page)
    • Content settings
    • Javascript
    • Toggle to blocked
    • Refresh the page and hit the "Open Modal" button in the block

If you click on the link for the modal, it should now display the non-JS version on the custom path.

Wrapping up

To see the complete code, have a look at the branch on GitHub. You can also check out the difference between this and the previous version here.

Comments

The introduction to this article is misleading when it comes to accessibility, javascript, and screen readers.

<blockquote>"While the vast majority of people do have JavaScript enabled, it is still important to gracefully work for those that don't for accessibility reasons (for example, visually impaired people might access the site with a screen reader)."</blockquote>

It's a common myth that Javascript doesn't play well with screen readers. The vast majority of screen reader users do in fact have Javascript enabled, just like everybody else. What's more, the modal dialogs in Drupal 8 work very well with screen readers when JS is enabled. For example, when configuring a view, a user can click the link which opens the "display name" dialog. When the dialog opens, a screen reader tells the user they have entered a dialog, and announces it's title. When pressing the "apply" button, the screen reader announces that the dialog has closed. The Drupal 8 dialog JS manages the appropriate WAI-ARIA (roles, properties, and states) so that the web browser can provide this information to the host operating system's accessibility API.

We should indeed provide a no-js fallback for modal functionality, but it's not an accessibility issue per se. The main reason for is providing robustness for all users. Javascript can fail to load for a number of reasons, even if the user has JS turned on.

Blair Wadman's picture

Thanks for the comment. The point is, for accessibility reasons a site should work without JavaScript. Accessibility includes making sites work for everyone, including old browsers, slow internet, or JS failing.

Add new comment