Initial commit

This commit is contained in:
Zhongwei Li
2025-11-30 08:45:16 +08:00
commit 684fd0de77
12 changed files with 1344 additions and 0 deletions

View File

@@ -0,0 +1,11 @@
{
"name": "drupal-frontend",
"description": "Drupal Front End Specialist skill for theme development, Twig templates, and rendering system (Drupal 8-11+). Use when working with Drupal themes, Twig syntax, preprocessing, CSS/JS libraries, or template suggestions.",
"version": "1.0.0",
"author": {
"name": "Drupal Skills Team"
},
"skills": [
"./"
]
}

3
README.md Normal file
View File

@@ -0,0 +1,3 @@
# drupal-frontend
Drupal Front End Specialist skill for theme development, Twig templates, and rendering system (Drupal 8-11+). Use when working with Drupal themes, Twig syntax, preprocessing, CSS/JS libraries, or template suggestions.

430
SKILL.md Normal file
View File

@@ -0,0 +1,430 @@
---
name: drupal-frontend
description: Drupal Front End Specialist skill for theme development, Twig templates, and rendering system (Drupal 8-11+). Use when working with Drupal themes, Twig syntax, preprocessing, CSS/JS libraries, or template suggestions.
---
# Drupal Front End Development
## Overview
Enable expert-level Drupal front end development capabilities. Provide comprehensive guidance for theme development, Twig templating, preprocessing, responsive design, and asset management for Drupal 8, 9, 10, and 11+.
## When to Use This Skill
Invoke this skill when working with:
- **Theme development**: Creating or customizing Drupal themes
- **Twig templates**: Writing or modifying .twig template files
- **Preprocessing**: Implementing preprocess functions for templates
- **Template suggestions**: Adding custom template naming patterns
- **CSS/JS libraries**: Managing theme assets and dependencies
- **Responsive design**: Implementing breakpoints and mobile-first design
- **Rendering system**: Understanding Drupal's rendering pipeline
- **Theme hooks**: Implementing theme-related hooks and alterations
## Core Capabilities
### 1. Theme Development
Build complete, standards-compliant Drupal themes with proper structure:
**Quick start workflow:**
1. Use theme template from `assets/theme-template/` as starting point
2. Replace `THEMENAME` with your theme's machine name
3. Replace `THEMELABEL` with human-readable name
4. Customize templates, CSS, JS, and libraries
5. Enable theme and configure via Appearance UI
**Theme components:**
- `.info.yml` - Theme metadata and configuration
- `.libraries.yml` - CSS/JS library definitions
- `.theme` - Preprocess functions and theme logic
- `.breakpoints.yml` - Responsive breakpoints
- `templates/` - Twig template files
- `css/` - Stylesheets
- `js/` - JavaScript files
**Reference documentation:**
- `references/theming.md` - Complete theming guide with examples
### 2. Twig Template System
Master Twig syntax and Drupal-specific extensions:
**Twig fundamentals:**
- `{{ variable }}` - Print variables
- `{% if/for/set %}` - Control structures and logic
- `{# comment #}` - Comments and documentation
- `{{ var|filter }}` - Apply filters to variables
- `{% extends %}` - Template inheritance
- `{% block %}` - Define overridable sections
**Drupal-specific Twig:**
- `{{ 'Text'|t }}` - Translate strings
- `{{ attach_library('theme/library') }}` - Load CSS/JS
- `{{ url('route.name') }}` - Generate URLs
- `{{ path('route.name') }}` - Generate paths
- `{{ file_url('public://image.jpg') }}` - File URLs
- `{{ content|without('field') }}` - Exclude fields
**Common templates:**
- `page.html.twig` - Page layout structure
- `node.html.twig` - Node display
- `block.html.twig` - Block rendering
- `field.html.twig` - Field output
- `views-view.html.twig` - Views output
### 3. Preprocessing Functions
Modify template variables before rendering:
**Preprocess pattern:**
```php
function THEMENAME_preprocess_HOOK(&$variables) {
// Add or modify variables
// Access entities, services, configuration
// Prepare data for templates
}
```
**Common preprocess hooks:**
- `hook_preprocess_page()` - Page-level variables
- `hook_preprocess_node()` - Node-specific data
- `hook_preprocess_block()` - Block modifications
- `hook_preprocess_field()` - Field alterations
- `hook_preprocess_html()` - HTML document
**Best practices:**
- Keep logic in preprocess, markup in templates
- Use dependency injection when possible
- Cache properly with cache contexts/tags
- Document complex preprocessing
### 4. Template Suggestions
Provide specific templates for different contexts:
**Suggestion patterns:**
```
page--front.html.twig # Homepage
page--node--{nid}.html.twig # Specific node
page--node--{type}.html.twig # Content type
node--{type}--{viewmode}.html.twig # Type + view mode
block--{plugin-id}.html.twig # Specific block
field--{entity}--{field}.html.twig # Specific field
```
**Adding suggestions:**
```php
function THEMENAME_theme_suggestions_HOOK_alter(array &$suggestions, array $variables) {
// Add custom suggestions
$suggestions[] = 'custom__suggestion';
}
```
### 5. CSS & JavaScript Management
Organize and load theme assets efficiently:
**Library definition:**
```yaml
# THEMENAME.libraries.yml
global-styling:
version: 1.0
css:
base:
css/base/reset.css: {}
theme:
css/theme/styles.css: {}
js:
js/custom.js: {}
dependencies:
- core/drupal
```
**Loading libraries:**
- Global: Define in `.info.yml`
- Conditional: Use `attach_library()` in templates
- Preprocessed: Attach in preprocess functions
**Best practices:**
- Separate CSS into layers (base, layout, component, theme)
- Use CSS aggregation in production
- Minimize JavaScript dependencies
- Leverage Drupal's asset library system
### 6. Responsive Design
Implement mobile-first, accessible designs:
**Breakpoints definition:**
```yaml
# THEMENAME.breakpoints.yml
THEMENAME.mobile:
label: Mobile
mediaQuery: 'screen and (min-width: 0px)'
weight: 0
THEMENAME.tablet:
label: Tablet
mediaQuery: 'screen and (min-width: 768px)'
weight: 1
THEMENAME.desktop:
label: Desktop
mediaQuery: 'screen and (min-width: 1024px)'
weight: 2
```
**Mobile-first approach:**
1. Design for smallest screens first
2. Enhance for larger viewports
3. Use responsive images
4. Test across devices
5. Follow accessibility standards (WCAG)
## Development Workflow
### Creating a New Theme
1. **Scaffold the theme:**
```bash
cp -r assets/theme-template/ /path/to/drupal/themes/custom/mytheme/
cd /path/to/drupal/themes/custom/mytheme/
# Rename files
mv THEMENAME.info.yml mytheme.info.yml
mv THEMENAME.libraries.yml mytheme.libraries.yml
mv THEMENAME.theme mytheme.theme
```
2. **Update theme configuration:**
- Edit `mytheme.info.yml` - Set name, regions, base theme
- Edit `mytheme.libraries.yml` - Define CSS/JS libraries
- Replace `THEMENAME` with your machine name
- Replace `THEMELABEL` with your label
3. **Enable the theme:**
```bash
ddev drush cr
# Enable via UI at /admin/appearance or:
ddev drush config:set system.theme default mytheme -y
```
4. **Enable Twig debugging:**
- Copy `sites/default/default.services.yml` to `sites/default/services.yml`
- Set `twig.config.debug: true`
- Set `twig.config.auto_reload: true`
- Set `twig.config.cache: false`
- Run `ddev drush cr`
5. **Develop and iterate:**
- Modify templates in `templates/`
- Update CSS in `css/`
- Clear cache frequently: `ddev drush cr`
- Check browser console for errors
### Standard Development Workflow
1. **Enable development settings** (Twig debug, disable CSS/JS aggregation)
2. **Create/modify templates** based on suggestions from Twig debug
3. **Implement preprocessing** in `.theme` file for complex data manipulation
4. **Add CSS/JS** via libraries system
5. **Test** across browsers and devices
6. **Clear cache** after changes: `ddev drush cr`
### Finding Template Names
With Twig debugging enabled, inspect HTML source:
```html
<!-- FILE NAME SUGGESTIONS:
* page--node--123.html.twig
* page--node--article.html.twig
* page--node.html.twig
x page.html.twig
-->
```
The `x` indicates the active template. Others are suggestions you can create.
## Common Patterns
### Adding Custom Variables in Preprocess
```php
function mytheme_preprocess_page(&$variables) {
// Add site slogan
$variables['site_slogan'] = \Drupal::config('system.site')->get('slogan');
// Add current user
$variables['is_logged_in'] = \Drupal::currentUser()->isAuthenticated();
// Add custom class
$variables['attributes']['class'][] = 'custom-page-class';
}
```
### Template Inheritance
```twig
{# templates/page--article.html.twig #}
{% extends "page.html.twig" %}
{% block content %}
<div class="article-wrapper">
{{ parent() }}
</div>
{% endblock %}
```
### Conditional Library Loading
```php
function mytheme_preprocess_node(&$variables) {
if ($variables['node']->bundle() == 'article') {
$variables['#attached']['library'][] = 'mytheme/article-styles';
}
}
```
### Accessing Field Values in Twig
```twig
{# Get field value #}
{{ node.field_custom.value }}
{# Check if field has value #}
{% if node.field_image|render %}
{{ content.field_image }}
{% endif %}
{# Loop through multi-value field #}
{% for item in node.field_tags %}
{{ item.entity.label }}
{% endfor %}
```
## Best Practices
### Theme Development
1. **Base theme**: Choose appropriate base (Olivero, Claro, or none)
2. **Structure**: Organize CSS logically (base, layout, components, theme)
3. **Naming**: Use BEM or similar CSS methodology
4. **Comments**: Document complex Twig logic and preprocessing
5. **Performance**: Optimize images, minimize CSS/JS, lazy load when possible
### Twig Templates
1. **Logic**: Keep complex logic in preprocess, not templates
2. **Filters**: Use appropriate filters (|t, |clean_class, |safe_join)
3. **Whitespace**: Use {% spaceless %} to control output
4. **Debugging**: Enable Twig debugging during development only
5. **Suggestions**: Use specific templates only when needed
### Preprocessing
1. **Services**: Use dependency injection in theme service subscribers
2. **Caching**: Add proper cache contexts and tags
3. **Performance**: Avoid heavy operations in frequently-called preprocessors
4. **Organization**: Group related preprocessing logically
### CSS & JavaScript
1. **Libraries**: Group related assets into logical libraries
2. **Dependencies**: Declare all library dependencies
3. **Loading**: Load globally only when necessary
4. **Aggregation**: Enable in production for performance
### Accessibility
1. **Semantic HTML**: Use proper elements (header, nav, main, etc.)
2. **ARIA**: Add labels and roles when needed
3. **Keyboard**: Ensure keyboard navigation works
4. **Contrast**: Meet WCAG color contrast requirements
5. **Alt text**: Provide for all images
### Responsive Design
1. **Mobile-first**: Design for small screens, enhance for large
2. **Breakpoints**: Define logical breakpoints
3. **Images**: Use responsive image styles
4. **Testing**: Test across devices and screen sizes
5. **Performance**: Optimize for mobile networks
## Troubleshooting
### Template Changes Not Showing
- Clear cache: `ddev drush cr`
- Verify file location and naming
- Check Twig debug for active template
- Ensure library is properly defined
### CSS/JS Not Loading
- Check library definition in `.libraries.yml`
- Verify file paths are correct
- Ensure library is attached (globally or conditionally)
- Clear cache and check browser console
### Variables Not Available in Template
- Check preprocess function naming
- Verify variables are being set correctly
- Use Twig debugging: `{{ dump() }}`
- Clear cache after preprocessing changes
### Twig Debugging Not Working
- Verify `sites/default/services.yml` exists
- Check `twig.config.debug: true` is set
- Clear cache: `ddev drush cr`
- Check file permissions
## Resources
### Reference Documentation
- **`references/theming.md`** - Comprehensive theming guide
- Twig syntax and filters
- Template suggestions
- Preprocessing patterns
- Library management
- Responsive design
- Best practices
### Asset Templates
- **`assets/theme-template/`** - Complete theme scaffold
- `.info.yml` with regions and configuration
- `.libraries.yml` with CSS/JS examples
- `.theme` with preprocess examples
- Page template
- CSS and JS starter files
### Searching References
```bash
# Find specific Twig filter
grep -r "clean_class" references/
# Find preprocessing examples
grep -r "preprocess_node" references/
# Find library patterns
grep -r "libraries.yml" references/
```
## Version Compatibility
### Drupal 8 vs 9 vs 10 vs 11
- **Twig syntax**: Consistent across all versions
- **Base themes**: Some legacy themes removed in 10+
- **Libraries**: Same structure across versions
- **jQuery**: Drupal 9+ doesn't load jQuery by default
- **Claro**: Admin theme standard in 9+
- **Olivero**: Frontend theme standard in 10+
### Migration Notes
- When upgrading, check for deprecated base themes
- Review jQuery dependencies
- Test with current Twig version
- Check for removed core templates
## See Also
- **drupal-backend** - Module development, hooks, APIs
- **drupal-tooling** - DDEV and Drush development tools
- [Drupal Theming Guide](https://www.drupal.org/docs/theming-drupal) - Official documentation
- [Twig Documentation](https://twig.symfony.com/doc/) - Twig syntax reference

View File

@@ -0,0 +1,63 @@
# Theme Template
This is a basic Drupal theme template. To use it:
1. Copy this directory to your Drupal installation: `themes/custom/yourthemename/`
2. Rename files from `THEMENAME.*` to `yourthemename.*`
3. Replace all instances of `THEMENAME` with your theme's machine name (lowercase, underscores)
4. Replace all instances of `THEMELABEL` with your theme's human-readable name
5. Customize the theme as needed
## Files Included
- `THEMENAME.info.yml` - Theme metadata
- `THEMENAME.libraries.yml` - CSS/JS library definitions
- `THEMENAME.theme` - Theme functions and preprocessing
- `templates/page.html.twig` - Page template
- `css/base/reset.css` - Base CSS reset
- `js/custom.js` - Custom JavaScript
## Directory Structure
```
THEMENAME/
├── THEMENAME.info.yml
├── THEMENAME.libraries.yml
├── THEMENAME.theme
├── css/
│ └── base/
│ └── reset.css
├── js/
│ └── custom.js
└── templates/
└── page.html.twig
```
You can expand the CSS structure as needed:
```
css/
├── base/
│ └── reset.css
├── layout/
│ └── layout.css
├── components/
│ └── components.css
└── theme/
└── theme.css
```
## Next Steps
After creating your theme:
1. Clear cache: `ddev drush cr`
2. Enable your theme: Go to `/admin/appearance`
3. Set as default theme
4. Start customizing!
## Development Tips
- Enable Twig debugging in `sites/default/services.yml`
- Disable CSS/JS aggregation during development
- Clear cache frequently: `ddev drush cr`
- Use template suggestions for specific pages/content types

View File

@@ -0,0 +1,40 @@
name: THEMELABEL
description: 'A custom Drupal theme.'
type: theme
core_version_requirement: ^9 || ^10 || ^11
package: Custom
# Base theme (uncomment one)
# base theme: stable9
# base theme: claro
# base theme: olivero
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
libraries:
- THEMENAME/global-styling
- THEMENAME/global-scripts
# Component libraries (optional)
# component-libraries:
# atoms:
# paths:
# - components/atoms
# Logo and favicon
# logo: images/logo.svg
# favicon: images/favicon.ico

View File

@@ -0,0 +1,19 @@
global-styling:
version: 1.0
css:
base:
css/base/reset.css: {}
layout:
css/layout/layout.css: {}
component:
css/components/components.css: {}
theme:
css/theme/theme.css: {}
global-scripts:
version: 1.0
js:
js/custom.js: {}
dependencies:
- core/drupal
- core/drupalSettings

View File

@@ -0,0 +1,39 @@
<?php
/**
* @file
* Functions to support theming in the THEMELABEL theme.
*/
use Drupal\Core\Form\FormStateInterface;
/**
* Implements hook_preprocess_HOOK() for page templates.
*/
function THEMENAME_preprocess_page(&$variables) {
// Add custom variables or modify existing ones.
}
/**
* Implements hook_preprocess_HOOK() for node templates.
*/
function THEMENAME_preprocess_node(&$variables) {
/** @var \Drupal\node\NodeInterface $node */
$node = $variables['node'];
// Add custom variables based on node.
}
/**
* Implements hook_theme_suggestions_HOOK_alter() for page templates.
*/
function THEMENAME_theme_suggestions_page_alter(array &$suggestions, array $variables) {
// Add custom template suggestions.
}
/**
* Implements hook_form_alter().
*/
function THEMENAME_form_alter(&$form, FormStateInterface $form_state, $form_id) {
// Modify forms as needed.
}

View File

@@ -0,0 +1,26 @@
/**
* @file
* Basic CSS reset and normalization.
*/
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
html {
font-size: 16px;
line-height: 1.5;
}
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
color: #333;
background-color: #fff;
}
img {
max-width: 100%;
height: auto;
}

View File

@@ -0,0 +1,16 @@
/**
* @file
* Custom JavaScript for THEMELABEL theme.
*/
(function (Drupal) {
'use strict';
Drupal.behaviors.THEMENAME = {
attach: function (context, settings) {
// Custom JavaScript code here
console.log('THEMELABEL theme loaded');
}
};
})(Drupal);

View File

@@ -0,0 +1,57 @@
{#
/**
* @file
* Theme override to display a page.
*/
#}
<div class="layout-container">
{% if page.header %}
<header role="banner" class="site-header">
{{ page.header }}
</header>
{% endif %}
{% if page.primary_menu %}
<nav role="navigation" class="primary-menu">
{{ page.primary_menu }}
</nav>
{% endif %}
{% if page.breadcrumb %}
<div class="breadcrumb-wrapper">
{{ page.breadcrumb }}
</div>
{% endif %}
{% if page.highlighted %}
<div class="highlighted">
{{ page.highlighted }}
</div>
{% endif %}
<main role="main" class="main-content">
<a id="main-content" tabindex="-1"></a>
<div class="layout-content">
{{ page.content }}
</div>
{% if page.sidebar_first %}
<aside class="sidebar sidebar--first" role="complementary">
{{ page.sidebar_first }}
</aside>
{% endif %}
{% if page.sidebar_second %}
<aside class="sidebar sidebar--second" role="complementary">
{{ page.sidebar_second }}
</aside>
{% endif %}
</main>
{% if page.footer %}
<footer role="contentinfo" class="site-footer">
{{ page.footer }}
</footer>
{% endif %}
</div>

77
plugin.lock.json Normal file
View File

@@ -0,0 +1,77 @@
{
"$schema": "internal://schemas/plugin.lock.v1.json",
"pluginId": "gh:Omedia/drupal-skill:drupal-frontend",
"normalized": {
"repo": null,
"ref": "refs/tags/v20251128.0",
"commit": "41cbbcfc5b908d2a4c909c8ed3cf4bf346d8347d",
"treeHash": "246b3d55adff0aa493e95588c8d801d78afdbb17202b57bd7c53e8765536c031",
"generatedAt": "2025-11-28T10:12:18.414950Z",
"toolVersion": "publish_plugins.py@0.2.0"
},
"origin": {
"remote": "git@github.com:zhongweili/42plugin-data.git",
"branch": "master",
"commit": "aa1497ed0949fd50e99e70d6324a29c5b34f9390",
"repoRoot": "/Users/zhongweili/projects/openmind/42plugin-data"
},
"manifest": {
"name": "drupal-frontend",
"description": "Drupal Front End Specialist skill for theme development, Twig templates, and rendering system (Drupal 8-11+). Use when working with Drupal themes, Twig syntax, preprocessing, CSS/JS libraries, or template suggestions.",
"version": "1.0.0"
},
"content": {
"files": [
{
"path": "README.md",
"sha256": "5e23c9576a6751990607cd10129f80a3a93456d7edefa59cd3e467d3337e9312"
},
{
"path": "SKILL.md",
"sha256": "ff8d70723699a19b27030618b278f28ce8480489866e02de73ec889e5afd3791"
},
{
"path": "references/theming.md",
"sha256": "2dc93f7f888dcf8f108dc034a59c5290dfb03b60d54fefc5e5224bc09767dd09"
},
{
"path": ".claude-plugin/plugin.json",
"sha256": "a50e29c08f31276d4a3070447cbb2d42f17d6e90c15b86f5ec138e60832a8a5b"
},
{
"path": "assets/theme-template/THEMENAME.libraries.yml",
"sha256": "e7348c8ee5729c458f1189aa4559398e5b92568b4b790c869eda020af5ca5bed"
},
{
"path": "assets/theme-template/README.md",
"sha256": "6e45999798046e8bcfa6ce4d98a3a46f5902b7083af37319549403ed98f62825"
},
{
"path": "assets/theme-template/THEMENAME.info.yml",
"sha256": "25a0760f6de46d0eed90d4b5a183404248b70a5f297bf28b4a92c6617c7465bd"
},
{
"path": "assets/theme-template/THEMENAME.theme",
"sha256": "9219803ea44915d93d50f4c5ae813eb1c29cc35b7bcaa0a3e1514bb18435ba22"
},
{
"path": "assets/theme-template/css/base/reset.css",
"sha256": "3a548377de02f63a2b44019f206200b368cfca5af624b6413aed879ac6bad628"
},
{
"path": "assets/theme-template/js/custom.js",
"sha256": "edef32819f6f1f7682063f228ccd11f9ccaf920e602998806bad1f7541341010"
},
{
"path": "assets/theme-template/templates/page.html.twig",
"sha256": "0bd1013051a32a702fe2fe8c494be3cb81721918ebff535fdc1eb1265943fec6"
}
],
"dirSha256": "246b3d55adff0aa493e95588c8d801d78afdbb17202b57bd7c53e8765536c031"
},
"security": {
"scannedAt": null,
"scannerVersion": null,
"flags": []
}
}

563
references/theming.md Normal file
View File

@@ -0,0 +1,563 @@
# 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
<?php
/**
* @file
* Functions to support theming in the My Theme theme.
*/
use Drupal\Core\Form\FormStateInterface;
use Drupal\node\NodeInterface;
/**
* Implements hook_preprocess_HOOK() for page templates.
*/
function mytheme_preprocess_page(&$variables) {
// Add custom variable to all pages
$variables['site_slogan'] = \Drupal::config('system.site')->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
<div class="layout-container">
<header role="banner">
{{ page.header }}
{{ page.primary_menu }}
</header>
{{ page.breadcrumb }}
{{ page.highlighted }}
<main role="main" class="main-content">
<a id="main-content" tabindex="-1"></a>
<div class="layout-content">
{{ page.content }}
</div>
{% if page.sidebar_first %}
<aside class="sidebar sidebar--first" role="complementary">
{{ page.sidebar_first }}
</aside>
{% endif %}
{% if page.sidebar_second %}
<aside class="sidebar sidebar--second" role="complementary">
{{ page.sidebar_second }}
</aside>
{% endif %}
</main>
{% if page.footer %}
<footer role="contentinfo">
{{ page.footer }}
</footer>
{% endif %}
</div>
```
### node.html.twig
```twig
<article{{ attributes.addClass('node', 'node--type-' ~ node.bundle|clean_class, node.isPromoted() ? 'node--promoted', node.isSticky() ? 'node--sticky', not node.isPublished() ? 'node--unpublished', view_mode ? 'node--view-mode-' ~ view_mode|clean_class) }}>
{{ title_prefix }}
{% if not page %}
<h2{{ title_attributes.addClass('node__title') }}>
<a href="{{ url }}" rel="bookmark">{{ label }}</a>
</h2>
{% endif %}
{{ title_suffix }}
{% if display_submitted %}
<div class="node__meta">
{{ author_picture }}
<span{{ author_attributes }}>
{% trans %}Submitted by {{ author_name }} on {{ date }}{% endtrans %}
</span>
{{ metadata }}
</div>
{% endif %}
<div{{ content_attributes.addClass('node__content') }}>
{{ content }}
</div>
</article>
```
### block.html.twig
```twig
<div{{ attributes.addClass('block', 'block-' ~ configuration.provider|clean_class, 'block-' ~ plugin_id|clean_class) }}>
{{ title_prefix }}
{% if label %}
<h2{{ title_attributes }}>{{ label }}</h2>
{% endif %}
{{ title_suffix }}
{% block content %}
{{ content }}
{% endblock %}
</div>
```
### field.html.twig
```twig
{% if multiple %}
<div{{ attributes.addClass('field', 'field--name-' ~ field_name|clean_class, 'field--type-' ~ field_type|clean_class) }}>
{% if label %}
<div{{ title_attributes.addClass('field__label') }}>{{ label }}</div>
{% endif %}
<div class="field__items">
{% for item in items %}
<div{{ item.attributes.addClass('field__item') }}>{{ item.content }}</div>
{% endfor %}
</div>
</div>
{% else %}
{% for item in items %}
<div{{ attributes.addClass('field', 'field--name-' ~ field_name|clean_class, 'field--type-' ~ field_type|clean_class) }}>
{% if label %}
<div{{ title_attributes.addClass('field__label') }}>{{ label }}</div>
{% endif %}
<div{{ item.attributes.addClass('field__item') }}>{{ item.content }}</div>
</div>
{% endfor %}
{% endif %}
```
## Twig Filters & Functions
### Common Filters
```twig
{# Translate #}
{{ 'Hello World'|t }}
{# Clean class name #}
{{ 'Field Name'|clean_class }}
{# Format date #}
{{ node.created.value|date('F j, Y') }}
{# Safe join #}
{{ items|safe_join(', ') }}
{# Render #}
{{ content|render }}
{# Without (remove array elements) #}
{{ content|without('field_image') }}
{# Placeholder #}
{{ 'Hello @name'|t({'@name': name}) }}
```
### Common Functions
```twig
{# Attach library #}
{{ attach_library('mytheme/modal') }}
{# URL #}
<a href="{{ url('entity.node.canonical', {'node': node.id}) }}">Link</a>
{# Path #}
<a href="{{ path('entity.node.canonical', {'node': node.id}) }}">Link</a>
{# File URL #}
{{ file_url('public://image.jpg') }}
{# Create attribute #}
{% set attributes = create_attribute() %}
{{ attributes.addClass('custom-class') }}
```
## Breakpoints (mytheme.breakpoints.yml)
```yaml
mytheme.mobile:
label: Mobile
mediaQuery: 'screen and (min-width: 0px)'
weight: 0
multipliers:
- 1x
- 2x
mytheme.tablet:
label: Tablet
mediaQuery: 'screen and (min-width: 768px)'
weight: 1
multipliers:
- 1x
- 2x
mytheme.desktop:
label: Desktop
mediaQuery: 'screen and (min-width: 1024px)'
weight: 2
multipliers:
- 1x
- 2x
mytheme.wide:
label: Wide
mediaQuery: 'screen and (min-width: 1440px)'
weight: 3
multipliers:
- 1x
```
## Debug Mode
Enable Twig debugging in development:
**sites/default/services.yml**
```yaml
parameters:
twig.config:
debug: true
auto_reload: true
cache: false
```
Then clear cache:
```bash
ddev drush cr
```
## Theme Development Workflow
1. **Enable development settings**
```bash
# Copy and enable development settings
cp sites/example.settings.local.php sites/default/settings.local.php
```
2. **Disable CSS/JS aggregation**
- Go to `/admin/config/development/performance`
- Uncheck "Aggregate CSS files"
- Uncheck "Aggregate JavaScript files"
3. **Clear cache frequently**
```bash
ddev drush cr
```
4. **Use Twig debugging**
- Check HTML source for template suggestions
- Look for `<!-- BEGIN OUTPUT -->` comments
5. **Rebuild theme registry**
```bash
ddev drush drush cr
```
## Best Practices
1. **BEM Methodology**: Use BEM for CSS class naming
2. **Component-based**: Build reusable components
3. **Accessibility**: Follow WCAG guidelines
4. **Performance**: Optimize images, minimize CSS/JS
5. **Mobile-first**: Design for mobile, enhance for desktop
6. **Semantic HTML**: Use proper HTML5 elements
7. **Template suggestions**: Use specific templates when needed
8. **Libraries**: Group related CSS/JS in libraries
9. **Preprocessing**: Keep logic in .theme file, not templates
10. **Documentation**: Comment complex template logic
## Common Template Suggestions
```
page--front.html.twig # Front page
page--node--123.html.twig # Specific node
page--node--article.html.twig # Content type
node--article--full.html.twig # Bundle and view mode
node--123.html.twig # Specific node
block--system-branding-block.html.twig # Specific block
field--node--title--article.html.twig # Specific field
views-view--blog--page-1.html.twig # View and display
```
## Useful Resources
- Theming Guide: https://www.drupal.org/docs/theming-drupal
- Twig Documentation: https://twig.symfony.com/doc/
- Template Suggestions: https://www.drupal.org/docs/theming-drupal/twig-in-drupal/debugging-twig-templates