Layers
Layers let a site use components and layouts that live in another repo or
directory — without copying them in. Declare them in site.toml:
toml[layers]use = [ "./vendor/components", # a directory "pkg:epresso_ui", # an installed Python package "github:owner/epresso-components@v1", # a repo, pinned to a tag]
Then use them exactly like your own components:
epresso<Button variant="primary">Save</Button>
What a layer is
A layer is a directory shaped like a site root. epresso reads these subdirectories, always after the site's own (the site wins on a clash):
treemy-components/ components/ # <Name>.ep, resolved by basename across subdirs layouts/ # shell components (searched after components/) styles/ assets/ # files asset('name') can resolve public/ # copied to the output root (a site file of the same path wins)
Everything else in the directory is ignored. Per-component scoped <style> and
<script> work exactly as they do for site components, so a layer carries its
own styles and behaviour with no extra setup. Global CSS travels in a
<style is:global> block inside a component.
Sources
| Form | Example | How it is fetched |
|---|---|---|
| directory | ./vendor/components, path:/abs/dir |
used in place |
| package | pkg:epresso_ui |
importlib — must be installed (uv add <pkg>) |
| GitHub repo | github:owner/repo@v1, https://github.com/owner/repo@v1 |
release tarball (no git needed) |
| other git | git+https://host/org/repo@v1, git@host:org/repo |
shallow git clone --depth=1 |
Remote sources are downloaded once into .cache/layers/ and reused. The @ref
(tag, branch, or commit) is part of the cache path, so changing it fetches a
different revision; delete .cache/layers/ to re-fetch a moved tag. Without a
ref, the repository's HEAD is used — pin a ref for reproducible builds.
Override order
The site always wins:
treesite/ components/ layouts/ ← searched first─────────────────────────────layer 1 (first entry in [layers].use)layer 2...
A site file with the same basename overrides the layer's; among layers, the
first declared wins. Use <Name:subdir /> to disambiguate explicitly when two
roots ship the same basename. Run epresso layers to see what resolved where:
console$ epresso layersLayers (the site's own components/ + layouts/ win; then these in order): path ./vendor/components → /home/me/site/vendor/components repo github:owner/epresso-components@v1 → /home/me/site/.cache/layers/github.com__owner__epresso-components@v1-1a2b3c4d
Editing a layer
- A directory layer is live: edit it and rebuild —
epresso devwatches layer directories outside the project too. - A repo layer is a cached snapshot: push a new tag upstream and bump the
@ref, or clear.cache/layers/.
Scope
Layers contribute components/, layouts/, styles/, assets/ and
public/. They do not contribute pages/, content/ or site.toml — the
site owns those (a plugin can serve a layer's page with caps.add_route).
Because the site wins every name clash, give a layer's stylesheet and shell
names that won't collide (the docs theme uses DocsBase and styles/docs.css).
Packages vs plugins
pkg: is only a convenient way to point at an installed package's directory. A
package that needs to do work at build time (register globals, transform HTML,
add a collection) should ship a Plugin instead — the two are
independent and can be combined.
UnoCSS / Tailwind caveat
If a layer's components are styled with utility classes that are scanned out of
the component files at build time (e.g. UnoCSS @source), a consumer's scan
config will not see files living in another repo or in .cache/layers/. Ship
prebuilt CSS in the layer rather than depending on the consumer's utility
build.
Was this page helpful?