Dynamic Attributes
Control HTML attributes dynamically using Spacebars expressions.
Basic Dynamic Attributes
Dynamic Classes
Conditional Classes
Class Helper
ts
Template.registerHelper('classNames', function (...args) {
const options = args.pop();
const classes = [...args];
// Add conditional classes from hash
Object.entries(options.hash).forEach(([className, condition]) => {
if (condition) classes.push(className);
});
return classes.filter(Boolean).join(' ');
});Dynamic Styles
Inline Styles
Style Helper
ts
Template.registerHelper('style', function (options) {
return Object.entries(options.hash)
.filter(([, value]) => value != null && value !== false)
.map(([prop, value]) => {
// Convert camelCase to kebab-case
const cssProp = prop.replace(/([A-Z])/g, '-$1').toLowerCase();
return `${cssProp}: ${value}`;
})
.join('; ');
});Boolean Attributes
HTML boolean attributes are present or absent — not true/false:
Attribute Spread
Return an object from a helper to set multiple attributes at once:
ts
Template.myInput.helpers({
attrs() {
return {
type: this.type || 'text',
class: `form-input ${this.size || 'md'}`,
placeholder: this.placeholder || '',
'aria-label': this.label,
'data-field': this.fieldName,
...(this.required ? { required: '' } : {}),
...(this.disabled ? { disabled: '' } : {}),
...(this.maxLength ? { maxlength: String(this.maxLength) } : {}),
};
},
});This renders all the key-value pairs as HTML attributes.
Data Attributes
Access in event handlers:
ts
Template.itemList.events({
'click .item'(event) {
const id = event.currentTarget.dataset.id;
const type = event.currentTarget.dataset.type;
Router.go(`/${type}/${id}`);
},
'click .action'(event) {
const { action, target } = event.currentTarget.dataset;
performAction(action, target);
},
});ARIA Attributes
Build accessible components:
SVG Attributes
Blaze handles SVG namespacing automatically:
ts
Template.progressRing.helpers({
half() {
return this.size / 2;
},
radius() {
return (this.size - this.strokeWidth) / 2;
},
circumference() {
const r = (this.size - this.strokeWidth) / 2;
return 2 * Math.PI * r;
},
offset() {
const r = (this.size - this.strokeWidth) / 2;
const c = 2 * Math.PI * r;
return c - (this.percentage / 100) * c;
},
});Complete Example: Dynamic Form Builder
ts
Template.dynamicForm.onCreated(function () {
this.values = new ReactiveDict();
this.errors = new ReactiveDict();
this.isSubmitting = new ReactiveVar(false);
// Set initial values
this.data.fields.forEach((field) => {
if (field.defaultValue != null) {
this.values.set(field.name, field.defaultValue);
}
});
});
Template.dynamicForm.helpers({
fieldValue(name) {
return Template.instance().values.get(name);
},
fieldError(name) {
return Template.instance().errors.get(name);
},
isSubmitting() {
return Template.instance().isSubmitting.get();
},
});
Template.dynamicForm.events({
'input .form-input, change .form-input'(event, instance) {
const { name, value, type, checked } = event.target;
instance.values.set(name, type === 'checkbox' ? checked : value);
instance.errors.set(name, null); // Clear error on change
},
'submit form'(event, instance) {
event.preventDefault();
// Validate and submit...
},
'click .reset-btn'(event, instance) {
instance.data.fields.forEach((field) => {
instance.values.set(field.name, field.defaultValue || '');
instance.errors.set(field.name, null);
});
},
});