Design templates
A template is your own HTML with a few Arc CMS attributes that say where the content goes. Copy a folder into your site folder, change the markup, and pick it on the content type. The full syntax and every value you can use are in Templates. For a worked example from start to finish, see Turn an HTML site into an Arc CMS site.
Start from a shipped folder
Each content type with public pages has three templates:
| File | Renders |
|---|---|
list.html | The list of the type, /{type} |
detail.html | One item, /{type}/{item} |
partials.html | Cards of the type on the home page, such as the latest articles |
detail-{name}.html | Optional: another layout for one item, such as detail-contact.html, which an editor picks with Layout (see Layouts) |
Arc CMS ships four folders of them in public/_site/templates/:
| Folder | Use it as |
|---|---|
public/_site/templates/default/ | The layout of any content type without a folder of its own, so every type renders even if you write no template. It already shows sources, related items, the author box and the common repeating fields. |
public/_site/templates/articles/ | A blog layout |
public/_site/templates/manuals/ | A documentation layout |
public/_site/templates/info/ | The standard pages (About, Contact, the policies), with a Contact layout, detail-contact.html (see Write static pages) |
These are core files: read and copy them, never edit them.
Read a shipped template
The shipped files follow a few habits worth keeping when you copy one:
| You will see | Why |
|---|---|
<div class="arc-cms-template"> around everything | A published page's <body> has no class, so one wrapping element gives your styles something to hang on. Arc CMS does not style the class itself; use your own name if you like. |
class="arc-skeleton" on headings and text | A grey loading shimmer in the app until the element is filled. The class is removed once it is; published pages have no style for it. |
| An empty state after a loop | Text such as "No Articles Yet" shown only when the loop has no rows, by CSS such as .articles-grid:empty + .empty-state. The default cards hide the whole block instead, with data-arc-if="hasItems". |
.article-content .arc-block rules in the default detail.html | The editor's structured blocks (FAQ, key takeaways, how-to, definition) reach the body as <section class="arc-block" data-arc-block="faq">. A detail template of your own must style them, or they show as plain headings and lists. Copy those rules and change the selector to match where your template puts the body. |
A <style> and a <script> at the end | The layout's own styles and small helpers. Publishing moves them; see What publishing puts around your template. |
Make your own
Copy the folder closest to your design into your site folder under a new name, for example
public/_site/templates/articles/tosrc/custom/site/templates/case-studies/. Name the folder with lowercase letters, digits, hyphens and underscores, starting with a letter or digit; any other name is not served. Keep only the files you change: a file your folder lacks is taken fromdefault, so a folder with justdetail.htmlis complete.Replace the markup and styles with your design. Keep each file an HTML fragment: no doctype and no
<html>,<head>or<body>. Wrap it all in one element with a class. Where to keep the styles is covered in Keep your CSS.Keep
<arc-header></arc-header>and<arc-footer></arc-footer>in the list and detail files; they become the site header and footer. Leave them out ofpartials.html.In the admin, open Content, Content types, edit the type, and choose the folder under Content Template. The list shows every folder your site has, with nothing to register. A new type whose slug matches a folder name, such as
case-studies, gets that folder picked for it.
To change one of Arc CMS's folders instead, put a file at the same path in your site folder: src/custom/site/templates/default/detail.html replaces the default detail layout and leaves its list and cards as Arc CMS ships them.
The header and footer work the same way: src/custom/site/header.html and src/custom/site/footer.html replace Arc CMS's (public/_site/header.html, public/_site/footer.html). They are plain HTML fragments with no {{ }} values; <arc-search> and <arc-language-switcher> in the header become the search box and the language links.
Your contact details and links go in with data-arc-site attributes, filled from Settings, About, so editors change them there instead of in the file (the full list is in Site info). Arc CMS's default footer lists the published standard pages and a copyright line:
<footer class="site-footer">
<div class="container">
<ul class="footer-pages" data-arc-site-if="pages" data-arc-site-loop="pages">
<li><a href="{{ url }}">{{ title }}</a></li>
</ul>
<p class="footer-copyright">© <span data-arc-site="year"></span> <span data-arc-site="name"></span></p>
</div>
</footer>
The {{ url }} and {{ title }} inside a data-arc-site-loop are the one place the footer has values to fill. Arc CMS's footer styles in main.css apply only to the site footer (a <footer> with the class site-footer, or one directly inside <body>, <arc-footer> or a template), so a <footer> inside a card or a dialog keeps its own look. A footer with your contact details is in Let editors change your home page.
A template that is a whole HTML document (with a doctype, <html>, <head> or <body>) is not used. Only real tags count: a comment, style or script that mentions <head> does not make a file a whole document. For a list or detail file, the app shows the default template instead, and publishing stops with a message on the item. Two cases differ: a partials.html like that shows nothing in the app, and a file of your own in src/custom/site/templates/default/ leaves the app's page loading, since there is nothing left to fall back to (publishing then uses Arc CMS's own default). If your page does not look like your template, check this first. A file in your site folder that is not a template, page or other site file (say templates/case-studies/card.html) is not served; npm run dev lists it in a warning.
Put the content in
Four attributes and one placeholder do most of the work:
| Write | To |
|---|---|
{{ title }} | Insert a value into text or an attribute, such as href="{{ url }}". A missing value prints nothing. |
data-arc-bind="title" | Replace the element's content with a value; on an <img> it sets the source, on an <a> the link |
data-arc-loop="items" | Repeat the first child once per item (on a list page or in cards), per tag (tags) or per row of a repeating field; data-limit="6" caps it |
data-arc-if="coverImage" | Remove the element when the value is empty |
data-arc-t="read_more" | Translate fixed text into the page's language (see Publish in more than one language) |
The values each kind of template can use, with an example of each, are in the reference: detail pages, list pages and cards, and partials. A custom field is written by its key without the type slug ({{ price }} as well as {{ events-price }}), on detail pages and inside the items loop of lists and cards, so one template can serve several types. Every image also has sized versions, such as {{ coverImage_m }} (see Manage media). Edge cases, such as escaping and what data-arc-if counts as empty, are in Syntax details.
The admin also lists values: open Content types, press View on a type, and read its Template Reference. It starts with the type's own fields, each with the snippet its field type needs (a loop for a repeating field, data-arc-bind for rich text), then lists what the detail page, the list page, a card block and each item can bind. Click a snippet to copy it.
Placeholder text behaves two ways. A loop keeps only its first child as the row, drops the rest, and is emptied when there are no rows: write one example row, and hide an empty container with CSS such as :empty { display: none; }. An element with data-arc-bind whose value is missing keeps the text you wrote, so placeholder text can reach the live page. Use {{ }} for optional values, or guard them with data-arc-if.
What publishing puts around your template
A template is only the middle of a page. Publishing builds the rest of a list or detail page around it:
| Part | What you get |
|---|---|
<title> | Detail page: the item's SEO title, else its title (on a translated page, the translation's first). List page: the type's name in the page's language. |
<meta name="description"> | Detail page: the item's meta description, empty when it has none. List page: the type's description, else "Browse all" and the type's name. |
The rest of the <head> | Canonical link, social sharing tags, language links, structured data and the site stylesheets, all written by Arc CMS. You cannot add tags of your own: a <meta>, <title> or <link rel="canonical"> in a template ends up in the body, where it does not do its job. Set titles and descriptions in the editor (see Get found in search). |
<body> | No class or attribute. Wrap your template in one element with a class, and style from there rather than from body. |
<style> | Every style block in the template (and in the header and footer) moves into the head, after the site stylesheets, so it wins over them when selectors are equally specific. |
<script> | Every script moves to the end of the body, after the footer, in the order written. It runs once the whole page is there. |
<link rel="stylesheet" href="/site/…"> | Stays where you wrote it and gets a version (?v=) that changes with the file, so visitors get a changed file at once. The same goes for any src or href to one of your files under /site/. |
The stylesheets loaded before yours are every URL in cssUrls of the Settings/site document, in order. By default these are Bootstrap 5, Font Awesome 6.5.1 and /assets/css/main.css (Arc CMS's own styles). Your /assets/css/site.css always comes after them. There is no admin screen for the list; change it in the Firestore console. A change shows on published pages only: the app's preview always has Bootstrap and Font Awesome.
Keep your CSS
| Where | Applies to | Good for |
|---|---|---|
A <style> block in the template | Pages built from that file. A partial's block stays in the home page, where the cards are. | Styles for one layout |
A linked file, such as <link rel="stylesheet" href="/site/blog.css"> for src/custom/site/assets/blog.css | Every template that links it | Styles shared by a few templates, kept by the browser between pages |
src/custom/site/site.css | Every published page (content, home and static pages), and the app's website pages and sign-in page | Site-wide styles, the header and the footer |
Preview
Run npm run dev, open an item in the editor and press Preview. The page opens at /{type}/{item}?preview=true, rendered with your local template files, so you can edit the HTML and reload. The list page is at /{type}. Test with long titles, a missing cover image and empty fields.
Preview versus live
The app fills the same template with the same attributes, but it is not the published page. Check the live page before you call a template done:
- Header and footer: the app draws them at the top and bottom of the page, wherever you put
<arc-header>and<arc-footer>. The live page puts them where the tags are. - Search box and language links: in the app they work in the header and the footer; the live page fills them anywhere, a template included.
- Scripts: the app runs a list or detail template's scripts after the page has loaded, once it has drawn the template, so a script waiting for
DOMContentLoadednever runs there. A detail page is drawn again when its author, related items or strings arrive, without running the scripts again, so attach listeners todocument, not to elements inside the template. A card block runs its scripts once, after its cards are first drawn. The header and footer run theirs each time they are drawn, again when the page's language arrives. - Surroundings: in the app your template sits inside the app's own elements, with Bootstrap, Font Awesome and the app's styles loaded whatever
cssUrlssays. Live, it sits directly in the body with onlycssUrlsandsite.css. - Unfilled values: the app shows a
data-arc-bindwith no value, and anyarc-skeletonnot yet filled, as a grey shimmer. Live, you see the text you wrote. - Next and previous links:
nextContent.urlworks only in the app. Build the link fromlangPrefix,contentTypeSlugandnextContent.slug. - Missing folders: the app quietly uses the default folder when the type's folder is not there; publishing stops instead.
Put it live
Publishing reads the templates, header and footer from the live site, not from your computer. Deploy the website once your templates are ready (see Deploy), then publish. npm run deploy:dev, npm run deploy:prod and the deploy menu (npm run deploy) republish every page with the new templates at the end of a website deploy. Otherwise open Content, Content types and choose Republish website, which rebuilds every content page, list page, the home page and static pages.
Publishing an item after editing its template locally, but before deploying the website, produces a page with the old template. Deploy the website first. A type whose folder is new, so not on the live site yet, is not published at all: the editor shows "Template folder 'x' is not on the live site. Deploy the website, then publish again." Publishing sees a deploy within a few seconds: the functions keep each live file only until a deploy changes it.
Cards on the home page
The home page can show cards of any type with <arc-content-partials content-type="articles" count="4"></arc-content-partials>, laid out by that type's partials.html (its folder's, else the default's), and republished whenever that type's content changes. section-title sets the heading and template-folder borrows another folder's cards; the attributes and defaults are in Partials. See Build your home page. Content pages and static pages do not render it.
Next
Write the front door of your site: Build your home page.