Manual HTML Templates

Templates

elements man html/templates Read as markdown

Where template files live, how a template is declared, and how a parent passes content into a child with slots.

Files

A page is a folder:

app/pages/home/
  index.ts          # route handler
  template.ehtml    # templates, handlers, rpc, interfaces, LiveTables
  style.css         # page styles
  test.ts

Everything the page needs starts in template.ehtml: its templates, event handlers, helpers, interfaces, @rpc functions, LiveTables, and Channels. Both siblings import from it by name.

// index.ts
import home, { posts, Post } from "./template";

// test.ts
import { normalize, Post } from "./template";

An .ehtml file is a module like any other. The <html> template is its default export, other templates are named exports, and so is any TypeScript declaration you mark export.

Add a sibling services.ts when the markup gets hard to find in template.ehtml, and move the @rpc functions and interfaces there first. That is a readability call on one file, not a size threshold, and most pages never reach it.

Move to app/shared/services/<topic>.ts when a second consumer appears: a second page, a job, an email, a route in app/routes/. One page plus one job counts; one page on its own does not. Shared templates go in app/shared/templates/.

Reusing a template

Two pages that render the same UI with different values want one template, not two copies. Sign in and sign up are the usual pair: the same field stack, the same error line, different labels and a different call on submit.

elements create template auth-form -attrs='title: string, action: string'

That writes app/shared/templates/auth-form/{index.ehtml, style.css}, and both pages import it.

Extract when the shared part is most of the markup and the differences fit in attributes. Do not extract two elements that happen to look alike today, and do not add a variant attribute that switches between two layouts with nothing in common. A template whose body is one big e:switch on its own attribute is two templates.

The same call makes page-local templates. elements create template ui/button nests under app/shared/templates/ui/, and a template used by one page can just be a second top-level tag in that page's template.ehtml.

Templates

A template name must start with a capital letter. <Card>, <FilterTab>, <AuthForm>. This is enforced, not a style preference: a call site reads a capitalized tag as a template and a lowercase one as a native HTML element, so a template declared <card> could never be called. Declaring one is an error, "The template name <card> must start with an uppercase letter."

<html> is the one exception, and a special reserved name. Marking a template as <html> makes it the default export of the file and turns it into an HTML page. Other named templates are named exports.

A page's CSS is every stylesheet reachable by import from its <html> template: the template's own imports, then each child template's imports, depth first. The imports live in the CSS files, so a page's style.css is where the shared baseline gets @imported. A rule written in another page's style.css is not in that set, which means markup copied from one page does not bring that page's styles with it. Scripts bundle by the same walk.

import html from "./template";
import html, { Footer } from "./template";   // page + named template

Import a template extensionless: ./template resolves to template.ehtml.

Templates compose like function or class declarations: define them in one file or split across multiple files.

A template name is a type as well as a value, so it cannot be shared with a type declared beside it. An interface Item and an <Item> template in the same file are two declarations of Item, and the compiler reports a duplicate identifier on both. Give the data a different name from the template that renders it (Message and <MessageRow>, or ItemData and <Item>).

Slots

Slots are how a parent passes content into a child template. The child declares <slot/> placeholders; the parent fills them. The syntax is standard html.

<Panel>
  default content
  <footer slot="footer">footer content</footer>
</Panel>

<Panel>
  <slot/>
  <slot name="footer">default footer</slot>
</Panel>