Attribute mapping
Learn about binding values to attributes of DOM elements and how to extend the attribute mapping with great ease.
Attribute mapping is Aurelia's way of keeping template syntax concise. After an attribute pattern parses the attribute name but before a binding command emits instructions, the mapper answers two questions:
Which DOM property does this attribute target?
Should
.bindimplicitly behave like.two-wayfor this attribute?
This is the mechanism that lets you write <input value.bind="message"> and automatically get a two-way binding. By teaching the mapper about your own elements, you can bring the same ergonomics to Web Components, design systems, or DSLs.
When to extend IAttrMapper
IAttrMapperReach for the mapper when:
Bridging custom elements – Third-party components often expose camelCase properties such as
valueAsDateorformNoValidate.Designing DSLs – Attributes like
data-trackorfoo-barneed to land on specific DOM properties regardless of casing.Improving authoring ergonomics – Upgrading
progress.bindto two-way on slider-like controls keeps templates readable.
If you need to invent new attribute syntaxes ([(value)], @click, etc.), start with attribute patterns. If you need to observe DOM properties, follow up with the node observer locator.
How the mapper decides
When Aurelia encounters an attribute that does not belong to a custom element bindable, it walks through the mapper logic:
Check tag-specific mappings registered via
useMapping.Fall back to global mappings from
useGlobalMapping.If no mapping exists, camelCase the attribute name.
If the binding command is
bind, ask each predicate registered viauseTwoWaywhether the attribute should become two-way.
Your extensions only run for attributes that are not already handled by custom element bindables, so you can layer mappings without unintentionally overriding component contracts.
Registering mappings during startup
Use AppTask.creating to register mappings before Aurelia instantiates the root component:
import Aurelia, { AppTask, IAttrMapper } from 'aurelia';
Aurelia.register(
AppTask.creating(IAttrMapper, attrMapper => {
attrMapper.useMapping({
'MY-CE': { 'fizz-buzz': 'FizzBuzz' },
INPUT: { 'fizz-buzz': 'fizzbuzz' },
});
attrMapper.useGlobalMapping({
'foo-bar': 'FooBar',
});
})
);Keys inside useMapping must match the element's tagName (uppercase). The destination values must match the actual DOM property names exactly (formNoValidate, not formnovalidate).
With the mapping above in place, templates stay clean:
<input fizz-buzz.bind="userLimit" foo-bar.bind="hint" ref="input">
<my-ce fizz-buzz.bind="42" foo-bar.bind="43" ref="myCe"></my-ce>export class App {
private input!: HTMLInputElement;
private myCe!: HTMLElement & { FizzBuzz?: number; FooBar?: number };
public attached() {
console.log(this.input.fizzbuzz); // userLimit
console.log(this.myCe.FizzBuzz); // 42
}
}Enabling implicit two-way bindings
Some controls should default to two-way binding even when authors write .bind. Use useTwoWay to register a predicate (element, attrName) => boolean:
import Aurelia, { AppTask, IAttrMapper } from 'aurelia';
Aurelia.register(
AppTask.creating(IAttrMapper, attrMapper => {
attrMapper.useTwoWay((element, attrName) =>
element.tagName === 'MY-CE' && attrName === 'fizz-buzz');
})
);Predicates receive the live element, so you can inspect classes, attributes, or even dataset values before opting into two-way. Keep the logic lightweight—these predicates run for every *.bind attribute Aurelia encounters.
Troubleshooting and best practices
Uppercase tag names – Browsers expose
element.tagNamein uppercase; use the same casing inuseMapping.Avoid duplicates – Registering the same tag/attribute combination twice throws. Remove or consolidate old mappings before adding new ones.
Destination accuracy – Mistyped destination properties silently fall back to camelCase conversion. Inspect the element in devtools and read
Object.keys(element)if unsure.Predicate order matters –
useTwoWaypredicates run in registration order. Put the most specific check first.Verify manually – Toggle the DOM property in devtools. If the UI updates but Aurelia does not, revisit the observer configuration. If neither updates, revisit the mapping.
Pair with observers – Mapping alone does not teach Aurelia how to observe custom properties. Follow up with
INodeObserverLocator.useConfigso bindings know which events to listen to.
With the mapper tailored to your components, you can keep templates expressive while relying on the full power of Aurelia's binding system.
Last updated
Was this helpful?