22

How can I programmatically change the active Drupal 8 theme?

In Drupal 6, we used the following code.

global $custom_theme;
$custom_theme = 'garland';

In Drupal 7, we used hook_custom_theme().

In Drupal 8, what's the correct way to do this?

25

In Drupal 8, you use theme negotiators, which essentially are services using a specific tag. See the theme negotiators implemented by Drupal, to understand exactly how they work; the example given in the change record is not updated.

user.services.yml

  theme.negotiator.admin_theme:
    class: Drupal\user\Theme\AdminNegotiator
    arguments: ['@current_user', '@config.factory', '@entity_type.manager', '@router.admin_context']
    tags:
      - { name: theme_negotiator, priority: -40 }

AdminNegotiator.php

<?php

namespace Drupal\user\Theme;

use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\DependencyInjection\DeprecatedServicePropertyTrait;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Routing\AdminContext;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\Theme\ThemeNegotiatorInterface;

/**
 * Sets the active theme on admin pages.
 */
class AdminNegotiator implements ThemeNegotiatorInterface {
  use DeprecatedServicePropertyTrait;

  /**
   * {@inheritdoc}
   */
  protected $deprecatedProperties = ['entityManager' => 'entity.manager'];

  /**
   * The current user.
   *
   * @var \Drupal\Core\Session\AccountInterface
   */
  protected $user;

  /**
   * The config factory.
   *
   * @var \Drupal\Core\Config\ConfigFactoryInterface
   */
  protected $configFactory;

  /**
   * The entity type manager.
   *
   * @var \Drupal\Core\Entity\EntityTypeManagerInterface
   */
  protected $entityTypeManager;

  /**
   * The route admin context to determine whether a route is an admin one.
   *
   * @var \Drupal\Core\Routing\AdminContext
   */
  protected $adminContext;

  /**
   * Creates a new AdminNegotiator instance.
   *
   * @param \Drupal\Core\Session\AccountInterface $user
   *   The current user.
   * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
   *   The config factory.
   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
   *   The entity type manager.
   * @param \Drupal\Core\Routing\AdminContext $admin_context
   *   The route admin context to determine whether the route is an admin one.
   */
  public function __construct(AccountInterface $user, ConfigFactoryInterface $config_factory, EntityTypeManagerInterface $entity_type_manager, AdminContext $admin_context) {
    $this->user = $user;
    $this->configFactory = $config_factory;
    $this->entityTypeManager = $entity_type_manager;
    $this->adminContext = $admin_context;
  }

  /**
   * {@inheritdoc}
   */
  public function applies(RouteMatchInterface $route_match) {
    return ($this->entityTypeManager->hasHandler('user_role', 'storage') && $this->user->hasPermission('view the administration theme') && $this->adminContext->isAdminRoute($route_match->getRouteObject()));
  }

  /**
   * {@inheritdoc}
   */
  public function determineActiveTheme(RouteMatchInterface $route_match) {
    return $this->configFactory->get('system.theme')->get('admin');
  }

}

The code is pretty easy to understand: The applies() method returns TRUE when the current route is the one for which your module wants to change the theme; the determineActiveTheme() method returns the theme machine name of the theme to apply.

See also ThemeNegotiator::determineActiveTheme() should not require a RouteMatch to be passed in for a possible change on the arguments received from the methods used by theme negotiators; if that patch is applied, you will need to change your theme negotiator code too.

| improve this answer | |
  • Shouldn't applies() be written as applies($route_match) in the above example? Posted the same question to the linked d.o. page. Thanks! – Stefanos Petrakis May 22 '16 at 15:41
  • @StefanosPetrakis Hmmm... Any current implementation does get that as parameter, contrary to what that change record says. – kiamlaluno May 22 '16 at 17:04
  • I have updated the answer, using the code Drupal core is actually using in one of its theme negotiators. – kiamlaluno May 22 '16 at 17:17
1

Another solution to this question is using the Theme Switcher module. it's compatible with Drupal 8 and 9 that developed by themeKey module developer.

Theme Switcher module allows you to create theme-switching rules which allow automatic selection of a theme based on Drupal 8 Conditions system. Because of this it can also be easily extended to support additional custom conditions exposed by other modules. In Drupal 8 conditions are no longer defined through a hook and they are plugins now.

With this module you can apply a specific theme to a unique node, use different themes based on the page language, use contrib conditions like a domain to apply one theme or another. The best of all is that you can do al of the above at once!

| improve this answer | |

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.