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.twigshares 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-352—machineNamefrom the plugin ID, template found bymachineName - 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