[TERMINAL · SKILLS]
> mounting /skills...
> indexing skill manifests...
> linking agents: claude · codex · gemini · cursor
> ready.
[░░░░░░░░░░░░░░░░░░░░░░░░░░░░] 0%
Skills/Ember.js
>

Ember.js

Ember.js is an opinionated JavaScript framework for large single-page web apps, built on convention over configuration: Glimmer components, tracked state, nested routing, services, Ember Data (WarpDrive) and the ember-cli build tool. Use when someone asks to "build an Ember app", "create an Ember component or route", "upgrade Ember", "use Ember Data", or "convert Ember templates to .gjs/.gts".

#javascript#ember#framework#spa#typescript
terminal-skillsv1.1.0
Works with:claude-codeopenai-codexgemini-clicursor
Source
Trust Score
87/ 100
1.72×
Impact

Validation

Score is stale (SKILL.md changed since last review)
Quality
87/ 100
Does it follow best practices?
4 PASS · 2 WEAK
Security
Passed
No known issues
Content review + injection scan
Impact
1.72×
50% → 86% agent success
Avg across 2 eval scenarios
Scored 10/2/2026 · skill v1.1.0
$
✓ Installed ember v1.1.0

Getting Started

  1. Install the skill using the command above
  2. Open your AI coding agent (Claude Code, Codex, Gemini CLI, or Cursor)
  3. Reference the skill in your prompt
  4. The AI will use the skill's capabilities automatically

Example Prompts

  • "Review the open pull requests and summarize what needs attention"
  • "Generate a changelog from the last 20 commits on the main branch"

Documentation

Overview

Ember is a batteries-included framework for ambitious web applications: file layout and naming drive behaviour, so teams share one structure. New apps (ember-cli 7.x, Ember 7.x at the time of writing, with a major release roughly every 18 months and a minor every six weeks) use:

  • Vite as the build tool (npm start runs vite, npm run build runs vite build); the older ember-cli/Broccoli build with ember serve is the legacy path.
  • Template-tag components (.gjs / .gts): the <template> block sits in the JavaScript or TypeScript file. Separate .hbs files still work in older apps.
  • Ember Data now ships as WarpDrive (@warp-drive/* packages; models import from @warp-drive/legacy/model), with the store in app/services/store.ts.
  • Octane idioms only: Glimmer components, @tracked, @action, {{on}} modifiers, this.args. Classic components, this.get(), mut and computed properties are legacy.

Instructions

Create and run an app

bash
npm install -g ember-cli        # or: npx ember-cli new ...
ember new todo-app --lang en --typescript --strict
cd todo-app
npm start                       # Vite dev server on http://localhost:4200
npm test                        # builds in development mode and runs the QUnit suite via testem
npm run build                   # production build into dist/
npm run lint:types              # type-check .gts files (ember-tsc)

--strict (template-tag format) is what the official quick start uses; drop --typescript for plain JavaScript. Generators: ember generate component todo-list, ember generate route todos, ember generate service session. In a TypeScript app ember generate model may still emit a .js file with untyped fields; rename it to .ts and add declare.

Components (template tag)

typescript
// app/components/todo-list.gts
import Component from '@glimmer/component';
import { tracked } from '@glimmer/tracking';
import { action } from '@ember/object';
import { on } from '@ember/modifier';
import { service } from '@ember/service';
import type Store from 'todo-app/services/store';
import type Todo from 'todo-app/models/todo';

interface Signature {
  Args: { title: string };
}

export default class TodoList extends Component<Signature> {
  @service declare store: Store;
  @tracked newText = '';

  get todos() {
    return this.store.peekAll('todo') as Todo[];
  }

  get remaining() {
    return this.todos.filter((t) => !t.completed).length;
  }

  @action updateText(event: Event) {
    this.newText = (event.target as HTMLInputElement).value;
  }

  @action async addTodo(event: SubmitEvent) {
    event.preventDefault();
    if (!this.newText.trim()) return;
    const todo = this.store.createRecord('todo', { text: this.newText, completed: false }) as Todo;
    await todo.save();
    this.newText = '';
  }

  <template>
    <h2>{{@title}} ({{this.remaining}} left)</h2>
    <form {{on "submit" this.addTodo}}>
      <input value={{this.newText}} {{on "input" this.updateText}} placeholder="Add todo" />
      <button type="submit">Add</button>
    </form>
    <ul>
      {{#each this.todos as |todo|}}<li>{{todo.text}}</li>{{/each}}
    </ul>
  </template>
}

In .gjs/.gts files every component, helper and modifier used in the template must be imported (on from @ember/modifier, fn from @ember/helper, other components by path). Built-ins such as {{#if}}, {{#each}} and {{yield}} need no import. A form submit handler must call event.preventDefault() itself.

Model, route and router

typescript
// app/models/todo.ts
import Model, { attr } from '@warp-drive/legacy/model';

export default class Todo extends Model {
  @attr('string') declare text: string;
  @attr('boolean') declare completed: boolean;
}
typescript
// app/routes/todos.ts
import Route from '@ember/routing/route';
import { service } from '@ember/service';
import type Store from 'todo-app/services/store';

export default class TodosRoute extends Route {
  @service declare store: Store;

  model() {
    return this.store.findAll('todo');
  }
}
typescript
// app/router.ts
Router.map(function () {
  this.route('todos');
  this.route('user', { path: '/users/:user_id' }, function () {
    this.route('posts');
  });
});
typescript
// app/templates/todos.gts
import TodoList from 'todo-app/components/todo-list';

<template><TodoList @title="Sprint 42" /></template>

The route's model() result is available as @model in the route template; pass it down as arguments. The store needs a data source for findAll/save: a legacy adapter in app/adapters/application.ts or request handlers configured in app/services/store.ts. Check the Ember Data (WarpDrive) guide for the setup that fits your API.

Examples

Example 1: Add a route with a nested page

User: "Add a /users/:id/settings page to my Ember app."

Run ember generate route user/settings, add this.route('settings') inside the user route in app/router.ts, load data in the model() hook and render it in app/templates/user/settings.gts. Result: npm start serves http://localhost:4200/users/42/settings.

Example 2: Convert a classic component

User: "Convert this classic component with this.get('count') and actions: {} to modern Ember."

Make it a @glimmer/component class, turn count into @tracked count = 0, turn each entry in actions into an @action method, replace {{action "inc"}} with {{on "click" this.inc}}, and read arguments as @title or this.args.title. Result: the component renders identically, with no this.get/this.set calls left.

Guidelines

  • Use @tracked for reactive state; assign to it, do not mutate in place when the value is an array or object (replace it, or use tracked-built-ins).
  • Load data in the route model() hook, not in component constructors.
  • Put shared state (session, notifications, feature flags) in services, injected with @service.
  • Follow file naming conventions; the resolver depends on them.
  • Upgrade one minor at a time and fix deprecation warnings before moving to the next major; deprecated APIs are removed in majors. npx ember-cli-update applies blueprint changes.
  • Keep application tests in tests/ (unit, integration, acceptance); npm test runs them.
  • Ember suits long-lived, large apps; for a small static site a lighter tool is easier.