Themes, Rendering, And Assets
This page explains how Coil turns a customer-owned theme into a final HTML document with working assets and injected metadata.
Start With The Full Path
A single page render usually passes through these layers:
request
-> route + site + locale resolution
-> render model assembly
-> template lookup through namespaces
-> asset-path resolution through the active manifest
-> SEO/head decoration
-> final HTML response
That is the real mental model. Themes are part of the render pipeline, not just decorative files.
What A Theme Contributes
A Coil theme contributes four practical things:
- document structure
- reusable fragments
- published frontend assets
- customer-owned presentation behaviour such as theme mode or small enhancements
This is why the theme is broader than “the CSS folder.”
One Concrete Document Flow
Imagine a customer layout like this:
<!DOCTYPE html>
<html xmlns:coil="https://coil.rs" coil:attr="lang=${locale}">
<head>
<title coil:text="${page.title}">Fallback title</title>
<link rel="stylesheet" href="/theme/assets/site.css" coil:href="asset('theme/assets/site.css')" />
</head>
<body>
<nav coil:replace="~{navigation/primary}"></nav>
<main coil:slot="content"></main>
<script src="/theme/assets/site.js" coil:src="asset('theme/assets/site.js')" defer="defer"></script>
</body>
</html>
What happens:
localealready comes from request resolutionpage.titlealready comes from the render modelasset('theme/assets/site.css')resolves to the published asset URLcoil:replacepulls in a fragment- the runtime later injects canonical, robots, alternate locale links, and JSON-LD into the head
That one example is the entire subsystem in miniature.
Why Some Templates Carry Full HTML Structure
This surprises people coming from frameworks that hide the outer document shell.
Coil keeps full HTML structure in customer templates because:
- the customer app owns the actual product shell
- the customer app often owns the nav, header, footer, and landmarks
- SEO and asset references are still part of customer-facing page composition
So seeing <html>, <head>, and <body> in customer templates is normal and correct.
Layouts, Fragments, Pages, And Assets
Layouts
Layouts own:
- document shell
- slots
- global navigation or footer
- shared page furniture
Fragments
Fragments own:
- reusable sections
- nav blocks
- collection grids
- account summary panels
Pages
Pages own:
- route-specific content
- headings
- page-level forms and lists
Assets
Assets own:
- CSS
- enhancement JS
- images and icons
The important separation is:
- templates describe structure
- assets describe presentation and enhancement
Asset Publication And Hashed Delivery
Customer templates should reference logical asset names:
coil:href="asset('theme/assets/site.css')"
coil:src="asset('theme/assets/site.js')"
Coil then:
- publishes assets from the declared theme asset roots
- gives them hashed artifact paths
- records the active manifest
- injects logical-path to public-URL mappings into the render model
That keeps templates stable while allowing production-safe cache busting.
Head Metadata And JSON-LD Injection
Document head output is not only what the template wrote by hand.
After the template renders, the runtime can inject:
- description
- canonical URL
- robots
- alternate locale links
- Open Graph fields
- JSON-LD
This is why themes and SEO belong in one conceptual conversation. The customer owns the visible document shell, but the runtime owns the search-facing metadata contract.
Where Accessibility Fits
A theme is not successful if it looks branded but breaks semantics.
At the theme level, the app still owns:
- landmarks
- skip links
- visible focus
- contrast
- usable language and theme controls
That is why rendering, assets, and accessibility are tightly coupled in Coil.
Common Mistakes
Treating the theme as only CSS
The theme includes templates, assets, and the customer-owned shell.
Rebuilding application state in site.js
Enhancement scripts should improve the HTML-first path, not replace it.
Hardcoding final asset URLs in templates
Use logical asset names and the asset helper instead.
Assuming SEO metadata must be hand-authored in every page template
The runtime already has a metadata decoration stage.
Supporting Implementation And Repo Examples
Concrete supporting files:
apps/shoppr/templates/layouts/base.htmlapps/shoppr/templates/layouts/storefront.htmlapps/shoppr/templates/pages/home.htmlapps/gitly/templates/gitly/home.htmlapps/gitly/theme/assets/site.jscrates/coil-runtime/src/render/model.rscrates/coil-runtime/src/render/seo.rscrates/coil-assets/src/release.rs