emberjs/ember.js · error

Unexpected named block at the top-level of a template

Error message

Unexpected named block at the top-level of a template

What it means

A top-level template (program) may only contain regular content; named blocks (`<:name>`) are only valid inside another element/component invocation as yielded blocks. TemplateChildren.assertTemplate throws when named blocks appear at the program's top level.

Source

Thrown at packages/@glimmer/syntax/lib/v2/normalize.ts:903

          case 'HtmlComment':
            return false;
          case 'HtmlText':
            return !/^\s*$/u.test(c.chars);
          default:
            return true;
        }
      }).length
    );
    this.nonBlockChildren = children.filter(
      (c): c is ASTv2.ContentNode => !(c instanceof ASTv2.NamedBlock)
    );
  }
}

class TemplateChildren extends Children {
  assertTemplate(table: ProgramSymbolTable): ASTv2.Template {
    if (isPresentArray(this.namedBlocks)) {
      throw generateSyntaxError(`Unexpected named block at the top-level of a template`, this.loc);
    }

    return this.block.builder.template(table, this.nonBlockChildren, this.block.loc(this.loc));
  }
}

class BlockChildren extends Children {
  assertBlock(table: BlockSymbolTable): ASTv2.Block {
    if (isPresentArray(this.namedBlocks)) {
      throw generateSyntaxError(`Unexpected named block nested in a normal block`, this.loc);
    }

    return this.block.builder.block(table, this.nonBlockChildren, this.loc);
  }
}

class ElementChildren extends Children {
  constructor(

View on GitHub (pinned to 26f97246a8)

Solutions

  1. Move the named block inside the element/component that yields it, e.g. `<Modal><:header>...</:header></Modal>`.
  2. If the content should be at the top level, remove the `<:name>` wrapper and keep plain content.
  3. If yielding multiple sections, use `{{yield to="name"}}` with `{{#if (has-block "name")}}` in the component instead of top-level named blocks.

Example fix

// before
<:header>Title</:header>

// after
<Card>
  <:header>Title</:header>
  <:body>Content</:body>
</Card>
Defensive patterns

Strategy: validation

Validate before calling

// Template roots must not contain named blocks.
function assertNoTopLevelNamedBlocks(templateTokens) {
  if (templateTokens.some(t => t.type === 'named-block')) {
    throw new Error('Named blocks are not allowed at template top level');
  }
}

Prevention

When it happens

Trigger: Writing `<:header>...</:header>` at the top level of a template file rather than nested inside a component tag.

Common situations: Copy-pasting named block syntax out of a component invocation; misunderstanding that `<:yield>`-style blocks belong to elements, not the template root; refactoring a component call away and leaving its named blocks behind.

Related errors


AI-assisted analysis of emberjs/ember.js@26f97246a8 (2026-09-01). Data as JSON: /api/errors/7365ab8bb5bdf5ad. Report an issue: GitHub.