Components are the building blocks of Aurelia applications. This guide covers creating, configuring, and using components effectively.
Components are the core building blocks of Aurelia applications. Each component typically consists of:
A TypeScript class (view model)
An HTML template (view)
Optional CSS styling
Component Naming
Component names must include a hyphen (e.g., user-card, nav-menu) to comply with Web Components standards. Use a consistent prefix like app- or your organization's initials for better organization.
Creating Your First Component
The simplest way to create a component is with convention-based files:
When a component imports an .html file, the bundler must deliver that file as a plain string. Otherwise tools such as Vite, Webpack, and Parcel try to parse the file as an entry point and emit errors like [vite:build-html] Unable to parse HTML; parse5 error code unexpected-character-in-unquoted-attribute-value or "template" is not exported by src/components/product-name-search.html.
Configure your bundler using the option that best matches your stack:
Vite / esbuild (default Aurelia starter), Parcel 2, Rollup + @rollup/plugin-string – append ?raw to the import so the bundler treats the file as text:
Add a matching declaration so TypeScript understands these imports (the query string can be reused for other text assets):
Webpack 5 – mark .html files as asset/source (or keep using raw-loader). After that you can import without a query parameter:
Other bundlers – use the equivalent “treat this file as a string” hook (e.g., SystemJS text plugin).
Once the bundler understands .html files as text, both npm start and npm run build can reuse the same component source without inline templates. Keep the import pattern consistent across the project so contributors immediately know which loader configuration applies.
Dependencies:
Alternative Creation Methods
Static Configuration:
Programmatic (mainly for testing):
HTML-Only Components
Create simple components with just HTML:
Usage:
Viewless Components
Components that handle DOM manipulation through third-party libraries:
Using Components
Global Registration (in main.ts):
Local Import (in templates):
Containerless Components
Render component content without wrapper tags:
Or configure inline:
Use Sparingly
Containerless components lose their wrapper element, which can complicate styling, testing, and third-party library integration.
Component Lifecycle
Components follow a predictable lifecycle. Implement only the hooks you need:
Components form the foundation of Aurelia applications. Start with simple convention-based components and add complexity as needed. The framework's flexibility allows you to adopt patterns that fit your project's requirements while maintaining clean, maintainable code.
import { bindable, customElement } from 'aurelia';
import * as nprogress from 'nprogress';
@customElement({
name: 'progress-indicator',
template: null
})
export class ProgressIndicator {
@bindable loading = false;
loadingChanged(newValue: boolean) {
newValue ? nprogress.start() : nprogress.done();
}
}
import Aurelia from 'aurelia';
import { UserCard } from './components/user-card';
Aurelia
.register(UserCard)
.app(MyApp)
.start();
<import from="./user-card"></import>
<!-- or with alias -->
<import from="./user-card" as="profile-card"></import>
<user-card user.bind="currentUser"></user-card>
<profile-card user.bind="selectedUser"></profile-card>
import { customElement, containerless } from 'aurelia';
@customElement({ name: 'list-wrapper' })
@containerless
export class ListWrapper {
// Component logic
}
@customElement({
name: 'list-wrapper',
containerless: true
})
export class ListWrapper {}
export class UserProfile {
constructor() {
// Component instantiation
}
binding() {
// Before bindings are processed
}
bound() {
// After bindings are set
}
attached() {
// Component is in the DOM
}
detaching() {
// Before removal from DOM
}
}
import { bindable, BindingMode } from 'aurelia';
export class UserCard {
@bindable user: User;
@bindable isActive: boolean = false;
@bindable({ mode: BindingMode.twoWay }) selectedId: string;
userChanged(newUser: User, oldUser: User) {
// Called when user property changes
}
}