Build your first theme 1: start from the starterkit
This is part 1 of a six-part tutorial that builds a complete Drupal 11 theme from the command line up: templates, CSS, a single-directory component, JavaScript, and theme settings. In this part you generate the theme and make Drupal render every page through it.
What you’ll build
Section titled “What you’ll build”A standalone theme named First theme (machine name first_theme), generated from Drupal core’s starterkit. By the end of this part:
- the theme exists in
web/themes/custom/first_themewith markup you own outright, - its
first_theme.info.ymldeclares your own regions, libraries, and logo, - it is installed and set as the default theme, so every visitor-facing page renders through it.
Later parts add Twig template overrides (part 2), CSS and breakpoints (part 3), a card component (part 4), a JavaScript behavior (part 5), and theme settings plus responsive images (part 6).
Theming means owning files in your own theme, generated by the starterkit, not editing core templates. Each tutorial part adds its piece to the first_theme directory: part 1 generates the info file, part 2 adds Twig template overrides, part 3 asset libraries and CSS, part 4 a single-directory component, part 5 a JavaScript behavior, part 6 theme settings and responsive image config.
Prerequisites
Section titled “Prerequisites”- A working local environment per Set up your environment.
- A Drupal 11 site installed with the standard profile per Install Drupal, with some article content to look at.
- Drush and the PHP CLI, per Essential tools.
- An administrator account.
Commands below run from the project root (the directory with composer.json), with the Drupal docroot in web/. They run inside the DDEV container: ddev drush for Drush, and ddev exec for anything else.
Step 1: Generate the theme
Section titled “Step 1: Generate the theme”Drupal core ships a console command, GenerateTheme, that creates a new theme by copying core’s hidden starterkit_theme and renaming everything in it to your machine name:
ddev exec vendor/bin/dr generate-theme first_theme \ --name "First theme" --path themes/customThe command resolves --path against the Drupal root, so this creates web/themes/custom/first_theme no matter where you run it from. It prints:
Theme generated successfully to themes/custom/first_themeAdded in 11.4 vendor/bin/dr is core’s CLI entry point. On Drupal 11.3 and earlier, run the same command through ddev exec php web/core/scripts/drupal generate-theme ... instead; from 11.4 on that script is a deprecated alias for dr and is removed in Drupal 13 (see the change record).
Why copy instead of inherit? The generated first_theme.info.yml contains 'base theme': false, which makes this a standalone theme: Drupal never falls back to another theme’s templates or CSS at runtime. The copy is stamped with a generator key recording which starterkit version it was forked from, so you can diff against later core releases on your own schedule. The alternative, subtheming a live base theme, means core or contrib markup changes land in your site the moment you update, whether you reviewed them or not.
Step 2: Install it and set it as the default
Section titled “Step 2: Install it and set it as the default”Drupal discovered the new theme as soon as the directory appeared; now install it and make it the default.
- Go to Administer → Appearance
/admin/appearance. - Find First theme under Uninstalled themes and select Install and set as default.
ddev drush theme:install first_themeddev drush config:set system.theme default first_themeStep 3: Make the info file yours
Section titled “Step 3: Make the info file yours”The .info.yml file is the theme’s manifest: Drupal reads it to learn what the theme is called, which assets to load on every page, and which regions it offers to the block system. Replace the generated first_theme.info.yml with the version this tutorial builds toward:
name: First themetype: themedescription: 'A standalone (starterkit-style) theme built in the "Build your first theme" tutorial.'package: Documentation examplescore_version_requirement: ^11# Standalone themes own all of their markup and CSS. Generate one with:# php core/scripts/drupal generate-theme first_theme'base theme': falselogo: logo.svg
libraries: # Attached on every page. Defined in first_theme.libraries.yml. - first_theme/global - core/normalize
regions: header: Header primary_menu: 'Primary menu' breadcrumb: Breadcrumb highlighted: Highlighted content: Content sidebar: Sidebar footer: FooterWalk through the keys that do the work:
type: themeandcore_version_requirement: ^11tell the extension system what this is and where it runs. They are required.'base theme': falsedeclares the theme standalone. The key is quoted because it contains a space, and it is required for every theme: Drupal throws a “Missing required key” exception for a theme that declares neither a base theme norfalse.logo: logo.svgpoints at the logo file, relative to the theme directory.logo.svgis also the path Drupal assumes when the key is absent, so this line is documentation as much as configuration; the generator copied a placeholderlogo.svgfor you.libraries:lists asset libraries attached to every page the theme renders.core/normalizeis core’s copy of normalize.css.first_theme/globaldoes not exist yet: you define it in part 3. Drupal skips library references it cannot resolve rather than failing, so the site keeps working, but with almost no CSS. That bare look is the honest starting point for the styling work ahead.regions:declares the named areas the block system can place blocks into. Declaring this key replaces the default region list (sidebar_first,secondary_menu,help, and friends) entirely. Keep acontentregion: that is where Drupal places the main page content block.
If you plan to diff against future starterkit releases, also keep the version and generator lines the command wrote; they are metadata and change nothing at runtime.
Rebuild caches so Drupal re-reads the file:
ddev drush cache:rebuildStep 4: Print your regions in the page template
Section titled “Step 4: Print your regions in the page template”A region only appears on the page if the page template prints it. The generated templates/layout/page.html.twig still prints the starterkit’s region set (page.sidebar_first, page.sidebar_second, page.secondary_menu, page.help), and those no longer exist. Open it and make it match your regions: block: delete the markup for regions you removed, and print the one you added:
{% if page.sidebar %} <aside class="layout-sidebar">{{ page.sidebar }}</aside>{% endif %}The page.* variables are render arrays, one per declared region; printing one renders every block placed in it. The info file and the page template are two halves of one contract, and Drupal does not check them against each other: a region declared but never printed is invisible, and a page.* variable printed but never declared is always empty.
Troubleshooting
Section titled “Troubleshooting”“Theme could not be generated because the destination directory exists already”
Section titled ““Theme could not be generated because the destination directory exists already””generate-theme refuses to overwrite an existing directory. Delete the previous attempt (or pick another machine name) and run the command again.
“The source theme starterkit_theme has a development version number”
Section titled ““The source theme starterkit_theme has a development version number””You are running against a development checkout of core rather than a tagged release. The command wants to record the exact starterkit commit in the generator key and needs git for that. Install git, or generate from a tagged core release.
The theme does not appear on the Appearance page
Section titled “The theme does not appear on the Appearance page”Drupal scans themes/ inside the docroot. Check that the theme landed in web/themes/custom/first_theme (not a top-level themes/ next to composer.json) and that the info file is named exactly first_theme.info.yml. Then run ddev drush cache:rebuild.
Block layout warns that blocks were assigned to an invalid region
Section titled “Block layout warns that blocks were assigned to an invalid region”Expected after step 3: declaring regions: replaced the default region list, and the Block module disables any block sitting in a region that no longer exists. The warning names each block; reassign the ones you want to keep to one of your seven regions.
Next steps
Section titled “Next steps”- Part 2: Twig templates and suggestions overrides your first template.
- The render pipeline explains how render arrays become the HTML your theme styles.
- Asset libraries covers everything
libraries:can do beyond this tutorial.
Was this page helpful?
Drupal is a registered trademark of Dries Buytaert.