Skip to content

Component File Structure

When to Use

  • You're creating a new component
  • You're debugging "component not found" errors
  • You need to understand automatic asset loading

Decision

Required Structure:

component-name/
├── component-name.component.yml  ← Metadata (REQUIRED)
└── component-name.twig           ← Template (REQUIRED)

Optional Files (auto-loaded if present):

component-name/
├── component-name.css            ← Styles (auto-attached as library)
├── component-name.js             ← Scripts (auto-attached as library)
├── README.md                     ← Documentation
├── thumbnail.png                 ← Preview for admin UI
└── assets/                       ← Additional assets (must reference manually)

CRITICAL: The .component.yml basename is the component's machine name, and every sibling file must match that basename — not the directory name.

WHY: ComponentPluginManager::alterDefinition() derives machineName by splitting the plugin ID ([, $machine_name] = explode(':', $definition['id'])), and the plugin ID came from basename($file, '.component.yml'). The Twig, CSS and JS are then located by machineName. The directory name is never read.

Matching directory to basename is still the right convention: it keeps the folder greppable and matches every example in core and contrib. But when you are debugging "component not found", renaming the directory will not fix it — check that the ID you are calling equals provider:{yml basename} and that the .twig shares that basename.

Core proves this with a fixture: core/modules/system/tests/themes/sdc_theme_test/components/mismatching-folder-name/ contains foo.component.yml + foo.twig, and ComponentPluginManagerTest asserts that sdc_theme_test:foo is found while sdc_theme_test:mismatching-folder-name throws ComponentNotFoundException.

Pattern

✓ CORRECT (conventional — keep doing this):
my-button/
├── my-button.component.yml
├── my-button.twig
├── my-button.css
└── my-button.js

✓ ALSO WORKS (directory name is ignored):
some-folder/
├── my-button.component.yml   ← ID is provider:my-button
└── my-button.twig

✗ BROKEN — the template basename does not match the YAML basename:
my-button/
├── my-button.component.yml
└── my_button.twig             ← template not found

Pattern: Automatic Library Generation

Each component generates a library automatically: - Format: core/components.{provider}--{component-name} - Example: core/components.my_theme--hero-banner - Includes CSS/JS files named identically to component - Loaded automatically when component renders

Common Mistakes

  • Renaming the directory to fix "component not found" — The directory name is never read; check that the ID equals provider:{yml basename} and that the .twig shares that basename.

Common Mistake: Using underscores in component names. WHY: Component names should use hyphens (kebab-case) per Drupal conventions. The provider name is left exactly as written — Component::getLibraryName() replaces only the : separator, with --, so my_theme:hero-banner becomes core/components.my_theme--hero-banner and the underscore survives.

See Also

  • Reference: /core/lib/Drupal/Core/Theme/ComponentPluginManager.php:343-352machineName from the plugin ID, template found by machineName
  • Reference: /core/tests/Drupal/KernelTests/Components/ComponentPluginManagerTest.php:29-53 — the mismatching-folder-name assertions
  • Reference: /core/themes/olivero/components/teaser/ — Reference implementation
  • Reference: /themes/contrib/radix/components/button/ — Radix button example
  • SCSS/CSS in SDCs
  • JavaScript in SDCs