Drupal 8's ThemeNegotiatorInterface provides a way for developers to create their own management class that decides which theme should go with which content. Whether it is something simple like controlling the theme for user profile pages, or something more complex like switching themes depending on a user's role, using this interface can make the process simple and very flexible.

You can create more than one implementation. A typical usage pattern would be to add an implementation to a module that requires a certain theme for specific pages, for example administration pages included in a module. More on multiple implementations and how Drupal "chains" these implementations will be discussed later.

Implement The Interface

To start, take a look at the documentation, and create a class that implements the interface.


<?php
/**
 * @file
 * Contains \Drupal\jcmodule\Theme\ThemeNegotiator
 */
namespace Drupal\jcmodule\Theme;

use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\Core\Theme\ThemeNegotiatorInterface;

class ThemeNegotiator implements ThemeNegotiatorInterface {

ThemeNegotiatorInterface requires two methods to be implemented


// Whether this theme negotiator should be used to set the theme.
function applies(RouteMatchInterface $route_match)

// Determines the active theme for the request.
function determineActiveTheme(RouteMatchInterface $route_match)

If applies return true, determineActiveTheme is called and either the theme is chosen or null is returned. As you can see, both methods have the same signature and return similar values related to a theme switch.

To keep my class easy to maintain, I created my own common negotiateRoute function that satisfies the requirements for both interface functions.. If my function returns a theme name, that theme is used.

Here is my common class function


    /**
     * Function that does all of the work
     * @param RouteMatchInterface $route_match
     * @return null|string
     */
    private function negotiateRoute(RouteMatchInterface $route_match)
    {
        if ($route_match->getRouteName() == 'user.login')
        {
            return 'seven';
        }
        elseif ($route_match->getRouteName() == 'some.other.route')
        {
            return 'some_other_theme';
        }
        return null;
    }

Symfony Service Requirement

For Drupal to find your implementation, you need to set up your class as a Symfony service and tag it.


# Module services file jcmodule.services.yml

services:

    jcmodule.theme.negotiator:
        class: Drupal\jcmodule\Theme\ThemeNegotiator
        tags:
          - { name: theme_negotiator, priority: 1000 }

  • The tag name theme_negotiator tells Drupal that this is a class that implements ThemeNegotiatorInterface, and should be used to determine the theme in the current request.
  • The tag priority tells Drupal how important this class is in relation to other ThemeNegotiatorInterface implementations in your project. The higher the priority, the sooner Drupal will call your class logic.

How this all works in Drupal

  • When is comes time for Drupal to render your page and select a theme, it looks for all classes that implement ThemeNegotiatorInterface.
  • All implementations begin to be called in priority order, from the highest to the lowest.
  • If the applies method returns false, Drupal moves on to the next implementation.
  • If the applies method returns true, Drupal calls the determineActiveTheme method in the same implementation.
  • If determineActiveTheme returns null, Drupal moves on to the next implementation.
  • If determineActiveTheme returns a theme name, Drupal will use this theme, and no further implementations are called.
  • Drupal has its own default implementation. If no other implementations selects a theme, the default will select a theme.

In my example, routes are checked to determine the theme, but you can add whatever logic you may need. This is where you can get creative. Switching the theme based on a user's role can be done within this context:


        $userRolesArray = \Drupal::currentUser()->getRoles();
        if (in_array("administrator", $userRolesArray)) 
        {
            return 'seven';
        }

Putting it all together

Here is the full source of my ThemeNegotiatorInterface implementation example on GitHub.

Conclusion

There are many modules in Drupal 7 & 8 that provide rule based tools for switching themes, like Administration Theme and ThemeKey to name just a few. However, few of these modules, at least at the time that this article was published, have been ported yet to Drupal 8. Similar modules can make the process easy to maintain and portable across Drupal instances. But if you don't want to wait for them to be ported to Drupal 8, or have complex rules to implement, or need a solution for your own custom module, ThemeNegotiatorInterface may be the way to go.

And it's always fun learning how to do it yourself.

Last Updated October 14th, 2019