--- title: Templates description: Override any part of a Luma widget's markup with a lit-html template — template names, the scope each one receives, results, helpers and product images. slug: luma/templates docKind: reference hub: luigisbox-ai --- Every widget renders through named templates. Each has a built-in version; a file named `template-.ts` in your integration replaces it. Listings use templates in your own markup and CSS classes, so results look like the rest of your site. ## Which file wins For each template name, the last one found is used: 1. The widget's built-in template 2. `integration/_global/template-.ts` 3. The widget's own folder — `integration/search/`, `integration/autocomplete/`, and so on Category pages use `search/` templates, overridden by any in `plp/`. A `_global/` template named after a widget (`template-recommender-…`) applies to that widget only. | Widget | Template names | |---|---| | Search results, category pages | `search-box`, `search-header`, `search-facets-default`, `search-range-default`, `search-results-default`, `search-result-default`, `search-pagination` | | Autocomplete | `autocomplete-box`, `autocomplete-result-default`, `autocomplete-sheet`, `autocomplete-empty` | | Recommenders | `recommender-box-default`, `recommender-result-default`, `recommender-popup` | Any other name is your own template, rendered from another with `include()`. ## Writing one A template is [lit-html](https://lit.dev/docs/templates/overview/): a default-exported function of its scope that returns `` html`…` ``. ```ts // integration/search/template-search-result-default.ts import { html, nothing } from '@/lit'; import type { SearchResultScope } from '@/types'; export default ({ r, i, img, price }: SearchResultScope) => html` ${img(r, i)} ${r.c.title} ${r.c.price ? html`${price(r.c.price)}` : nothing} `; ``` Import lit from `@/lit` and scope types from `@/types`. A re-render changes only the bindings whose value changed, so images, focus and open `
` survive it. Templates need no `eval` and work under a strict Content Security Policy. | Syntax | | |---|---| | `${expr}` | Text, escaped | | `` ${cond ? html`…` : nothing} `` | A condition | | `` ${repeat(list, (x) => x.id, (x) => html`…`)} `` | A keyed list; `list.map(…)` for an unkeyed one | | `attr=${v}`, `?disabled=${b}`, `.checked=${b}` | Attribute, boolean attribute, property | | `@click=${s.showMore}` | Event handler | | `${include('product-tile', { badge })}` | Another template, with this scope plus the given properties | `@/lit` also exports `svg`, `classMap`, `styleMap`, `ifDefined` and `unsafeHTML`. A template must depend on its scope by value. Luma reuses a result tile's output while the values it read are unchanged, so an object changed in place, rather than replaced, can keep showing the old value. ## Scopes | Template | Scope type | Holds | |---|---|---| | `search-*` | `SearchScope` | `query`, `title`, `total`, `results`, `facets`, `ranges`, `flags`, `chips`, `sorts`, `more`, `showMore`, and the actions on them | | `search-result-default` | `SearchResultScope` | The above, plus `r` (the result) and `i` (its position, from 0) | | `search-range-default` | `SearchRangeScope` | The above, plus `range` (one numeric facet) | | `autocomplete-*` | `AutocompleteScope` | `query`, `groups`, `byType()`, `active`, `choose()`, `allHref` | | `autocomplete-result-default` | `AutocompleteResultScope` | The above, plus `r` and `i` | | `recommender-*` | `RecommenderScope` | `results`, and `close()` in the popup | | `recommender-result-default` | `RecommenderResultScope` | The above, plus `r` and `i` | Every field is documented on its type in `@/types`. The ones a listing template binds most: | Field | | |---|---| | `facets` | Text facets. Each value carries `label`, `checked`, `href` and `rank` (its place in the API's order, most frequent first). There are no counts per value. | | `ranges` | Numeric facets: `min`, `max`, `from`, `to`, `step`, and `set(from, to)` to apply one. Call `set` when the shopper lets go, not on every move. | | `chips` | Active filters, each with the link that removes it | | `clear` | A link that removes every filter, or `null` | | `sorts` | Each with `label`, `selected` and `href` | | `more`, `showMore` | Whether another page exists, and the handler that appends it | A control that changes the listing is a link: put its `href` on an ``, or as `data-lbx-href` on any other element, and bind `go(link)` as the click handler. Luma then prefetches the state the control leads to when the shopper hovers or focuses it. ### Pagination Listings use "show more" only. `results` holds every page loaded so far; `showMore` appends the next one. Bind it on `