Ember Template Colocation: One Folder Per Component
Keep a Glimmer component's class, template, tests, and styles in one directory to cut navigation and make ownership clear.
05 Jul 2026, 12:16 UTC

You receive a bug report: PrimaryButton renders a solid blue background instead of the outlined variant. The component class is at app/components/primary-button.js. Its template is at app/templates/components/primary-button.hbs. The integration test lives in tests/integration/components/primary-button-test.js, and the styles are in app/styles/components/_primary-button.scss. To make a one-line change, you jump across three directory trees and hope the names match.
That hunting cost is the problem Ember template colocation addresses. In Ember Octane and later, the convention places a Glimmer component's JavaScript class, Handlebars template, tests, and styles in a single folder under app/components with matching base names. The useful takeaway is simple: one folder equals one component, which makes reviews and onboarding faster.
How colocated templates resolve
Template colocation pairs a component class with its template in the same directory, typically app/components/foo.js and app/components/foo.hbs. A Glimmer component is an ES class, often using @tracked for reactive state, and it is part of Ember's Octane programming model. The term resolver refers to Ember CLI's build-time logic that connects a component class to its template.
When you import a colocated component, the resolver looks for an .hbs file with the same base name in the same folder. You do not import the template explicitly, and no manual registration is required. The file system itself becomes the mapping between logic and markup.
Concrete component layout
Here is a realistic layout for a user-badge component:
app/components/user-badge/
user-badge.js
user-badge.hbs
user-badge.test.js
user-badge.css
user-badge.js defines the class. user-badge.hbs contains the markup. user-badge.test.js holds the integration test. user-badge.css holds component-scoped styles. Because the base names match and the files share a folder, Ember's default resolver pairs the class and template automatically.
To generate this layout, run the Ember CLI blueprint from the repository root, where package.json lives:
ember generate component user-badge
No elevated permissions are required. The placeholder user-badge is the component name in kebab-case. After generation, verify that app/components/user-badge contains both user-badge.js and user-badge.hbs. Then open an integration test and confirm it imports the component from the colocated path without an explicit template import.
Risks include moving files without updating imports, which breaks resolution, and custom resolvers or non-standard blueprints that change default discovery. If your app uses a custom resolver, confirm it still honors same-folder lookup before adopting colocation.
Migrating from classic or pod structure
Older Ember apps often use classic components or the pod structure, where templates live under app/templates/components and classes under app/components. Moving to colocation is primarily a mechanical file move with limited runtime behavior change, but it requires updating import paths, test imports, and any custom resolver configuration.
Colocation does not enforce organization by itself. Teams can still scatter concerns if naming conventions and folder boundaries are inconsistent. It also assumes a standard Ember CLI resolver; custom build pipelines may alter file discovery.
A practical way to check the result is to inspect app/components for folders containing a .js and .hbs with matching base names, then run ember test from the repo root and confirm component tests pass without template import errors. If you perform the move, use git to track the file changes so you can revert the directory restructuring if tests fail.
Default to colocation for new components
Use colocation as the default for new Glimmer components. Migrate existing components when you touch them for a feature or bug fix, rather than scheduling a separate migration. The payoff is fewer directory jumps during code review and a clearer mental model for new contributors.
Verify resolver behavior in your specific app setup before standardizing the convention, especially if you use custom blueprints or a non-standard build pipeline.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.