Skip to content

Ember Templates — Handlebars Syntax and Rendering

DodaTech Updated 2026-06-28 4 min read

In this tutorial, you will learn about Ember Templates. We cover key concepts, practical examples, and best practices to help you master this topic.

Ember templates use Handlebars, a logic-less templating language that separates presentation from logic. Templates access data from routes and components using {{}} expressions, handle conditionals with {{if}} and {{unless}}, and iterate with {{each}}.

What You'll Learn

You will learn Handlebars syntax, template expressions, built-in helpers, conditional rendering, looping, component invocation, and template composition in Ember.

Why It Matters

Templates are what users see. Clean, readable templates make the application maintainable. Handlebars enforces logic-less templates, keeping business logic in JavaScript and presentation in templates.

Real-World Use

A data dashboard template renders charts, tables, and metrics using {{each}} for data iteration, {{if}} for conditional display, and custom helpers for date formatting and data transformation.

flowchart LR
    A[Route/Component] --> B[Context data]
    B --> C[Template]
    C --> D[{{each}} loop]
    C --> E[{{if}} conditional]
    C --> F[{{helper}}]
    C --> G[{{component}}]
    D --> H[Rendered HTML]

Expressions

Use double curly braces to output values:

{{! app/templates/profile.hbs }}
<h1>{{this.user.name}}</h1>
<p>Email: {{this.user.email}}</p>
<p>Member since: {{this.user.createdAt}}</p>
<p>Total posts: {{this.user.postCount}}</p>

Conditionals: if and unless

{{! app/templates/user-card.hbs }}
<div class="user-card">
  <h3>{{@user.name}}</h3>

  {{#if @user.isActive}}
    <span class="badge active">Active</span>
  {{else if @user.isPending}}
    <span class="badge pending">Pending</span>
  {{else}}
    <span class="badge inactive">Inactive</span>
  {{/if}}

  {{#unless @user.isBlocked}}
    <button type="button" {{on "click" @this.sendMessage}}>
      Send Message
    </button>
  {{/unless}}
</div>

Loops: each

{{! app/templates/posts.hbs }}
<h1>Posts ({{this.model.length}})</h1>

{{#each this.model as |post index|}}
  <article class="post-card">
    <span class="post-number">#{{add index 1}}</span>
    <h2>
      <LinkTo @route="post" @model={{post.id}}>
        {{post.title}}
      </LinkTo>
    </h2>
    <p class="post-meta">
      By {{post.author.name}} on {{format-date post.createdAt}}
    </p>
    <p class="post-excerpt">{{post.excerpt}}</p>
  </article>
{{else}}
  <div class="empty-state">
    <p>No posts yet.</p>
  </div>
{{/each}}

Local Variables with let

{{! app/templates/product.hbs }}
{{#let (multiply @product.price (add 1 @product.taxRate)) as |totalPrice|}}
  <div class="product-card">
    <h3>{{@product.name}}</h3>
    <p>Base price: ${{@product.price}}</p>
    <p>Tax: {{multiply @product.taxRate 100}}%</p>
    <p class="total">Total: ${{totalPrice}}</p>
  </div>
{{/let}}

Component Invocation

Use angle bracket invocation for components:

{{! app/templates/application.hbs }}
<header>
  <NavBar @session={{this.session}} />
</header>

<main>
  {{outlet}}
</main>

<footer>
  <FooterLinks />
</footer>

Element Modifiers

Use {{on}} and {{did-insert}} modifiers:

{{! app/components/click-counter.hbs }}
<button
  type="button"
  {{on "click" this.increment}}
  {{did-insert this.setup}}
>
  Clicked {{this.count}} times
</button>
// app/components/click-counter.js
import Component from '@glimmer/component';
import { tracked } from '@glimmer/tracking';
import { action } from '@ember/object';

export default class ClickCounterComponent extends Component {
  @tracked count = 0;

  @action
  increment() {
    this.count++;
  }

  @action
  setup(element) {
    console.log('Button rendered:', element);
  }
}

Built-in Helpers

Ember provides several built-in helpers:

{{! Helper examples }}
<p>Current year: {{year}}</p>
<p>Capitalized: {{capitalize @name}}</p>
<p>Joined: {{join ', ' @items}}</p>
<p>HTML safe: {{{@htmlContent}}}</p>

{{! Equality check }}
{{#if (eq @status 'active')}}
  <span>Active</span>
{{/if}}

{{! Math }}
<p>Total: {{add @base @tax}}</p>
<p>Discount: {{multiply @price 0.9}}</p>

{{! Array helpers }}
{{#each (filter-by 'isActive' @users) as |user|}}
  <li>{{user.name}}</li>
{{/each}}

Yielding Content

Components can yield content using {{yield}}:

{{! app/components/card.hbs }}
<div class="card {{if @padding 'card-padded'}}">
  <div class="card-header">
    {{@title}}
  </div>
  <div class="card-body">
    {{yield}}
  </div>
</div>
{{! Usage }}
<Card @title="User Info" @padding={{true}}>
  <p>Name: {{user.name}}</p>
  <p>Email: {{user.email}}</p>
</Card>

Template Only Components

Simpler components do not need a JavaScript file:

{{! app/components/status-badge.hbs }}
{{! No .js file needed }}
<span class="badge badge-{{@type}}">
  {{@text}}
</span>

Common Mistakes

  1. Using {{this.model.property}} instead of {{this.model.property}}. Always use this. for controller/component properties and @ for arguments.
  2. Forgetting {{else}} in each loops for empty states. Without a block, empty collections render nothing visible. Always include an empty state template.
  3. Using triple {{{}}} without sanitizing. Triple braces disable HTML escaping. Only use them with trusted content.
  4. Putting complex logic in templates. Helpers should handle transformations. Templates should only display data.
  5. Not using angle bracket invocation for components. Old {{component-name}} syntax is deprecated. Use <ComponentName />.

Practice Questions

  1. How do you output a property from a component in a template?
  2. What is the difference between {{if}} and {{unless}}?
  3. How do you render a list of items with an empty state?
  4. What is the purpose of {{yield}}?
  5. Challenge: Create a template that displays a list of products with images, prices, and stock status. Use {{each}} with empty state, {{if}} for in-stock/out-of-stock labels, {{let}} for computed price with tax, and a custom helper for currency formatting.

FAQ

What is the difference between `this.` and `@` in templates?

this. accesses properties on the current component/controller. @ accesses arguments passed to the component.

Can I use JavaScript logic in templates?

No. Handlebars is logic-less. Use helpers for transformations and keep logic in JavaScript.

How do I create a custom helper?

Generate it: ember generate helper format-date. The helper file exports a function that transforms input.

What is the curly braces vs angle bracket invocation?

Angle bracket (<Component />) is the modern syntax. Curly braces ({{component}}) is deprecated.

How do I prevent XSS in templates?

Use double {{}} which auto-escapes. Never use triple {{{}}} with untrusted content.

Mini Project

Create a product catalog template that renders a grid of product cards. Each card shows product name, price (formatted with currency helper), stock status badge, and an add-to-cart button. Use {{each}} with empty state, {{if}} for stock status, {{let}} for price with tax, and a custom currency helper. Style with co-located CSS.

What's Next

Now that you understand templates, learn Ember Components for building reusable UI. Then explore Ember Component Lifecycle for lifecycle management.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro