Build your first theme 6: settings, responsive images, and shipping
In the final part of the theme tutorial you give site builders a setting, turn part 3’s breakpoints into responsive images, and take stock of what ships.
What you’ll build
Section titled “What you’ll build”- A Show drop shadows on cards checkbox on First theme’s settings page, stored in configuration, read through the modern theme-settings API, and wired to the teaser cards.
- A responsive image style built on the
first_themebreakpoint group from part 3, applied to article images. - A finished theme, ready to commit and deploy.
Prerequisites
Section titled “Prerequisites”- First theme as part 5 left it.
- At least one article with an image, for step 3.
Step 1: Add the settings form
Section titled “Step 1: Add the settings form”Every installed theme gets a settings page under Administer → Appearance with core’s shared options (logo, favicon). A theme extends that form by implementing hook_form_system_theme_settings_alter() in a theme-settings.php file in its root; core includes that file (and the .theme file) right before building the form. Create theme-settings.php, starting with the opening <?php tag and a @file docblock, then add:
<?php
/** * @file * Theme settings form for the First theme. */
declare(strict_types=1);
use Drupal\Core\Extension\ThemeSettingsProvider;use Drupal\Core\Form\FormStateInterface;
/** * Implements hook_form_system_theme_settings_alter(). * * Adds theme-specific options to Appearance > Settings > First theme. * Read the value in preprocess hooks with the ThemeSettingsProvider * service, then pass it to your templates as a variable. */function first_theme_form_system_theme_settings_alter(array &$form, FormStateInterface $form_state): void { $form['first_theme_settings'] = [ '#type' => 'details', '#title' => t('First theme settings'), '#open' => TRUE, ]; $form['first_theme_settings']['show_card_shadows'] = [ '#type' => 'checkbox', '#title' => t('Show drop shadows on cards'), '#default_value' => \Drupal::service(ThemeSettingsProvider::class)->getSetting('show_card_shadows') ?? TRUE, ];}This is ordinary Form API: a collapsible details wrapper and a checkbox. Two lines deserve a closer look:
- The element key,
show_card_shadows, becomes the storage key. On submit, core saves every custom form value into the theme’s configuration object,first_theme.settings, so the setting deploys, exports, and imports like any other configuration. #default_valuereads the current value through theThemeSettingsProviderservice, with?? TRUEsupplying the default while nothing is saved yet. The static\Drupal::service()call is the correct form here, because this hook is a procedural function where constructor injection does not exist.
Step 2: Read the setting the non-deprecated way
Section titled “Step 2: Read the setting the non-deprecated way”For years the way to read a theme setting was the global function theme_get_setting(). It still exists, and it is this track’s biggest landmine:
The replacement is the service you already used in step 1. Its getSetting() method takes the setting name and an optional theme name, defaulting to the active theme, and returns the value layered from the global system.theme.global defaults and the theme’s own THEME.settings configuration.
To act on the setting, read it in a preprocess function. Open the first_theme.theme file the generator created, and add the use line below its @file docblock and the function below it:
use Drupal\Core\Extension\ThemeSettingsProvider;
/** * Implements hook_preprocess_HOOK() for node templates. * * Adds the card--shadow class to teasers when the "Show drop shadows on * cards" theme setting is on. node--teaser.html.twig passes the node's * attributes to the card, so the class lands on the card wrapper. */function first_theme_preprocess_node(array &$variables): void { $show_shadows = \Drupal::service(ThemeSettingsProvider::class)->getSetting('show_card_shadows') ?? TRUE; if ($show_shadows && $variables['view_mode'] === 'teaser') { $variables['attributes']['class'][] = 'card--shadow'; }}The function adds a card--shadow class to each teaser’s attributes, and node--teaser.html.twig from part 4 passes those attributes to the card, so the class lands on the card’s article element. Theme hooks like hook_preprocess_HOOK() stay procedural in theme files; that rule from part 2 applies to preprocessing too.
Then give the class a style, at the end of components/card/card.css:
/* Added by first_theme_preprocess_node() when the theme setting is on. */.card--shadow { box-shadow: 0 2px 8px rgb(0 0 0 / 0.12);}Saving theme settings invalidates rendered output, so the change shows on the next page load.
Step 3: Turn breakpoints into responsive images
Section titled “Step 3: Turn breakpoints into responsive images”Part 3’s first_theme.breakpoints.yml declared three screen widths and 1x/2x multipliers. The Responsive Image module reads them and renders a picture element that lets the browser download only the size it needs.
- Install the module:
ddev drush pm:install responsive_image. - Go to Administer → Configuration → Media → Responsive image styles
/admin/config/media/responsive-image-styleand select Add responsive image style. (On the standard profile, installing the module pulls in two generic styles the profile ships as optional configuration, Narrow and Wide; you are building one against your own theme’s breakpoints instead.) - Enter
Article heroas the label and choose First theme under Breakpoint group. The form now lists each breakpoint and multiplier from your YAML file:first_theme.mobile,first_theme.tablet, andfirst_theme.desktop, twice each. - For each breakpoint you want to serve, pick Select a single image style. and choose an image style of an appropriate width (core ships Large, Medium, and Wide; tighter control comes from defining your own image styles, which the images and media guide covers).
- Choose a Fallback image style for browsers that ignore
picture, and save. - Apply it: go to Administer → Structure → Content types → Article → Manage display, change the Image field’s format to Responsive image, select the gear, pick
Article heroas the responsive image style, Update, then Save.
The division of labor: the theme owns the breakpoints (they describe its design), while the responsive image style and the display configuration own which image sizes map onto them.
Step 4: Ship it
Section titled “Step 4: Ship it”Look at what web/themes/custom/first_theme contains now (plus whatever you kept from the starterkit copy):
- first_theme.info.yml
- first_theme.libraries.yml
- first_theme.breakpoints.yml
- first_theme.theme
- theme-settings.php
- logo.svg
- screenshot.png
Directorycomponents/
Directorycard/
- card.component.yml
- card.twig
- card.css
Directorycss/
- base.css
Directoryjs/
- first-theme.js
Directorytemplates/
- node.html.twig
- node—teaser.html.twig
Shipping it is unglamorous on purpose:
- Commit the directory. A custom theme is code; it belongs in the project’s repository like any module.
- Export configuration. The choices that live outside the theme directory (default theme,
first_theme.settings, the responsive image style, the display settings) are configuration:ddev drush config:exportcaptures them so deployment reproduces them. Configuration management explains the workflow, and the deployment guide places it in the release process. - Check the Appearance listing. The
screenshot.pngthe generator copied is what shows on/admin/appearance; replace it with a screenshot of your actual theme before anyone else has to pick it from a list.
Troubleshooting
Section titled “Troubleshooting”The settings form does not show my checkbox
Section titled “The settings form does not show my checkbox”The file must be named theme-settings.php, in the theme root, and the function must be named exactly first_theme_form_system_theme_settings_alter. Rebuild caches after creating the file.
Deprecation notices about theme_get_setting() in the log
Section titled “Deprecation notices about theme_get_setting() in the log”Some code, often copied from older tutorials, still calls the deprecated function. Replace each call with \Drupal::service(ThemeSettingsProvider::class)->getSetting(...) as in step 2; the signature maps one to one.
“First theme” is missing from the Breakpoint group select
Section titled ““First theme” is missing from the Breakpoint group select”The breakpoint group only exists if first_theme.breakpoints.yml was discovered: rebuild caches and recheck the part 3 checkpoint (getBreakpointsByGroup('first_theme')). The group appears under the theme’s human-readable name.
The image still renders as a plain img element
Section titled “The image still renders as a plain img element”The field format is still a regular image style. Recheck the last item of step 3: the format must be Responsive image with your responsive image style selected, on the view mode you are actually looking at (the front page teaser and the full node use different displays).
Next steps
Section titled “Next steps”- Single-directory components deepens part 4: variants, overriding module components, and JavaScript inside components.
- Images and media covers image styles, the
sizesattribute, and media entities beyond this tutorial’s single field. - Configuration management turns the export you made in step 4 into a deployment habit.
Was this page helpful?
Drupal is a registered trademark of Dries Buytaert.