A server-side HTML template engine in Dart.



  • Auto-completion and static analysis in the template.
  • Classical control-flow constructs: *if, *for, *switch
  • Conditionally add CSS classes ([]="$condition") and HTML attributes ([disabled]="$condition")
  • Automatically escapes the variables
  • Syntax highlighting (in IntelliJ-based IDE)


Write the template code

Declare a private void function tagged with a @template attribute:

import 'package:html_template/html_template.dart';

part 'main.g.dart';

void _productTemplate(Product product) {
  <img *if="${product.icon != null}" src="${product.icon}" />
  <h1 []="${product.isNew}">$product</h1>

void _pageTemplate(Product product, {List<String>? scripts}) {
  var script = '';
<html lang="${Language.current}">
    <title>${} - My site</title>
    <script *for="$script in $scripts" src="$script" async></script>

Generate the code

  • dart run build_runner watch --delete-conflicting-outputs

This generates a public function with the same arguments as the original. The generated code looks like:

// Generated
TrustedHtml productTemplate(Product product) {
  var $ = StringBuffer();

  $.write('  ');
  if (product.icon != null) {
    $.write('<img src="${TrustedHtml.escape.attribute(product.icon)}">');
  $.write('\n  ');
  $.write('<h1${template.classAttribute({'new': product.isNew})}>');
  $.write('\n  ');

  return TrustedHtml($.toString());

See the real generated code here

Use the template

Call the generated public methods to build the HTML page from your data.

void main() {
  router.get('/products/<id>', (request) async {
    var product = await database.findProduct(params(request, 'id'));

    // Create the html for the response from the Product of your database
    var html = pageTemplate(product);

    return Response.ok(html, headers: {'content-type': 'text/html'});


void _conditionExample({required bool someCondition}) async {
  <!-- Conditionally include the <h2> tag -->
  <h2 *if="$someCondition">Condition on a tag</h2>
  <!-- Include the 'disabled' attribute if the condition is true -->
  <input [disabled]="$someCondition"/>
  <!-- Add 'my-class' CSS class if the condition is true -->
  <input []="$someCondition">
    <!-- Use any Dart expression for the condition -->
  <hr *if="${(await fetchData()).isEmpty}"/>


To repeat an HTML element, use the attribute: *for="$item in $iterable".

void _simpleLoop(List<MenuItem> menu) {
  MenuItem? item;
    <li *for="${item!} in $menu">

Notice that we have to define the item variable outside of the string literal.
This is a bit unfortunate but string literals don't allow to define a variable inside them.

Alternatively, we can write the loop in Dart arround the string literals:

void _alternativeLoop(List<MenuItem> menu) {
  for (var item in menu) {


void _switchExample(Season season) {
<div *switch="$season">
  <span *case="${Season.summer}">Hot</span>
  <span *case="${Season.winter}">Cold</span>
  <div *default>Pleasant</div>

CSS Classes

void _cssClassesExample(List<Data> data, {bool showMenu = false}) {
  // Add classes based on condition
  '<li []="$showMenu" [class.enabled]="${data.isNotEmpty}">Actif</li>';

  // We can pass a Map<String, bool> to the [classes] attribute
  var myClasses = {'enabled': showMenu};
  '<a type="text" [classes]="$myClasses"></a>';

Dart code

You can use normal Dart code around the string literals to do complex things in your template. You can have has many strings literal as you want.

void _movieTemplate() async {
  '<h1>My movies</h1>';

  var page = await fetchPage();
  if (!page.isLoggedIn) {
    '<h2>Log in</h2>';
  } else {
    for (var movie in page.myMovies) {
      '<li [class.favorite]="${movie.isFavorite}">$movie</li>';

Nested template & master layout

Include another template by calling the generated function in a string interpolation:

void _myTemplate() {

void _img(String url) {
  '<img src="$url">';


  • Use <text *if="..">xx</text> tag if you want to output some text without the html element wrapper.

  • Use this comment in your dart file to workaround linter warnings

    // ignore_for_file: unnecessary_statements