tvOS Tutorial: Using TVML Templates

In this tvOS tutorial, you’ll learn how to use TVML templates and templating engines to make great-looking user interfaces. By Chris Belanger.

Leave a rating/review
Save for later

In this tutorial, you’ll learn how to use the plethora of TVML templates that Apple has provided to make some stunning interfaces. You’ll use these templates to build a comprehensive screen for RWDevCon 2015 videos, which will include a wide range of information about the video and display it in an appealing and recognizable manner.

Getting Started

You can download the starter project for this tutorial here.

The sample app for this tutorial is wenderTV; it lets you browse and watch video content on your Apple TV, so you can enjoy a vast quantity of knowledge – and bad jokes – from the comfort of your sofa. Right now, wenderTV is quite empty.

wenderTV includes a lot of resources you’ll use during this tutorial; we’ve also reorganized some of the code to make it easier to understand.

Build and run the wenderTV starter project from Xcode; you’ll notice the screen is completely blank. This is because there aren’t any TVML documents in wenderTV yet. Before you can turn your attention to creating some TVML documents, you have a little housekeeping to do.

Note: Although you can use Xcode to write all the code you need for a TVML-based app, it can be quite a painful experience. Xcode is good for editing Swift or Objective-C, but has only rudimentary support for XML and JavaScript. It’s definitely worth using Xcode when you need to, and then switching to a more capable editor such as Sublime Text, Atom, Visual Studio Code or MacVim to work with TVML documents and JavaScript files.

Loading Scripts

The project for wenderTV already has ResourceLoaderJS split out into its own file, so you need to discover how to import it into main.js.

TVJS provides the evaluateScripts() function which takes an array of URLs of JavaScript files and a callback. The function reads each URL in turn, attempts to parse it and adds any contained functions and object definitions to the context’s global object. Finally, it invokes the supplied callback with a Boolean denoting success or failure.

The AppDelegate contains code that provides the JavaScript application with a list of URLs to the required JavaScript files as part of the launch options object. Open main.js and add the following code to the App.onLaunch() function:

// 1:
    if (success) {
      // 2:
      resourceLoader =
        new ResourceLoaderJS(NativeResourceLoader.create());
      var initialDoc = loadInitialDocument(resourceLoader);
    } else {
      // 3:
      var alert = _createAlert("Evaluate Scripts Error",
        "Error attempting to evaluate the external JS files.");

      throw ("Playback Example: unable to evaluate scripts.");

Taking this new function body step-by-step:

  1. The options object is prepared in Swift and then passed to the JavaScript app in the app delegate, while the initialJSDependencies property is an array of URLs for the different JavaScript files that need to be loaded as the app starts. evaluateScript() performs this action and then invokes the callback indicating whether it was successful.
  2. If the JavaScript sources evaluated successfully, create a ResourceLoaderJS object before using it to load the initial document and then pushing this TVML document onto the navigation stack. loadInitialDocument() is currently a stub method you’ll populate in a bit.
  3. If the JavaScript files failed to load, there is nothing more that the app can do. Therefore it uses _createAlert(), defined at the bottom of main.js to build and present an alert document and then throw an error.

Add the following to loadInitialDocument():

return resourceLoader.getDocument("video.tvml");

This uses the resource loader to get the TVML file from the app’s resource bundle and return it as a DOM object.

Next up, you’ll need to create the file your app is trying to load: video.tvml.

Head into Xcode, and right-click on the layouts group in the project navigator. Select New File…:

Navigate to tvOS\Other\Empty and hit Next. Name the file video.tvml and ensure that the wenderTV target is checked:

Open the new file in either your favorite XML editor (or Xcode, if you insist) and add the following lines:

<?xml version="1.0" encoding="UTF-8" ?>

This defines a simple TVML document using a new type of template: productTemplate.

Build and run the app to see how things look so far:

Hmm. The screen is still blank. Although you’ve created the file and provided the template, you haven’t provided any content. You’ll fix that shortly, but first you need to cover a bit of background on TVML templates.

TVML Templates

A TVML document is just an XML (eXtentible Markup Language) document with a specific schema defined by Apple. If you’ve used HTML in the past, XML will look familiar. HTML isn’t actually XML (despite some failed efforts with XHTML), but the syntax and structure is fairly similar.

Since TVML documents are XML they should start with the following line:

<?xml version="1.0" encoding="UTF-8" ?>

This is known as the XML prologue; it notes this file is an XML document and which character encoding it uses.

The TVML document has a root element, which is a single element at the top level of the document and the parent (or ancestor) of all other elements. The element has a single direct descendant, which can be one of 18 possible template tags.

A template tag specifies the top-level layout tvOS should use to render the document on screen. In addition to specifying the appearance onscreen, template tags also convey some semantic information about the content they contain. Templates might look similar, but have entirely different purposes.

TVML templates can be divided into the following categories:

  • Informational: Shows a small amount of information to the user, and optionally requests input from the user. It’s not designed for browsable content. Includes alertTemplate and loadingTemplate.
  • Data Entry: The user experience of entering data on TVs is pretty horrendous, and Apple TV is no different. However, there are a few templates for requesting data from the user, including searchTemplate and ratingTemplate.
  • Single Item: Displays information or content about a single product or item, such as a film or episode in a TV series. Includes productTemplate, oneupTemplate, compilationTemplate and showcaseTemplate.
  • Collections: Displays a collection of products, such as a TV series, a genre of films or tracks on an album. Includes stackTemplate, listTemplate and productBundle.
  • Other: Includes menuBarTemplate, which hosts a set of other templates, and divTemplate, which is a completely clean slate upon which you draw.
Note: Rather than go into detail here about each and every template, the Apple reference is a great resource for getting the low-down on the different templates, their capabilities and layouts:

The Product Template

The first document you’ll create uses , which is designed to display all information relating to a specific product — in this case, a video.

Open video.tvml and add the following code between the tags:

      <text>Ray Wenderlich</text>

This code snippet introduces a lot of new element types. Taking them one-by-one:

  • <banner>: Displays content across the top of a page.
  • <infoList>: Displays a list of elements, arranged in a vertical list.
  • <info>: Acts as a container forthis s the content to appear as an item in an or an .
  • <header>: Serves as a description of the content of the section in which it resides.
  • <title>: Contains the text of a short title.
  • <text>: Displays text.

Build and run the app to see what your new TVML looks like:

This page represents a video, but it currently lacks a title. Time to change that.

Add the following inside the <banner> tags, just after the closing tag:

    <text>17m 54s</text>
    <badge src="resource://nr" />is 
    <badge src="resource://cc" />
    <badge src="resource://hd" />

This section introduces more TVML elements:

  • <stack>: Stacks lay out their content vertically down the screen in a manner similar to . There’s a wider range of tags that can be in a Stack.
  • <row&gt: A row is like a stack, but with a horizontal orientation instead of vertical.
  • <badge&gt: Badges display a small inline image. The URL is provided by the src attribute.

Notice that the URL of the two badge images begin with resource://. This is a special URL scheme that points to images that exist within tvOS itself. These images include common action icons, such as “play”, rating images for different countries and video information such as HD.

Note: For a full list of the resource images available within tvOS, check out Apple’s documentation at

Build and run again to see how the page is shaping up:

It’s starting to look good, but there’s still a long way to go. Before continuing with the template coding, you first need to consider the separation of the data and the view.

Data Injection

As your video document currently stands, all the data is hard-coded. To show information about a different video, you’d have to create a whole new page. If you wanted to reformat the video page once you’ve created all the pages, you’d have to go back through and edit every single one of them.

A much better approach is to use a templating engine, where you build the video page as a template and specify where the data should be injected. At runtime, the template engine takes the page template along with the data and generates the TVML page for tvOS to display.

Note: The word “template” is now being used for two different purposes: TVML templates are the layouts provided by tvOS that render your documents on the screen, whereas a templating engine uses template documents combined with data to generate complete TVML documents. Don’t worry too much about the distinction; it’ll be much more clear once you’ve used them both.

Mustache.js is a popular simple templating engine for JavaScript. You might recognize the templating syntax which is based around curly-braces:


The Mustache.js library is already part of wenderTV, but you need to build the mechanisms to use it. This presents you with several tasks to accomplish:

  • The data is stored as JSON files in the app bundle. The JavaScript app needs the ability to request them.
  • When a document is loaded, it now requires data, and this should be combined with the document string using Mustache.js.
  • Images that are present in the app bundle need their complete URL substituted.
  • The video document should be updated to turn it into a templated document.

You’ll address each of these in order.

Note: As of tvOS 11, TVML supports the concept of prototypes in TVML that leverage data binding to link data elements to the TVML. This reduces code redundancy and improves performance when paginating large content sets, such as a huge list of YouTube videos in a grid view format.

However, prototyping is only available at the <section> level, while you’re using it in this tutorial for much more, such as in the <header> section of your TVML. You’ll continue to use Mustache.js as a templating engine for this section of the book, but if you end up having to handle large content sets in your tvOS apps, read up on prototyping and data binding at

Reading JSON From the App Bundle

Open ResourceLoader.js and add the following method to ResourceLoaderJS:

getJSON(name) {
  var jsonString = this.nativeResourceLoader
  var json = JSON.parse(jsonString);
  return json;

This function uses the native resource loader to pull the JSON file from the app bundle before parsing it into a JavaScript object.

Note: It would be relatively simple to replace this functionality with a method that calls a remote server for data instead of finding static data inside the app bundle. The rest of this templating approach would continue to work as it stands.

Injecting Data Into the Document String

Now that you can obtain the data for a given page, you need to combine it with the document template itself. Update getDocument() in ResourceLoaderJS to match the following:

getDocument(name, data) {
  data = data || {};
  var docString = this.nativeResourceLoader
  var rendered = Mustache.render(docString, data);
  return this.domParser.parseFromString(rendered,

Here you’ve added an additional data argument to the method and used render on Mustache to convert the template and data to a completed document. As before, you use a DOMParser to convert the document string to a DOM object.

Resolving Image URLs

For simplicity’s sake, your sample data stores images as the names of files in the app bundle, which need to be converted to URLs before you can display them. The utility functions to do this are already in the resource loader, so you just need to call them. You’ll do this at the same time as you update the initial document loading to use the templating engine.

Open main.js and update loadInitialDocument() to match the following:

function loadInitialDocument(resourceLoader) {
  var data = resourceLoader.getJSON("teamwork.json");
  data["images"] = resourceLoader
  data = resourceLoader
    .recursivelyConvertFieldsToURLs(data, "image");
  data["sharedImages"] = _sharedImageResources(resourceLoader);
  return resourceLoader.getDocument("video.tvml", data);

First, you load the data using the new getJSON() method. Then you use the utility functions to perform the image name-to-URL conversion. These convert three different image name sources:

  • Each value in the images object on the data array.
  • Every value associated with a key of image anywhere within the JSON data structure.
  • A set of shared images that are useful for all documents in wenderTV.

That takes care of the plumbing underneath; you’re ready to use this powerful new functionality.

Using the Mustache.js Templates

Open teamwork.json and take a look at the data you’ll use to populate the video page. There’s quite a lot of data, but it’s a standard JSON object and fairly easy to understand. You should spot some fields such as title, presenter and duration that you’ve already hard-coded into video.tvml. You’re now going to swap these out.

Open video.tvml and find the title tag that contains Ray Wenderlich. Replace the name with {{presenter}}, so that the first section now looks like this:


The syntax for Mustache.js is really simple; it will replace {{presenter}} with the value of presenter in the data object supplied to it.

Now that you’ve got the hang of that, you can replace the following content with the respective template tags:

  • Teamwork{{title}}
  • 17m 54s{{duration}}
  • Inspiration{{category}}
  • 2015{{year}}
  • resource://nrresource://{{rating}}

Build and run; you shouldn’t see any difference, which is exactly what you want. The page is now data-driven, and even better, you didn’t break anything. Bonus! :]

There are still some parts of the template you haven’t touched: closed-captions, HD and tags. These use some slightly more advanced parts of the Mustache.js templating engine.

Template Sections

Remove the three tags in the Tags section and add the following in their place:


This new syntax defines a template section. Look at teamwork.json and you’ll see that tags is an array of strings. The Mustache.js syntax here loops through the array, with {{.}} rendering the content of the current index.

Finally, you need to handle the two Boolean badges. Replace the cc and hd badges with the following:

  <badge src="resource://cc" />
  <badge src="resource://hd" />

Once again you’re using sections, but this time they’re structured like an if statement. If the specified property exists and has a true value, then render this section; otherwise, ignore it.

Build and run again; check out your newly templated video page.

To confirm that the data injection is actually working, open main.js and change the data file loaded in loadInitialDocument() from teamwork.json to identity.json. Build and run again to see the data change.

You can now see details of Vicki’s talk on identity — sweet!


Filling out the TVML Template

The video page is still looking a little barren. It’s time to double-down on adding some content.

Open video.tvml and add the following inside the <stack>, just below the existing <row>:

<description allowsZooming="true"
  <buttonLockup type="play">
    <badge src="resource://button-play" />
  <buttonLockup type="buy">

Once again, this introduces some new TVML elements:

  • <description>: Displays a larger amount of text that’s used to describe content. If the text is too long for the display area then a label will be displayed with a title defined by the moreLabel attribute.
  • &ltbuttonLockup>: A lockup is a class of element that joins its children together as a single element. A button lockup can contain text and a badge and will appear as a button.

Remember that these elements are all contained within a <stack>, so they’ll appear on top of each other.

Before checking your work, you need to add one more element to the top banner. Add the following line immediately after the </stack> closing tag:

<heroImg src="{{images.hero}}" />

A heroImg element is a large image that defines the content of this document. It appears inside the <banner> and tvOS uses it to define the blurred page background.

Build and run to see the completed top banner:

It’s starting to look really cool – But wait! You may now have noticed that the text color has changed from black to white. How did that happen?

tvOS has decided that because the background image you specified is darker than a certain threshold, it needed to increase the contrast of your text, so it changed the default text color.

Update the <productTemplate> tag to match the following:

<productTemplate theme="light">

Build and run to see the difference:

The visual effect on the background has changed along with the foreground font colors. You can change the theme attribute to dark if you wish to force the previous look if you prefer it.

Note: The default behavior has changed since the previous version of tvOS, so you may need to explicitly define the theme to get the effect you’re used to. Also, please don’t get this behavior confused with the new dark mode introduced by Apple in tvOS 10. That will be covered in more detail later in the book.

Adding Shelves

The remainder of the productTemplate is made up of “shelves”. A shelf is a horizontal section of the page with content elements scrolling on and off the screen.

Add the following below the closing </banner> tag, towards the bottom of video.tvml:

    <title>You might also like</title>
        <img src="{{image}}" width="402" height="226" />

This shelf displays a set of other videos that the user might enjoy as defined in the recommendations property of the data model. Each recommendation has an image and a title, each of which you use in the code above.

There are two other elements introduced in this code segment:

  • <section>: Defines a set of related content that should all be laid out together. A section can contain a title and multiple lockup elements.
  • <lockup>: You saw <buttonLockup> before; lockup is a more general type of lockup. It provides layout for an image, a title, a badge and a description.

Now add another shelf below the one you just created:

        <monogram firstName="{{firstname}}"
        <title>{{firstname}} {{lastname}}</title>

This shelf displays a list of people associated with the production; it’s stored in the people property in the data model.

This introduces the and elements, which let you represent a person when an avatar isn’t available. Like the other lockup elements, a monogram lockup simply locks its content together.

A monogram has firstName and lastName attributes, from which it generates a monogram (initials) and places it in a large circle.

Build and run to see how your shelves are taking shape. You will have to scroll down to reveal the lower shelf:

Take a look at the description for the “Identity” talk. Notice that the more label has appeared, because there is too much text to display in the available space. Navigate to the label and you’ll see you can focus on the description and press select to trigger an action. This action doesn’t currently do anything, but wouldn’t it be nice if it would display the full text?

Time for another TVML template.

Handling Text Overflow

The descriptive alert template provides space for an extended area of text and buttons. It sounds ideal for this purpose. You’ll use this template and a spot of JavaScript to wire it up.

In the Xcode project, right-click on the layouts group and select New File…. Choose tvOS\Other\Empty and name the file expandedDetailText.tvml.

Open the new file and add the following:

<?xml version="1.0" encoding="UTF-8" ?>
    <button action="dismiss">

This should be quite straightforward to understand. There’s the usual XML prologue, the tag and some elements you’ve used before. Notice that the button tag has an action attribute; this is a user-defined attribute that’s not part of the TVML specification.

You can define any attributes that you want (provided they don’t clash with existing attributes) and then read them from your JavaScript app. You’ll write some code to handle this dismiss action in just a bit.

Open video.tvml and find the tag. Update the element to match the following:

<description allowsZooming="true"

You’ve added two new attributes: action and title. You’ll use both of these in the event handler to create the expanded detail text document.

Event Handling

Now that the document templates are ready to go you can turn your attention to the JavaScript that wires everything up.

Open main.js and add the following function:

function _handleEvent(event) {
  // 1:
  var sender =;
  var action = sender.getAttribute("action");
  // 2:
  switch(action) {
    case "showOverflow":
      // 3:
      var data = {
        text: sender.textContent,
        title: sender.getAttribute("title")
      // 4:
      var expandedText = resourceLoader
        .getDocument("expandedDetailText.tvml", data);
      expandedText.addEventListener("select", _handleEvent);
    case "dismiss":
      // 5:

Taking this piece-by-piece:

  1. The target property of the event argument represents the DOM object that fired the event. getAttribute() of a DOM object will return the value for the specified attribute. Here you’re using it to find the value of the action attribute you added above.
  2. Switch on the action attribute to invoke the appropriate code.
  3. If the action is showOverflow, then you have a description field with too much content. Construct an object with the data required by the expanded detail text document. Once again you’re using getAttribute() along with textContent, which returns the content of the tag itself.
  4. Load the expandedDetailText.tvml document in the usual way, add an event listener and use presentModal() on NavigationDocument to display the new document on top of the current document.
  1. If the action is set to dismiss, use dismissModal() on NavigationDocument to perform the dismissal.

Now that you’ve created this event handler, you need to wire it up to the initial document. Add the following line to App.onLaunch, just after you call loadInitialDocument():

initialDoc.addEventListener("select", _handleEvent);

This registers _handleEvent as a listener for the select event, and uses event bubbling to handle all events triggered within the document.

Build and run the app, navigate down to the over-full description and hit the select button. You’ll see your new expanded detail text page:

You can use the dismiss button to return to the video screen.

Viewer Ratings

In the final part of this tutorial, you’ll add a new ratings section to the video page and let the user select a rating using a new template.

Open video.tvml and add the following new shelf underneath the Production shelf:

    <title>What other people thought</title>
      <ratingCard action="addRating">
          <title>{{out-of-five}} / 5</title>
          <ratingBadge value="{{badge-value}}"></ratingBadge>
          <description>Mean of {{count}} ratings.</description>
          <text>{{name}} {{date}}</text>

By now, you’ll recognize most of the TVML elements, but there are still a few new ones:

  • <ratingCard>: Displays a small card suitable for showing the ratings of a product.
  • <ratingBadge>: A specialized badge for showing a star-rating. The value attribute should be a value between 0 and 1, which will be converted to a proportion of five stars.
  • <reviewCard>: A card for displaying user or critic reviews.

Notice that the element has the custom action attribute again. You’ll use this later to display the rating page.

Build and run to see what the new shelf looks like:

When the user selects the rating card, you want to let them choose a rating for the current video. This is exactly what the ratings TVML template is for.

Collecting Ratings

In Xcode, right-click on the layouts group and select New File…. Choose tvOS\Other\Empty and name the file videoRating.tvml.

Open the new file and add the following:

<?xml version="1.0" encoding="UTF-8" ?>
    <ratingBadge />

This new file uses the which simply displays and collects ratings. It contains a <title> and a <ratingBadge>, both of which you’ve already seen.

The template is ready; you just need to display it. Open main.js and add the following case to the switch statement in _handleEvent():

case "addRating":
  var ratingDoc = resourceLoader.getDocument("videoRating.tvml",
    {title: "Rate Video"});

These few lines load the new document you created and provide the title to the templating engine. It then displays the document modally.

Build and run, navigate to the rating card and hit select to see the new ratings page:

Now that is one swell-looking – and extensible – interface.

Where to Go From Here?

You can download the final project from this tutorial here.

In this tutorial you’ve created a great-looking TVML app and used three of the built-in templates along with a vast array of TVML-specific elements. You also integrated a JavaScript templating engine to separate the UI from the data. Adopting great development techniques right from the start is a win in anyone’s book.

You can check out Apple’s documentation ( for specifics about the templates and elements covered in this tutorial.

If you enjoyed what you learned in this tutorial, why not check out the complete tvOS Apprentice book, available in our store?

Here’s a taste of what’s in the book:

Section I: Architecture

This section is designed to give you a birds-eye view of how tvOS works and help you decide what to read next.

Section II: TVML Apps

This section covers the basics for creating an app via the TVML approach. From the basics of Hello World through a real world example, by the end of this section you’ll know everything you need to create client / server apps for Apple TV.

Section III: Traditional Apps

This section covers the basics for creating apps via the traditional approach. You’ll learn the new libraries created for Apple TV, and how the ported libraries from iOS can be used.

Section IV: Advanced Frameworks

This section covers some of the more advanced frameworks you’ll need for many TV app use cases. Whether you took the TVML approach or the Traditional approach, these frameworks will be important to understand to make your app stand out.

Section V: Design

This section covers design concepts important for tvOS. For your app to stand apart from the rest, you’ll need to understand these design concepts well.

Bonus Chapter

And that’s not all — on top of the above, we have a bonus chapter for you that gives you a crash course in JavaScript!

By the end of this book, you’ll have some great hands-on experience with building exciting, good-looking apps for the Apple TV!

And to help sweeten the deal, the digital edition of the book is on sale for $49.99! But don’t wait — this sale price is only available for a limited time.

Speaking of sweet deals, be sure to check out the great prizes we’re giving away this year with the iOS 11 Launch Party, including over $9,000 in giveaways!

To enter, simply retweet this post using the #ios11launchparty hashtag by using the button below:

We hope you enjoy this update, and stay tuned for more book releases and updates!