Starterkit and Subthemes

This content was generated with the assistance of AI and should be verified against the official Drupal documentation before being relied on in production.

This sub-section’s Drupal bibliography lists the reference material consulted while preparing these pages.

A theme in Drupal is a directory with an .info.yml file, libraries, templates and, optionally, a .theme file and Single Directory Components. Every site needs one that carries its own design, and there are only three ways to get it: start from a copy of a known-good theme (the Starterkit approach), build on top of another theme as a sub-theme, or write one from nothing. This page explains the first two, why Drupal replaced the old "inherit from a core base theme" model with generation, what is happening to the themes that ship in core as Drupal 12 approaches, and how to keep a generated theme healthy as core moves on.

This page was checked against the official documentation on 2026-10-30. It is written against Drupal 11.4.x, Drush 13 or 14 and PHP 8.3 or later (see Requirements and hosting for the full baseline). The examples use the fictitious Meridian Museum site from the sub-section index and create its theme, meridian. Drupal 10 reaches end of life on 2026-12-09, so new sites should start on Drupal 11. Drupal 12.0.0 is planned for the week of 2026-12-07 and is not yet released; statements about Drupal 12 below describe scheduled changes recorded in change records and project pages, and the version numbers should be re-checked at the final release. The wider theming picture is in Theming overview and the render pipeline; the keys of the .info.yml file are in Theme info files and regions.

Who does what, and why this matters

  • The site builder or administrator picks the default and administration themes at /admin/appearance and installs contributed themes with Composer.

  • The themer generates the site’s theme, decides whether it stands alone or builds on another theme, and maintains it across core releases.

  • The developer of a distribution or of a design system publishes a starterkit so that other teams can generate themes from it.

The decision made on day one (a standalone generated theme, a sub-theme of a contributed theme, or a sub-theme of a core theme) determines how much upstream change reaches the site automatically and how much the team must review by hand. In Drupal 12 that decision also decides whether the site still depends on three themes that are leaving core.

Why Classy and Stable went away

Before Drupal 10, the usual recipe for a theme was one line in its info file: base theme: classy (or stable). Both were core themes, and a sub-theme inherited their templates, libraries and preprocess functions. The arrangement had two problems.

  • Core could not fix the base themes. The point of Stable was to be a frozen copy of core’s markup, so that a change in core’s templates would not break the thousands of themes that inherited from it. Stable could therefore only fall further behind core, and Classy sat on top of it adding a large number of CSS classes that themes then depended on. Any improvement to markup, accessibility or performance in core had to either wait for a major version or be added as yet another opt-in.

  • Sub-themes inherited things they did not want. A theme that only wanted a few Classy classes still inherited all of its CSS and libraries and had to override its way out of what it did not need.

Drupal 10.0 replaced the pattern with a theme generator. The change record "Classy removed and replaced with Starterkit theme generator" states that Starterkit is the recommended way to create new themes instead of setting a base theme to a core theme such as Classy, that it applies to new themes only, and that no tool converts an existing theme. Existing themes that used Classy can keep working through the contributed Classy project. Its migration steps for a theme that wants to drop Classy are: change the base theme to Stable, copy the preprocess functions from classy.theme into the theme’s own .theme file under the theme’s prefix, copy the Classy templates the theme does not override into its templates directory, and copy the libraries it uses (with their JavaScript, CSS and images) into its own info and library files. The record’s comments suggest using Stable 9 as the base in that first step now that Stable itself has gone from core, which is the situation described in the next sections.

Older books such as Modernizing Drupal 10 Theme Development (2023) and Drupal 10 Development Cookbook (2023) cover the Starterkit generator because it was new in 10.x, but they were written before the changes that follow: they predate the vendor/bin/dr command (11.4), the deprecation of Stable 9 and the removal of Olivero and Claro from core (Drupal 12). Tutorials from the Drupal 8 and 9 era that say "create a sub-theme of Classy" or "set base theme: stable`" describe a model that no longer exists in core: Classy and Stable are not in Drupal 10 or later, and the `stable9 base theme is now contributed (see below).

The idea in one picture: a base-theme sub-theme stays linked to its parent for the life of the site, and so is changed by every parent update, while a generated theme is copied once and owned by the site from then on.

flowchart LR subgraph Old[Before Drupal 10: inheritance] C[Classy or Stable in core] -- base theme --> T1[Site theme] C -. markup frozen, cannot improve .-> T1 end subgraph New[Drupal 10 and later: generation] S[starterkit_theme in core] -- generate-theme copies once --> T2[Site theme meridian] T2 --> Own[Templates, CSS and libraries owned by the site] S -. later core changes: review with git diff .-> T2 end

The Starterkit theme and how generation works

Core ships a hidden theme named starterkit_theme at core/themes/starterkit_theme. It is hidden, so it never appears at /admin/appearance, and its info file sets 'base theme': false, meaning that it declares no parent. It contains default templates (for example block templates), a set of small component stylesheets under css/components, libraries that attach them, and libraries-extend entries that add the theme’s styling to core’s user, dropbutton, dialog, file, item-list, progress and tablesort libraries. The official documentation describes the result as a theme "based on latest default markup".

Generating a theme does not create a link to starterkit_theme. The command copies the starterkit directory into a new directory, replaces the starterkit’s machine name in file names and in file contents with the new theme’s machine name, adds a generator key to the new info file recording the source and its version, and writes a README.md. After that step the new theme is an ordinary standalone theme that you own. This is the whole point: later changes to core’s starterkit never alter the generated theme, so nothing you build on it can be broken by a Drupal update, and the price is that improvements in core reach your theme only when you copy them.

Generating the meridian theme

The command is run from the project root of a Composer-based site. Drupal 11.4 introduced the dr command (the change record "dr - Drupal CLI capable of running commands from modules"); Composer installs it as vendor/bin/dr. The older entry point, core/scripts/drupal, is deprecated from 11.4.0 and keeps working with a deprecation. The dr command and its commands are described in that change record as internal and experimental, so check the usage output of your version.

# Drupal 11.4 and later, from the project root (with DDEV, prefix with "ddev exec")
vendor/bin/dr generate-theme meridian \
  --name "Meridian Museum" \
  --description "Theme for the Meridian Museum website" \
  --path themes/custom

# Drupal 11.3 and earlier 11.x, 10.x: the original entry point
# (with a web root, the script lives at web/core/scripts/drupal)
php core/scripts/drupal generate-theme meridian --path themes/custom

The options are:

Argument or option Meaning Default

machine_name (argument)

The machine name of the new theme: lowercase letters, digits and underscores, starting with a letter. It becomes the directory name and the prefix of every file, library, template suggestion and preprocess function.

none; the command prompts for it

--name

The human-readable name shown at /admin/appearance.

derived from the machine name

--description

The description in the info file.

empty

--path

The directory in which the theme directory is created, relative to the web root.

themes

--starterkit

The machine name of the theme to generate from. Any installed theme that has a *.starterkit.yml file qualifies.

starterkit_theme

-n, --no-interaction

Do not prompt.

prompts

The command also accepts the usual verbosity and quiet switches. Run vendor/bin/dr generate-theme --help for the exact list in your version. Two practical rules follow from the table. First, choose the machine name carefully: it is baked into file names and function names, and there is no rename command. Use a name that no module, theme or profile already has. Second, put the theme in themes/custom rather than the default themes directory, so that contributed themes (which Composer installs into themes/contrib) and your own code are separated, which also makes a .gitignore or a code-review rule simple to write.

Enable the generated theme and make it the default with Drush:

ddev drush theme:install meridian
ddev drush config:set system.theme default meridian -y
ddev drush cache:rebuild

The generated info file looks approximately like the following. The generator line is added by the command, and its value records the core version the starterkit came from.

name: Meridian Museum
type: theme
description: Theme for the Meridian Museum website
'base theme': false
core_version_requirement: ^11.3
generator: 'starterkit_theme:11.4.0'
libraries:
  - meridian/base
  - meridian/messages
  - core/normalize
libraries-extend:
  user/drupal.user:
    - meridian/user
  core/drupal.dropbutton:
    - meridian/dropbutton
  core/drupal.dialog:
    - meridian/dialog
regions:
  header: Header
  content: Content
  sidebar_first: 'Sidebar first'
  footer: Footer

The exact content depends on the core version that generated it. A generated theme also carries meridian.libraries.yml with base, messages and component libraries, copies of the default templates and stylesheets under templates and css, and its own README.md. Because the file is yours, edit core_version_requirement to the range you intend to support (for example ^11.3 || ^12) and review it whenever you move across a major version.

What the generator does is shown below.

flowchart TD A[vendor/bin/dr generate-theme meridian] --> B[Locate the starterkit theme: starterkit_theme or the --starterkit value] B --> C[Read STARTERKIT.starterkit.yml: ignore, no_edit, no_rename, info] C --> D[Copy the files except the ignored paths] D --> E[Replace the starterkit machine name in file names and contents] E --> F[Merge the info keys and write the generator key] F --> G[Call StarterKit postProcess if the class exists] G --> H[Theme directory under the --path value, owned by the site]

What is in a generated theme

  • meridian.info.yml: no parent, global libraries and libraries-extend entries, regions.

  • meridian.libraries.yml: the base library that aggregates the component stylesheets, plus messages, dialog, dropbutton and other small libraries named in the info file.

  • css/components/: one stylesheet per core component (action links, breadcrumb, button, form, menu, pager, tabs and so on). These are plain CSS that you edit; they use the cascade layers and CSS custom properties that core’s markup expects.

  • templates/: the default markup for the elements the starterkit overrides, for example block, field, form and navigation templates.

  • README.md, logo.svg, screenshot.png: the generated README mentions the starterkit documentation.

Treat these files as a starting point to be edited, not as a library to leave alone. A common beginner mistake is to leave the generated stylesheets untouched and put all new CSS in a single new file, which makes the later comparison with core harder, because the copied files no longer match upstream and it is no longer clear which differences are intentional. Keep your own CSS in separate component files, and change the copied files only where you must.

Writing your own starterkit

Any theme can be used as a starterkit, not only the one in core, as long as it has a <machine_name>.starterkit.yml file. This is how design systems and distributions publish a theme that their sites can generate from; the Bootstrap 5 theme project is the example named by the official documentation. For the Meridian Museum, the web agency that builds the museum’s sites keeps a meridian_starter theme and generates one theme per site from it. The configuration file lists what the generator should do.

# themes/custom/meridian_starter/meridian_starter.starterkit.yml
ignore:
  - '/src/StarterKit.php'
  - '/meridian_starter.starterkit.yml'
  - '/docs'
no_edit:
  - '/images/*.svg'
no_rename:
  - '/css/vendor'
info:
  version: 1.0.0
  hidden: null
  libraries-override: null

The four keys are:

  • ignore: files and directories that are not copied into the generated theme. The starterkit’s own configuration file and its post-processing class are always listed here.

  • no_edit: files whose contents are not rewritten when the machine name is replaced.

  • no_rename: files whose names are not changed.

  • info: extra properties merged into the generated .info.yml. Setting a property to null removes it, which is how a starterkit that is itself hidden (hidden: true) makes sure that generated themes are visible.

Post-processing code goes in a class \Drupal\meridian_starter\StarterKit that implements \Drupal\Core\Theme\StarterKitInterface. Core’s own starterkit uses it only to write the generated README.md.

<?php

namespace Drupal\meridian_starter;

use Drupal\Core\Theme\StarterKitInterface;

/**
 * Post-processing for themes generated from meridian_starter.
 */
final class StarterKit implements StarterKitInterface {

  /**
   * {@inheritdoc}
   */
  public static function postProcess(string $working_dir, string $machine_name, string $theme_name): void {
    $readme = "$working_dir/README.md";
    $text = "$theme_name, generated from meridian_starter. See docs for the museum design tokens.\n";
    file_put_contents($readme, $text);
  }

}

Generate a site theme from the custom starterkit with the --starterkit option. The starterkit theme must be present in the codebase (it can be hidden or not installed, but its files must be there):

vendor/bin/dr generate-theme meridian_kiosk \
  --name "Meridian Kiosk" \
  --starterkit meridian_starter \
  --path themes/custom

The generated theme records generator: meridian_starter:1.0.0 (the version from the starterkit’s info key), which is how its owners later find out which version to compare against. Give a custom starterkit an explicit version and bump it with every change; without one the comparison step in the update section below has nothing to refer to.

Pitfalls specific to custom starterkits:

  • If generation stops with an error part-way, check that the target directory does not contain leftovers before running the command again.

  • Never ship a starterkit that contains secrets, local configuration or private keys. Everything not listed under ignore is copied into every generated theme, and then into each site’s repository.

Sub-theming a core or contrib theme

A sub-theme declares a parent with the base theme key in its info file (the key has a space, not an underscore) and inherits the parent’s resources, so that it overrides only what it changes. The parent’s files must be present in the codebase, and installing a sub-theme whose parent is missing fails.

The official guide "Creating sub-themes" documents what is, and is not, inherited. Keep these rules in mind.

  • Templates and libraries are inherited. A template in the parent is used unless the sub-theme has one with the same name (or a more specific suggestion). Libraries declared by the parent stay attached.

  • Regions are not inherited. Copy the parent’s regions into your info file. A mismatch between the sub-theme’s regions and the parent’s makes block placement unpredictable.

  • Block placement is inherited only in a narrow case: when the sub-theme has no block configuration of its own and is the current default theme. Blocks without a matching region go to the default region.

  • Block templates are not inherited automatically. Block templates are named after the theme that places the block, so a sub-theme’s blocks do not find the parent’s templates. The guide’s fix is a hook_theme_suggestions_block_alter() implementation in the sub-theme’s .theme file that adds back the parent’s prefix.

  • Configuration schema is not inherited. Extend the parent’s theme-settings schema type in the sub-theme’s own schema file; for a sub-sub-theme, extend the immediate parent.

  • Administration themes are special: the guide notes that an administration theme such as Claro is usually not the default theme, so block inheritance does not apply, and that the sub-theme should copy the parent’s config/optional directory with the files renamed.

  • A project: line in the info file can make /admin/reports/updates report "No available releases found". The guide suggests removing it, with the side effect of warnings on the translations page. Generated themes do not have one.

A sub-theme of the contributed Olivero theme, for a Meridian Museum visitor kiosk that wants Olivero’s responsive layout but its own colours:

# themes/custom/meridian_kiosk/meridian_kiosk.info.yml
name: Meridian Kiosk
type: theme
description: Large-type kiosk theme for the museum lobby, built on Olivero.
core_version_requirement: ^11.4 || ^12
'base theme': olivero
libraries:
  - meridian_kiosk/global
libraries-extend:
  core/drupal.dialog:
    - meridian_kiosk/dialog
regions:
  header: Header
  content: Content
  sidebar: Sidebar
  footer_top: 'Footer top'
  footer_bottom: 'Footer bottom'
# themes/custom/meridian_kiosk/meridian_kiosk.libraries.yml
global:
  css:
    theme:
      css/kiosk.css: {}
dialog:
  css:
    theme:
      css/dialog.css: {}

libraries-override (not used in the example above, because the exact asset paths belong to the parent’s current release) replaces or removes assets of a library defined by the parent or by a module, and libraries-extend adds libraries whenever another library is attached. They are the supported way to adjust inherited CSS and JavaScript, and they are described in Libraries and assets. Because they refer to the parent’s file paths, an update of the parent that renames a file silently turns an override into a no-op. After each parent update, check that overrides still do something.

The same technique works for a core theme or a sub-theme of one of your own themes (base theme: meridian); a sub-sub-theme sets the intermediate theme as its parent.

flowchart TD P[Parent theme: templates, libraries, preprocess] --> S[Sub-theme meridian_kiosk] S --> O[Own templates override same-name parent templates] S --> R[Regions: must be declared again] S --> L[libraries-override and libraries-extend adjust inherited assets] P -. changes on update reach the sub-theme .-> S

Base themes: the choices in 2026

The correct base-theme choice depends on what the site needs.

Choice What you get What to watch

Generated from starterkit_theme (no base theme)

Complete ownership. Core markup and CSS at the moment of generation. No parent to disappear.

You must review core’s starterkit changes yourself (see "Keeping a generated theme up to date").

Generated from a custom or contrib starterkit

A tailored starting point (design tokens, build tooling, components) with no link to the starterkit afterwards.

The starterkit author must publish versions so that diffs are possible.

Sub-theme of a contributed theme (for example Olivero 2.x, Bootstrap 5, Radix)

Updates from the parent arrive through Composer, and you override only what differs.

Parent updates can change markup or file names under you, regions and block templates are not inherited, and the parent’s Composer constraints become your constraints.

Sub-theme of Olivero or Claro in core

Works on Drupal 10 and 11.

Both are deprecated in 11.5.0 and removed from core in Drupal 12.0.0; the site must require the contributed project before it can move to 12.

Sub-theme of Stable 9 in core

Frozen markup, as in Drupal 8 and 9 themes.

Deprecated in core (11.4) and moved to a contributed project; see below.

Sub-theme of Classy or Stable

Not available in core since Drupal 10.0.

Use the contributed projects only as a short bridge; generate a new theme instead.

The practical advice follows the official recommendation on the "Deprecated and obsolete modules and themes" page: start new themes from the Starterkit generator. Use a sub-theme only when you want a maintained parent to keep changing under your theme (a contributed design system, or your own agency theme shared by several sites).

Olivero, Claro and Stable 9 leave core in Drupal 12

Three of the themes that Drupal core has shipped are moving to contributed projects. The versions and dates below come from change records and project pages published in 2026, and the "deprecated in 11.5.0" lines refer to a release that is still ahead of the check date, so confirm them against the release notes when 11.5.0 and 12.0.0 are published.

Theme Core status Contributed replacement

Olivero

Core’s default front-end theme from 9.4 through 11. Deprecated in 11.5.0 and scheduled for removal in 12.0.0 (change record "Olivero theme is deprecated").

Project drupal/olivero, stable release 2.0.0 (2026-09-21), for Drupal ^11.4 or ^12. Install with composer require 'drupal/olivero:^2.0'.

Claro

The administration theme. Deprecated in 11.5.0 and scheduled for removal in 12.0.0 (change record "Claro theme is deprecated"). The recommendation is to plan a move to the Default Admin theme that core adds, which is derived from the Gin administration theme.

Project drupal/claro, stable release 3.0.0 (2026-09-19), for Drupal ^11.5 or ^12; its 3.0.x branch is a clone of the core theme and 4.0.x is for future development. Install with composer require 'drupal/claro:^3.0'.

Stable 9

A frozen copy of the Drupal 9 markup, used as a base theme by themes that were migrated from Classy or Stable. Deprecated in core (the deprecation issue is tagged for the 11.4.0 release notes) and removed in Drupal 12; the status report warns about deprecated themes.

Project drupal/stable9, stable release 1.0.2 (2026-10-16), for Drupal ^11.3 or ^12. Install with composer require 'drupal/stable9:^1.0'.

Two points about these sources are worth knowing. First, the official recommendation for Stable 9 on the deprecation page is to start new themes from the Starterkit generator; the contributed project exists so that existing sub-themes keep working. Second, the deprecation page does not give a removal version for Stable 9, while the Drupal 12 notes in the release planning list it with Olivero and Claro; treat the removal as scheduled for 12.0.0 and re-check it at the release.

What this means for the Meridian site

The museum site has a front-end theme (meridian, generated from the starterkit, with no base theme) and an administration theme (Claro today). Only the second one changes in Drupal 12.

  • meridian has 'base theme': false, so it does not depend on Olivero, Claro or Stable 9 and needs no change.

  • The editors' administration theme must be chosen at /admin/appearance. Before the upgrade, either keep Claro by requiring drupal/claro:^3.0 or switch to the Default Admin theme. On Drupal 12 the Default Admin theme, based on Gin, becomes the administration theme.

  • The kiosk sub-theme meridian_kiosk has 'base theme': olivero. Before upgrading to Drupal 12, require the contributed project; Composer can add it while the site is still on Drupal 11, because the contributed Olivero 2.0.0 works on 11.4 as well (Drupal 11 keeps using the copy in core until the major upgrade).

{
    "require": {
        "drupal/core-recommended": "^11.4",
        "drupal/olivero": "^2.0",
        "drupal/claro": "^3.0"
    }
}

The same change needs to be remembered when moving a site that has stored theme configuration: block placements and theme settings are keyed by the theme’s machine name, so installing the contributed project under the same machine name keeps them. A site that switches administration themes starts with that theme’s own block placement and settings.

To find which installed themes depend on a deprecated one before upgrading, look at /admin/reports/status (the deprecated-themes warning), at the base theme keys, and run a scan with Upgrade Status:

ddev composer require --dev drupal/upgrade_status
ddev drush en upgrade_status -y
ddev drush upgrade_status:analyze meridian meridian_kiosk
grep -rn "base theme" web/themes/custom --include='*.info.yml'

Preparation for the full Drupal 12 upgrade is covered in Preparing for Drupal 12, and the list of everything that left core is in Removed core modules and replacements.

Migrating a Classy or Stable theme

A theme from Drupal 8 or 9 that has base theme: classy or stable has three options on Drupal 10 and 11.

  1. Bridge with contributed projects: require drupal/stable9 (or drupal/classy) and keep the theme working while planning a rewrite. This is the cheapest and the shortest-lived option.

  2. Detach from the base theme: follow the Classy change record’s steps (copy templates, preprocess functions and libraries into the theme and remove the base theme key) so the theme stands alone, as a generated theme does.

  3. Regenerate: generate a new theme from the starterkit and port the design (CSS, templates and components) onto it, then retire the old theme. For a theme with a lot of custom markup this is often cleaner than detaching.

# Option 1: bridge, for a theme that still has "base theme: stable9"
ddev composer require 'drupal/stable9:^1.0'
ddev drush cache:rebuild

# Option 3: regenerate, then port the design
ddev exec vendor/bin/dr generate-theme meridian_next --name "Meridian Museum" --path themes/custom

Keeping a generated theme up to date

A generated theme never updates itself. The generator key in its info file is the pointer for catching up. The official documentation describes a manual procedure.

  1. Read generator in meridian.info.yml; for example starterkit_theme:11.3.3.

  2. In a clone of the Drupal core repository, diff the starterkit between that version and the one you now run:

    git clone https://git.drupalcode.org/project/drupal.git
    cd drupal
    git diff '11.3.3' '11.4.0' -- core/themes/starterkit_theme
    git log --oneline '11.3.3'..'11.4.0' -- core/themes/starterkit_theme
  3. Each commit message begins with "Issue #NNNNNN by …​". Open https://drupal.org/node/NNNNNN to read why the change was made and decide whether it applies to your theme.

  4. Apply the changes that matter, by hand or with git apply after renaming paths (the diff uses starterkit_theme in paths and function names, so replace it with meridian before applying a patch).

  5. Update the generator value to the version you have caught up to, so the next comparison starts from there.

For a contributed starterkit, clone its project and compare tags (for example git diff 1.0.0 1.5.0), omitting the path filter.

Hints for making this sustainable:

  • Do not wait for a major version. Compare at each minor release (June and December), when core’s markup and CSS change.

  • Keep your own CSS in files that core’s starterkit does not have, so a diff of the copied files shows only upstream changes.

  • Record the comparison in the pull request that updates Drupal, next to the output of Composer.

  • Remember that an updated parent is the other way to receive upstream changes: if the maintenance burden of comparing is too high for a small team, a sub-theme of a maintained contributed theme trades that burden for the risks listed in the base-theme table above.

Security, errors and pitfalls

Security.

  • A theme is code that runs on every request: its .theme file, preprocess functions and templates execute with full privileges. A contributed theme (or contributed starterkit) deserves the same review as a module, so pin it in composer.lock and follow its security advisories. The stable releases of Olivero 2.0.0, Claro 3.0.0 and Stable 9 1.0.2 are covered by the Drupal security advisory policy.

  • Twig autoescapes output. A template copied from an older theme that uses |raw, or a preprocess function that marks text safe, on content from users is a cross-site scripting risk; remove them when you port the code.

  • Do not place secrets (API keys, analytics tokens) in theme files or theme settings that are exported to configuration. Use environment variables read in settings.php and expose only the value the page needs.

  • A generated theme is committed to the project’s repository. Do not generate it into an ignored path, and do not commit node_modules.

Common errors.

  • "The theme could not be installed because base theme X is missing." The parent is not in the codebase: require its Composer package, or fix the machine name in base theme. The key is base theme with a space; base_theme is ignored.

  • The theme does not appear at /admin/appearance. A hidden theme (hidden: true) is not listed. A generated theme from a hidden starterkit stays hidden unless the starterkit’s info key sets hidden: null.

  • Blocks are missing after changing the default theme. Block placement is stored per theme, and regions are not inherited. Place the blocks again for the new theme and export the configuration (see Blocks and block layout).

  • Templates in the sub-theme are ignored. The template name or suggestion does not match, Twig debugging is off, or the cache has not been rebuilt. See Twig templates and debugging and Template suggestions and preprocess.

  • Overrides stop working after a parent update. A libraries-override key refers to a file that the parent renamed or removed.

  • Deprecation warnings about the drupal script. From 11.4, use vendor/bin/dr for generate-theme. Tools that wrap the old script (for example theme generators of some contributed base themes) may need an update; check their issue queues.

  • Name collisions. Do not give a theme the machine name of an existing module: the machine name is the prefix of functions, libraries and templates, and extension machine names should be unique across the codebase.

Preprocess and .theme code, Single Directory Components (the way to organise components in a new theme) and theme settings are covered in Template suggestions and preprocess, Single Directory Components and Theme settings and configurable themes. Front-end build tools for the generated theme are in Front-end tooling, and the accessibility and performance checks that should accompany a new theme are in Accessibility and performance in themes.