1

Drupal 10 with Groups Module.

I have a Group with a field, field_custom_css. This field is a long formatted text area that is using Ace Editor. The user inputs CSS into this field.

In my theme, I'm using hook_preprocess_page(), like so:

if (($group = \Drupal::routeMatch()->getParameter('group'))) {

  $custom_css = $group->get('field_custom_css')->value;

  if ($custom_css) {
    $variables['#attached']['html_head'][] = [
      [
        '#tag' => 'style',
        '#attributes' => [
          'id' => 'course-css',
        ],
        '#value' => $custom_css,
      ],
      'course___css',
    ];
  }
}

This works, but it loads the CSS above all the other CSS in the <head>. This means that, unless I use !important for everything, I cannot easily override styling using this field.

I would like to load the CSS very last.

I've tried using $variables['page']['#attached']['html_head'][], but this doesn't add css to the <head> at all.

I've also tried using #weight, but that has no affect.

I would like course___css to be the very last thing that gets loaded into the <head>, or perhaps even gets attached to the body instead.

2 Answers 2

2

The best way to understand how it gets rendered is to look at a typical html.html.twig template:

<head>
  <head-placeholder token="{{ placeholder_token }}">
  <title>{{ head_title|safe_join(' | ') }}</title>
  <css-placeholder token="{{ placeholder_token }}">
  <js-placeholder token="{{ placeholder_token }}">
</head>

You're adding CSS to <head-placeholder>, and the system CSS comes out in <css-placeholder>. So the literal answer would be "swap those around in the template", but that's going to put your <head> in an undesirable order (styles shouldn't be at the top).

There's a super-simple solution, and (at least) a couple of more difficult, but arguably more robust/elegant solutions.

The simple method: move your code to hook_preprocess_html(), set a variable containing $custom_css, and literally render that where you want it in your theme's html.html.twig:

{% if custom_css %}<style>{{ custom_css }}</style>{% endif %}

Note that this method is risky, especially with user input; there's no validation unless you do your own. Even if the frontend editor validates it, a nefarious user could easily sidestep that in the browser and send dodgy CSS, conceivably breaking any page it's included on.

More complex solutions:

  1. Find a way to inject your styles into the existing <css-placeholder>. This would be best achieved by using hook_library_info_build(), querying your groups, and dynamically adding libraries. They can be attached to the output in something like hook_preprocess_group() with similar logic. This method would also mean implementing a small system to write the CSS to a physical file in the public file system, as libraries don't accept inline styles.
  2. Create your own placeholder. See this article for some inspiration. If your use case isn't going to get any more complicated, this might be overkill.
0

Expanding on and implementing @Clive's answer, here's what my full solution looks like:

in customtheme.theme:

/**
 * Implements hook_preprocess_HOOK() for HTML document templates.
 */
function customtheme_preprocess_html(&$variables) {

  // Add Custom CSS to #attributes for Group page
  if (($group = \Drupal::routeMatch()->getParameter('group'))) {
    $custom_css = $group->get('field_custom_css')->getString();
    $variables['attributes']['group']['id'] = $group->id();
    $variables['attributes']['group']['css'] = $custom_css;
  }

  // Add Custom CSS to #attributes for Group entity (e.g. node in Group)
  if (($node = \Drupal::request()->attributes->get('node'))) {
    
    if (  $node->getType() === 'page' ) {
      
      $storage = \Drupal::entityTypeManager()->getStorage('group_relationship');
      assert($storage instanceof GroupRelationshipStorageInterface);

      $group_relationship = $storage->loadByEntity($node);
      assert($group_relationship instanceof GroupRelationshipInterface);

      foreach ($storage->loadByEntity($node) as $group_content) {
        $custom_css = $group_content->getGroup()->get('field_custom_css')->getString();
        $variables['attributes']['group']['id'] = $group_content->getGroup()->id();
        $variables['attributes']['group']['css'] = $custom_css;
      }
    }
  }

}

in html.html.twig:

<!DOCTYPE html>
<html{{ html_attributes }}>
  <head>
    <head-placeholder token="{{ placeholder_token }}">
    <title>{{ head_title|safe_join(' | ') }}</title>
    <css-placeholder token="{{ placeholder_token }}">
    <style id="group-custom-css">
      {{ attributes.group.css }}
    </style>
  ...

This ensure that the value of field_custom_css loads for the Group page as well as any entities related to the Group.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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