# Drupal Theming Reference Comprehensive guide to Drupal theming for Drupal 8-11+. ## Theme Structure ``` mytheme/ ├── mytheme.info.yml # Theme metadata (required) ├── mytheme.libraries.yml # CSS/JS libraries ├── mytheme.theme # Theme functions and preprocess ├── mytheme.breakpoints.yml # Responsive breakpoints ├── logo.svg # Theme logo ├── screenshot.png # Admin screenshot ├── composer.json # Composer dependencies ├── package.json # NPM dependencies (if using build tools) ├── config/ │ ├── install/ # Default configuration │ └── schema/ # Configuration schema ├── css/ │ ├── base/ │ ├── components/ │ ├── layout/ │ └── theme/ ├── js/ │ └── custom.js ├── images/ ├── templates/ │ ├── block/ │ ├── content/ │ ├── field/ │ ├── layout/ │ ├── navigation/ │ ├── views/ │ ├── page.html.twig │ ├── node.html.twig │ └── block.html.twig └── src/ # Optional PHP classes └── Plugin/ └── Preprocess/ ``` ## Theme Info File (mytheme.info.yml) ```yaml name: My Theme type: theme description: 'A custom Drupal theme' core_version_requirement: ^9 || ^10 || ^11 package: Custom # Base theme base theme: stable9 # Or for no base theme: # base theme: false # Regions regions: header: Header primary_menu: 'Primary menu' secondary_menu: 'Secondary menu' page_top: 'Page top' page_bottom: 'Page bottom' highlighted: Highlighted breadcrumb: Breadcrumb content: Content sidebar_first: 'Sidebar first' sidebar_second: 'Sidebar second' footer: Footer # Libraries to load on all pages libraries: - mytheme/global-styling - mytheme/global-scripts # Libraries to load only when certain conditions are met libraries-override: # Replace core library core/drupal.dialog: mytheme/custom-dialog: {} libraries-extend: # Add to existing library core/drupal.dialog: - mytheme/dialog-extend # Remove libraries libraries-override: core/normalize: css: base: assets/vendor/normalize-css/normalize.css: false # Component libraries (for single-directory components) component-libraries: atoms: paths: - components/atoms molecules: paths: - components/molecules # Logo and favicon logo: images/logo.svg favicon: images/favicon.ico # Stylesheets to remove stylesheets-remove: - core/assets/vendor/normalize-css/normalize.css - '@classy/css/components/tabs.css' # CKEditor stylesheet ckeditor_stylesheets: - css/ckeditor.css # Hidden theme (for base themes) hidden: false ``` ## Libraries (mytheme.libraries.yml) ```yaml global-styling: version: 1.0 css: base: css/base/reset.css: {} layout: css/layout/layout.css: {} component: css/components/button.css: {} css/components/card.css: {} theme: css/theme/colors.css: {} css/theme/typography.css: {} global-scripts: version: 1.0 js: js/custom.js: {} dependencies: - core/drupal - core/jquery # Conditional library (loaded via preprocess or template) modal: version: 1.0 css: component: css/components/modal.css: {} js: js/modal.js: {} dependencies: - core/drupal - core/drupalSettings # External library fontawesome: version: 6.0 css: theme: https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css: { type: external, minified: true } # SCSS/SASS (requires compilation) compiled-styles: version: 1.0 css: theme: dist/css/styles.css: {} dependencies: - core/normalize ``` ## Theme Functions (mytheme.theme) ```php get('slogan'); // Add body classes $variables['attributes']['class'][] = 'custom-page-class'; } /** * Implements hook_preprocess_HOOK() for node templates. */ function mytheme_preprocess_node(&$variables) { /** @var \Drupal\node\NodeInterface $node */ $node = $variables['node']; // Add custom variables $variables['created_date'] = \Drupal::service('date.formatter')->format( $node->getCreatedTime(), 'custom', 'F j, Y' ); // Add template suggestions $variables['theme_hook_suggestions'][] = 'node__' . $node->bundle() . '__' . $variables['view_mode']; } /** * Implements hook_preprocess_HOOK() for block templates. */ function mytheme_preprocess_block(&$variables) { // Add block ID as class if (isset($variables['elements']['#id'])) { $variables['attributes']['class'][] = 'block-' . $variables['elements']['#id']; } } /** * Implements hook_preprocess_HOOK() for field templates. */ function mytheme_preprocess_field(&$variables) { $element = $variables['element']; // Custom preprocessing for specific fields if ($element['#field_name'] == 'field_custom') { $variables['custom_class'] = 'field-custom-class'; } } /** * Implements hook_theme_suggestions_HOOK_alter() for page templates. */ function mytheme_theme_suggestions_page_alter(array &$suggestions, array $variables) { // Add template suggestions based on current route if ($node = \Drupal::routeMatch()->getParameter('node')) { if ($node instanceof NodeInterface) { $suggestions[] = 'page__node__' . $node->bundle(); $suggestions[] = 'page__node__' . $node->id(); } } } /** * Implements hook_theme_suggestions_HOOK_alter() for node templates. */ function mytheme_theme_suggestions_node_alter(array &$suggestions, array $variables) { /** @var \Drupal\node\NodeInterface $node */ $node = $variables['elements']['#node']; $view_mode = $variables['elements']['#view_mode']; // Add suggestion for bundle and view mode combination $suggestions[] = 'node__' . $node->bundle() . '__' . $view_mode; } /** * Implements hook_form_alter(). */ function mytheme_form_alter(&$form, FormStateInterface $form_state, $form_id) { // Add custom classes to forms if ($form_id == 'search_block_form') { $form['actions']['submit']['#attributes']['class'][] = 'custom-search-submit'; } } /** * Implements hook_page_attachments_alter(). */ function mytheme_page_attachments_alter(array &$attachments) { // Add custom meta tags $meta_charset = [ '#tag' => 'meta', '#attributes' => [ 'charset' => 'utf-8', ], ]; $attachments['#attached']['html_head'][] = [$meta_charset, 'meta_charset']; } /** * Implements hook_library_info_alter(). */ function mytheme_library_info_alter(&$libraries, $extension) { // Modify libraries from other modules if ($extension == 'core' && isset($libraries['drupal.dialog'])) { $libraries['drupal.dialog']['dependencies'][] = 'mytheme/custom-dialog'; } } ``` ## Twig Templates ### page.html.twig ```twig