Components are the fundamental building blocks of Aurelia applications. A component consists of a view-model (TypeScript class) and an optional view (HTML template) that work together to create reusable UI elements.
Basic Component Structure
Every Aurelia component starts with a simple class:
exportclassMyComponent{ message ='Hello from Aurelia!';}
And its corresponding HTML template (no <template> wrapper is needed in Aurelia 2):
<h1>${message}</h1>
The ${message} syntax creates a binding between your view-model property and the template, automatically updating the UI when the property changes.
When to Create a Component?
Before creating a component, consider these guidelines:
Create a component when:
✅ You need reusable UI that appears in multiple places
✅ The UI has its own behavior and state
✅ You want to encapsulate complexity (a component should do one thing well)
✅ The UI represents a meaningful concept in your domain (e.g., <user-card>, <product-list>)
Use a custom attribute instead when:
✅ You're adding behavior to existing elements without changing structure
✅ You're creating a decorator or modifier (e.g., tooltip, draggable)
✅ Multiple behaviors can be combined on the same element
Use case: Separate data management from presentation logic.
Container (Smart) Component - Manages data and business logic:
Presenter (Dumb) Component - Pure presentation, no data fetching:
Why this works: Container components handle complexity (data, routing, state), while presenter components are simple, reusable, and easy to test. You can reuse <user-list> anywhere without worrying about data fetching.
Pattern: Composition with Slots
Use case: Create flexible container components that accept custom content.
Usage:
Why this works: Slots make components flexible without needing dozens of bindable properties. The component controls the structure while consumers control the content.
Pattern: Form Components with Two-Way Binding
Use case: Reusable form inputs that work seamlessly with parent form state.
Usage:
Why this works: Two-way binding with BindingMode.twoWay keeps the parent and child in sync automatically. Changes in either place propagate to both.
Pattern: Stateful UI Components
Use case: Components that manage their own internal state.
Why this works: The component owns its UI state (which item is expanded) while accepting data as props. This keeps the parent simple - it just provides data, not UI state.
Pattern: Event-Emitting Components
Use case: Child components that notify parents of user actions.
Usage:
Why this works: Components can communicate via callbacks (tight coupling) or events (loose coupling) depending on your needs. Use callbacks for parent-child communication, events for unrelated components.
Best Practices
Keep Components Focused
✅ Each component should have one clear responsibility
✅ If a component is doing too much, split it into smaller components
❌ Avoid "god components" that handle everything
Favor Composition Over Inheritance
✅ Use slots and component composition
✅ Build complex UIs from simple, reusable pieces
❌ Avoid deep inheritance hierarchies
Make Components Predictable
✅ Use bindable properties for inputs
✅ Use callbacks or events for outputs
✅ Document what bindables are required vs optional
❌ Don't manipulate parent state directly
Test-Friendly Components
✅ Presenter components are easy to test (just props)
export class MyComponent {
created() {
// Component instance created
}
binding() {
// Data binding starts
}
bound() {
// Data binding completed
}
attaching() {
// Before DOM attachment
}
attached() {
// Component attached to DOM
}
detaching() {
// Before DOM removal
}
unbinding() {
// Data binding being removed
}
}