# Creating Templates Guide for adding a new template to `@dui/templates`. --- ## What templates are Templates are pre-composed UI patterns built from DUI components + vanilla HTML/CSS. Unlike components (which extend primitives), templates are **self-contained** — they own all their CSS and ship a complete look that adapts via design tokens. ### Templates vs. components | | Component | Template | |---|---|---| | Extends | A primitive class | `LitElement` directly | | Styles | Aesthetic layer on top of primitive | Self-contained — owns all CSS | | Purpose | Reusable primitive (button, badge) | Ready-to-use pattern (feed item, social post) | | Package | `@dui/components` | `@dui/templates` | ### What templates should NOT do - **No data fetching.** Templates are presentational. Feed them props; they render. - **No global state.** No context providers, no stores. - **No complex interaction logic.** Templates can compose interactive DUI components (accordion, tabs) — the component owns the interaction. But templates shouldn't implement their own state machines, pagination, or sorting. --- ## File structure Each template category gets its own folder: ``` packages/templates/src/ {category}/ {name}.ts # Template class index.ts # Re-exports + family array all.ts # Barrel: all template families ``` --- ## The template class Here's the pattern, using `dui-feed-item` as an example: ```typescript // packages/templates/src/feed/feed-item.ts import { css, html, LitElement, nothing, type TemplateResult } from "lit"; import { property } from "lit/decorators.js"; import { base } from "@dui/core/base"; // resolves to @dui/primitives/core/base // Side-effect imports — registers DUI components used in this template import "@dui/components/badge"; const styles = css` :host { display: block; } article { display: flex; flex-direction: column; gap: var(--space-1_5); padding: var(--space-3) var(--space-4); border: var(--border-width-thin) solid var(--border); border-radius: var(--radius-md); background: var(--surface-1); } .title { font-family: var(--font-sans); font-size: var(--font-size-sm); font-weight: var(--font-weight-semibold); color: var(--foreground); } .meta { font-size: var(--font-size-xs); color: var(--text-2); } `; export class DuiFeedItem extends LitElement { static tagName = "dui-feed-item" as const; static override styles = [base, styles]; @property() override accessor title = ""; @property() accessor subtitle = ""; @property() accessor timestamp = ""; @property() accessor severity = ""; override render(): TemplateResult { return html`
${this.title} ${this.severity ? html`${this.severity}` : nothing}
${this.subtitle}
`; } } customElements.define(DuiFeedItem.tagName, DuiFeedItem); ``` ### Key conventions | Convention | Details | |---|---| | **Tag name** | `dui-{name}` — plain `dui-` prefix | | **`static tagName`** | Required for the tag name | | **`customElements.define()`** | At module level — self-registers on import | | **Component dependencies** | Side-effect imports (`import "@dui/components/badge"`) — triggers registration | | **`base` import** | Always include `base` from `@dui/core/base` (part of `@dui/primitives`) for structural resets | | **Design tokens only** | Never hardcode `px`, `rem`, or color values | | **Semantic HTML** | Use `
`, `
`, `