Templates
elements man html/templates Read as markdownWhere 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>