Views in Tailor


The simplest way to generate pages in response to a request is to create a template, which is a type that implements the TemplateType protocol. You can initialize a template in the controller, with the data from the request, and then call its generate method to build the body. That will in turn call the body method, which you must define in your templates, and which should add the contents of the response to its contents buffer.

A template must provide a state variable, which must be a TemplateState. This encapsulates all of the fields required by the template helpers, in a single struct to minimize the overhead when defining your template types. The TemplateState can be initialized with just a controller, but it also provides some other fields that get modified as you build up your response. You can define your template initializers to take whatever data they want, as long as they fill in the template state field. The best practice is to have the controller extract the necessary data from the request and the database, and give those to the template when it creates it. This allows you to keep different parts of the logic separated, and design and test the two independently of each other.

Your templates will often be specific to the controller that is expected to render them. They will also often have similar purposes, like a template for an index page or for a form for editing a resource. A good convention for handling this is to scope them within the controller. For instance, if you declare a template with

extension HatsController {
  struct IndexTemplate: TemplateType {
    var state: TemplateState
    mutating func body() {

then you can just refer to it as IndexTemplate within the HatsController type. Swift does require that the every source file have a unique filename, unfortunately, so even if you keep all your templates for a controller in a dedicated directory, you will have to give them specific names like HatsIndexTemplate.swift.

Basic Tags

The tag method adds an HTML tag to a template's buffer. It takes in a tag name, a dictionary of attributes, and a block that adds in more content to go within the tag. For instance, if you had this code in your body method:

tag("p", ["class": "help-block"]) {
  tag("input", ["class": "help-button", "value": "Help"])

it would add this content to the buffer: <p class="help-block"><input class="help-button" value="Help"></input></p>.

There is a variant of the tag method that does not take any attributes, and another variant that takes the text to put inside the tag. There is also a text method that adds text directly into the buffer.

The link method puts in a link to another page in your site. It builds the path for you, based on a controller name, action, and request parameters. You can omit any of these, and it will default to matching the values for the current request. For instance, if you call link(actionName: "new"), that will generate a link to the new action in the current controller. The link method also takes attributes for the HTML tag, and a block that can generate the contents to go inside the tag.

Form Building

The TemplateForm type provides another set of helpers specifically for building forms for creating and updating model objects. You can create a form by calling the form tag on the template, which will create the template form and pass it to a callback. This form method allows you to specify the path to submit the form to, the HTTP method, additional attributes for the tag, and finally a block for the body of the form.

Within the body of the form, you can use the input method to add in input tags. This serves as a wrapper around the form's inputBuilder, which is responsible for adding the tags. An input builder is a block that is given the form builder, the attribute name, the attribute value, input attributes, and validation errors. The default input builder will add in a label and an input element, but you will often want to give a custom input builder to add styling or the display of error messages.

Localization and Sanitization

You will generally want to put the text on your site through a localization system, so that you can translate it into different languages. Tailor's localization system is provided by the LocalizationSource protocol. This protocol provides a fetch method, which takes in a key and provides you the localized text. Keys should be in lowercase, with dots to separate different sections of the key. The fetch method may return nil, if there is no translated value available for the key.

The text method on templates automatically localizes the text, but it has a flag for disabling this behavior. The version of the tag method that takes text contents will also localize the text.

The LocalizationSource protocol itself does not provide any mechanism for storing or fetching translations. There are two types that provide different strategies for this by implementing the protocol. PropertyListLocalization looks for the values in your application's configuration, in the staticContent configuration setting. For instance, if you are looking for a translation for hats.index.title in the locale en, it will look for a value in the configuration's static content for the key en.hats.index.title. You can find out more about configuring your app in the Configuration guide. The other available strategy is the DatabaseLocalization class. This looks for translations in a table called tailor_translations. This table should have a field for translation_key, locale, and translated_text, which should all be string types.

Localizations also allow you to interpolate dynamic content into them. The fetch method takes in an optional interpolations argument. If you provide a dictionary with name mapped to John, then any occurrence of \(name) in the translated text will be replaced with John.

Localizations support fallbacks from country-specific locales to global ones, and from other languages to English. For instance, if you're looking for a translation for the locale es-mx, and there is no translation available, it will look for one in the locale es, and then en. If you want to change the available fallbacks, you can use a custom LocalizationSource type that overrides the fallbackLocales method.

The localization for a template is specified by the controller. The default initializer for a controller will create a localization using a localization source specified in the application's configuration. The localization setting on the configuration gives a block that takes in a locale and returns a localization. The default configuration creates a PropertyListLocalization. The controller will set the locale for the localization based on the Accept-Language header on the request. If you want to use a different locale, perhaps based on a request parameter or user settings, you will have to set that localization in your controller's initializer.

Tailor has some conventions for keys that can make them less repetitive. If your key starts with a dot, Tailor automatically prepends the name of the controller class an action. Let's say your app is called Haberdashery, and you are fetching a translation inside the create method of HatsController. You can just type localization.fetch(".success_message"), and it will look for the key haberdashery.hats_controller.create.success_message. Along the same lines, localization in templates are automatically prepended with the class name. If you are in the template HatsController.IndexTemplate, and you call localization.fetch(".title"), it will look for the key haberdashery.hats_controller.index_template.title.

Whenever you pull in text from a source outside of your code, whether it's a localization file or user-supplied content, you need to be careful to avoid accidentally allowing that text to alter the structure of the page or inject any javascript into the page. Tailor has a sanitization system to help you avoid these problems. The Sanitizer type provides a mechanism for sanitizing text and wrapping it in a SanitizedString to prove it has been sanitized. The sanitized string records all of the sanitization classes that have been run on it. The one you'll use most is Sanitizer.htmlSanitizer, which replaces angle brackets, quotation marks, ampersands, and backslashes with ampersand-escaped equivalents. You run text with a sanitizer by passing the string to the sanitizeString method. If you have text that you already know is safe, you can pass it through the accept method, which will add the sanitizer class to the text's list but will not modify its contents.

Any text that you provide in your templates is automatically run through an HTML sanitizer. If you want to avoid this, you can pass the text to the raw method, which will accept the text without sanitization.


You'll often want to have all the pages in your site share a common layout, with a page's particular content embedded into it. The LayoutType protocol supports this pattern. A layout is a template that wraps around another template, and renders that template within it. You can create a custom layout for your app like you would any other template. When you reach the point where you want the page's content to appear, you can call self.renderTemplate(self.template). The ControllerType protocol provides a layout attribute to include layouts automatically. You just need to set a layout type on this attribute, and any templates rendered in the controller will be wrapped in an instance of that layout type.

Template Examples

extension HatsController {
  class FormTemplate: TemplateType {
    var state: TemplateState
    let hat: Hat

    init(controller: Controller, hat: Hat = Hat()) {
      self.hat = hat
      state = TemplateState(controller)

    mutating func body() {
      tag("h1", text: ".title")
      link(action: "index") { text(".back") }

      let action = ( == nil ? "create" : "update")
      form.form(controller.pathFor(actionName: action) ?? "", type: Hat.self) { form in
        form.input("brimSize", hat.brimSize)
        form.input("color", hat.color)
        tag("input", ["type": "submit", "value": ".submit"])