Skip to content

Block Rendering & Theming

When to Use

Customizing block appearance through templates, preprocessing, or altering render output.

Steps

  1. Block render pipeline
  2. BlockViewBuilder::viewMultiple() called by region
  3. BlockAccessControlHandler checks access
  4. BlockPluginInterface::build() generates render array
  5. BlockViewBuilder::preRender() adds wrapper
  6. Theme system renders templates

  7. Using block templates

  8. Default: block.html.twig
  9. Suggestions: block--{plugin-id}.html.twig, block--{region}.html.twig
  10. Place in {theme}/templates/block/

  11. Preprocessing blocks

    function mytheme_preprocess_block(&$variables) {
      $block = $variables['elements']['#block'];
      $plugin_id = $block->getPluginId();
      $variables['custom_var'] = 'value';
    }
    

  12. Template variables available

  13. {{ content }} — Block content from build()
  14. {{ plugin_id }} — Block plugin ID
  15. {{ label }} — Block label
  16. {{ configuration }} — Block configuration
  17. {{ attributes }} — HTML attributes

  18. Altering block output

    function mymodule_block_view_alter(&$build, BlockPluginInterface $block) {
      if ($block->getPluginId() === 'system_branding_block') {
        $build['#attached']['library'][] = 'mymodule/branding-styles';
      }
    }
    

Decision Points

At this step... If... Then...
Step 2 (templates) Styling specific plugin Use block--{plugin-id}.html.twig
Step 2 (templates) Styling region's blocks Use block--{region}.html.twig
Step 3 (preprocess) Adding data for template Preprocess; keep logic out of templates
Step 5 (alter) Changing all blocks Use hook_block_view_alter()
Step 5 (alter) Changing specific plugin Check plugin ID in alter hook

Pattern

Template suggestion (block--system-branding-block.html.twig):

<div{{ attributes.addClass('site-branding') }}>
  {% if content.site_logo %}
    <a href="{{ path('<front>') }}">
      {{ content.site_logo }}
    </a>
  {% endif %}
  {% if content.site_name %}
    <h1>{{ content.site_name }}</h1>
  {% endif %}
</div>

Preprocessing:

function mytheme_preprocess_block(&$variables) {
  $block = $variables['elements']['#block'];

  // Add custom class based on plugin
  $plugin_id = $block->getPluginId();
  $variables['attributes']['class'][] = 'block-plugin-' . str_replace('_', '-', $plugin_id);

  // Add region as variable
  $variables['region'] = $block->getRegion();
}

Altering block build:

function mymodule_block_view_alter(&$build, BlockPluginInterface $block) {
  // Add cache tag to all blocks
  $build['#cache']['tags'][] = 'mymodule:blocks';

  // Modify specific block
  if ($block->getPluginId() === 'my_custom_block') {
    $build['#prefix'] = '<div class="custom-wrapper">';
    $build['#suffix'] = '</div>';
  }
}

Reference: core/modules/block/templates/block.html.twig, core/modules/block/src/BlockViewBuilder.php

Common Mistakes

  • Putting business logic in templates → Use preprocess or alter hooks; templates are for presentation only
  • Not using attributes variable in custom templates → Loses important classes, IDs, ARIA attributes
  • Overriding block.html.twig when specific suggestion is better → Use block--{plugin-id}.html.twig for targeted changes
  • Forgetting to clear cache after template changes → Twig templates cached; must clear cache
  • Altering $build without preserving cache metadata → Merge cache tags/contexts, don't replace

See Also