Розробка користувальницької теми Drupal
Теми Drupal — це набір Twig-шаблонів, CSS, JS та конфігураційних YAML-файлів. Розробка з нуля або на основі базової теми. Contrib-теми типу Bootstrap або Gin використовуються як стартові точки або для адміністративного інтерфейсу.
Структура теми
web/themes/custom/my_theme/
├── my_theme.info.yml # описання теми
├── my_theme.libraries.yml # CSS/JS бібліотеки
├── my_theme.theme # PHP хуки теми
├── config/
│ └── install/ # конфіг, що встановлюється з темою
├── css/
│ ├── base.css
│ ├── layout.css
│ └── components/
├── js/
│ └── main.js
├── images/
├── fonts/
├── templates/
│ ├── layout/
│ │ ├── html.html.twig
│ │ └── page.html.twig
│ ├── content/
│ │ ├── node.html.twig
│ │ ├── node--article.html.twig
│ │ └── node--article--teaser.html.twig
│ ├── block/
│ │ └── block.html.twig
│ └── field/
│ └── field--body.html.twig
└── screenshot.png
my_theme.info.yml
name: Моя тема
type: theme
description: 'Користувальницька тема проекту'
core_version_requirement: ^10
base theme: stable9 # або false якщо з нуля
libraries:
- my_theme/global
regions:
header: Заголовок
primary_menu: 'Основне меню'
breadcrumb: Хлібні крошки
highlighted: Виділено
content: Контент
sidebar_first: 'Бічна панель'
footer: Футер
CSS/JS Бібліотеки
# my_theme.libraries.yml
global:
version: VERSION
css:
base:
css/base.css: {}
layout:
css/layout.css: {}
css/components/header.css: {}
js:
js/main.js: { defer: true }
dependencies:
- core/drupal
- core/jquery
# Окрема бібліотека для слайдера — завантажується тільки при потребі
slider:
version: VERSION
css:
component:
css/components/slider.css: {}
js:
js/slider.js: {}
dependencies:
- core/once
Підключуйте бібліотеки в шаблоні:
{# У шаблоні node--landing.html.twig #}
{{ attach_library('my_theme/slider') }}
Twig Шаблони
Drupal використовує ієрархію шаблонів — чим специфічніше ім'я, тим вищий пріоритет:
node.html.twig # всі ноди
node--article.html.twig # тип article
node--article--full.html.twig # тип article, вид full
node--123.html.twig # конкретна нода за id
Приклад переопалення шаблона статті:
{# templates/content/node--article--full.html.twig #}
<article{{ attributes.addClass('article', 'article--full') }}>
{% if label %}
<h1{{ title_attributes.addClass('article__title') }}>
{{ label }}
</h1>
{% endif %}
<div class="article__meta">
{% if display_submitted %}
<span class="article__author">{{ author_name }}</span>
<time class="article__date" datetime="{{ date.attributes.datetime }}">
{{ date }}
</time>
{% endif %}
{% if content.field_tags %}
<div class="article__tags">
{{ content.field_tags }}
</div>
{% endif %}
</div>
{% if content.field_image %}
<div class="article__cover">
{{ content.field_image }}
</div>
{% endif %}
<div class="article__body prose">
{{ content.body }}
</div>
{# Рендеримо решту полів крім тих, що вже вивели #}
{{ content|without('body', 'field_tags', 'field_image', 'links') }}
</article>
PHP Хуки в .theme Файлі
// my_theme.theme
/**
* Додаємо змінні в шаблон сторінки.
*/
function my_theme_preprocess_page(array &$variables): void {
$variables['site_name'] = \Drupal::config('system.site')->get('name');
$variables['is_front'] = \Drupal::service('path.matcher')->isFrontPage();
// Хлібні крошки з користувальницькою логікою
$route = \Drupal::routeMatch();
if ($node = $route->getParameter('node')) {
$variables['node_type'] = $node->bundle();
}
}
/**
* Змінні для шаблона ноди.
*/
function my_theme_preprocess_node(array &$variables): void {
$node = $variables['node'];
if ($node->bundle() === 'article') {
$variables['reading_time'] = my_theme_calculate_reading_time($node->get('body')->value);
}
}
function my_theme_calculate_reading_time(string $html): int {
$text = strip_tags($html);
$words = str_word_count($text);
return (int) ceil($words / 200); // 200 слів в хвилину
}
/**
* Переопаки пропозицій шаблонів — для дебагу.
*/
function my_theme_theme_suggestions_node_alter(array &$suggestions, array $variables): void {
$node = $variables['elements']['#node'];
$view_mode = $variables['elements']['#view_mode'];
// Додаємо пропозицію за типом матеріалу
if ($node->hasField('field_material_type') && !$node->get('field_material_type')->isEmpty()) {
$type = $node->get('field_material_type')->value;
$suggestions[] = 'node__' . $node->bundle() . '__' . $type;
}
}
/**
* Alter для форми — додаємо класи.
*/
function my_theme_form_alter(array &$form, FormStateInterface $form_state, string $form_id): void {
if ($form_id === 'contact_message_feedback_form') {
$form['#attributes']['class'][] = 'contact-form';
$form['actions']['submit']['#attributes']['class'][] = 'btn btn--primary';
}
}
Адаптивні Зображення та Стилі Зображень
# config/install/image.style.article_cover.yml
langcode: en
status: true
id: article_cover
label: 'Обкладинка статті'
effects:
uuid1:
id: image_scale_and_crop
data:
anchor: center-center
width: 1200
height: 630
Використовуйте responsive_image замість звичайного image в шаблоні:
{% if content.field_image %}
{{- content.field_image -}}
{% endif %}
Група адаптивних зображень налаштовується в /admin/config/media/responsive-image-style через UI або YAML.
Побудова Фронтенду (опціонально)
Якщо тема використовує npm-побудову:
// package.json
{
"scripts": {
"build": "postcss css/src -o css --map",
"watch": "postcss css/src -o css --watch"
}
}
Або Vite/Webpack якщо потрібні ES-модулі, TypeScript:
// vite.config.js
export default {
build: {
outDir: 'dist',
rollupOptions: {
input: { main: 'js/src/main.ts' },
output: { entryFileNames: 'js/[name].js' }
}
},
css: { postcss: './postcss.config.js' }
}
Дебаг Шаблонів
В settings.local.php включаємо Twig дебаг:
$config['system.performance']['css']['preprocess'] = FALSE;
$config['system.performance']['js']['preprocess'] = FALSE;
$settings['cache']['bins']['render'] = 'cache.backend.null';
// Показує імена шаблонів в HTML коментарях
$config['twig.settings']['debug'] = TRUE;
Після включення в source code сторінки будуть видні всі використані шаблони та доступні пропозиції. Це основний інструмент при розробці.
Терміни
Базова тема з шаблонами для основних типів контенту, адаптивна, з бібліотеками: 5–8 днів. З анімаціями, складним JS, адаптивними зображеннями, користувальницькими блоками: 10–15 днів.







