Language
Styles
[style], scoped component styles, --name and css. props.
Three ways to style
[style]…[end]: a block of CSS. Global in a page, scoped in a component.css.nameprops: one CSS property on one element, likecss.color: "red".--nameprops: a CSS variable on one element, read by your CSS withvar(--name).
[style] blocks
The body of a [style] is CSS. ArcMoon only reads ${ }$ in it; everything else is plain CSS:
${ import theme from "./theme.json"; }$
[h1]Welcome[end]
[input = type: "text", placeholder: "Name" !]
[style]
:root { --brand: ${ theme.brand }$; }
h1 { color: var(--brand); }
#main, input[type="text"] { border: 1px solid #ccc; }
h1::after { content: "\2014"; }
[end]<style>:root{--brand: #d7263d}h1{color:var(--brand)}#main,input[type=text]{border:1px solid #ccc}h1:after{content:"\2014"}</style><h1>Welcome</h1>
<input type="text" placeholder="Name">
#cccand#mainare CSS, not comments.input[type="text"]is a selector, not a block.${ }$values are written into the CSS, like${ theme.brand }$from a JSON file.- Backslashes are kept, so CSS escapes like
"\2014"work. Only\[endand\${are special. - The CSS is minified and moved into one
<style>in the<head>.
A [style] in a page is global: it styles the whole page, components included.
Scoped component styles
A [style] in a component only styles that component's own elements:
[div = class: "card"]
[h2]Card title[end]
[slot!]
[end]
[style]
.card { padding: 1rem; border: 1px solid #ddd; }
h2 { color: tomato; }
[end][import = Card: "./Card.arcm" !]
[h2]Page title[end]
[Card][p]Inside the card[end][end:Card]<style>.card[data-a-rvah5h]{padding:1rem;border:1px solid #ddd}h2[data-a-rvah5h]{color:tomato}</style><h2>Page title</h2>
<div class="card" data-a-rvah5h>
<h2 data-a-rvah5h>Card title</h2>
<p>Inside the card</p>
</div>
- ArcMoon adds an attribute like
data-a-rvah5hto the component's elements and to its selectors, soh2 { color: tomato }only matches the card's<h2>, not the page's. - The attribute comes from the file, so it is the same on every build.
- The CSS is written once, however many times the component is used, and only on pages that use it.
How far scoping reaches
- It keeps the component's CSS in. It doesn't keep the page's CSS out: resets, fonts and theme colors still reach inside.
- The component wins a tie. When a page rule and a component rule set the same property, the component's rule is more specific.
- Slot content is the caller's. The
<p>above came from the page, so the card's CSS doesn't style it. - Child components are their own. A parent component's CSS doesn't reach inside a component it uses.
:global, @keyframes and @media
[div = class: "spinner"][end]
[p]Loading[end]
[style]
:global(.dark) p { color: white; }
.spinner { animation: spin 1s linear infinite; }
@keyframes spin { to { rotate: 1turn; } }
@media (max-width: 600px) { p { font-size: 14px; } }
[end][import = Spinner: "./Spinner.arcm" !]
[Spinner!]<style>.dark p[data-a-672ft5]{color:#fff}.spinner[data-a-672ft5]{animation:spin-672ft5 1s linear infinite}@keyframes spin-672ft5{to{rotate:1turn}}@media(max-width:600px){p[data-a-672ft5]{font-size:14px}}</style><div class="spinner" data-a-672ft5></div>
<p data-a-672ft5>Loading</p>
:global(.dark)leaves that part unscoped:.darkcan be on the page's<body>.@keyframes spingets the component's suffix, and theanimationfollows, so two components can both have aspin.- Rules inside
@mediaand@supportsare scoped too.
css. props
A prop starting with css. sets one CSS property on that element. No [style] block needed:
[p = css.color: "white", css.background: "black", css.font-size: "18px"]Hello[end]
[img = src: "logo.svg", width: 100, css.width: "50%" !]<p style="color: white; background: black; font-size: 18px">Hello</p>
<img src="logo.svg" width="100" style="width: 50%">
- Names are CSS names, with dashes:
css.font-size, notcss.fontSize. widthstays the attribute,css.widthis the CSS property. You choose each time.- Numbers are written as they are:
css.opacity: 0.5.falseleaves the property out. - On a component,
css.props go onto its outer element.
--name props
A prop starting with -- sets a CSS variable on that element. The CSS stays the same; each element brings its own value:
${ const { value = 0 } = ArcMoon.props(); }$
[div = class: "bar"]
[div = class: "fill", --progress: ${ value + "%" }$][end]
[end]
[style]
.bar { background: #eee; height: 8px; }
.fill { width: var(--progress); height: 100%; background: tomato; }
[end][import = Progress: "./Progress.arcm" !]
[Progress = value: 30 !]
[Progress = value: 80 !]<style>.bar[data-a-1oa216m]{background:#eee;height:8px}.fill[data-a-1oa216m]{width:var(--progress);height:100%;background:tomato}</style><div class="bar" data-a-1oa216m>
<div class="fill" data-a-1oa216m style="--progress: 30%"></div>
</div>
<div class="bar" data-a-1oa216m>
<div class="fill" data-a-1oa216m style="--progress: 80%"></div>
</div>
- One stylesheet, two widths: each
.fillreads its own--progress. - Like any CSS variable, children inherit it: set it on a parent, use it inside.
- This is how a component's style differs per use: its
[style]is shared, so pass the difference as a--prop.
style:, css. and -- props combine in the order written: [div = style: "margin: 0", css.color: "red", --x: "1"] gives style="margin: 0; color: red; --x: 1".
Styles that change
css. and -- props take live values, so styles can follow signals. Drag the sliders:
runtime ${
import { signal } from "arcmoon/reactive";
const size = signal(18);
const hue = signal(350);
}$
[input = type: "range", min: 12, max: 40, value: "18", oninput: runtime ${ (e) => size(Number(e.target.value)) }$ !]
[input = type: "range", min: 0, max: 360, value: "350", oninput: runtime ${ (e) => hue(Number(e.target.value)) }$ !]
[p = css.font-size: runtime ${ size() + "px" }$, --hue: runtime ${ hue() }$]Live style[end]
[style]
p { color: hsl(var(--hue) 70% 45%); }
[end]
css.font-sizefollowssize;--huefollowshue, and the CSS reads it.- A page's
[style]can holdruntime ${ }$too: the whole<style>updates when it changes. - A component's
[style]can't: one stylesheet is shared by every use. Use a--prop instead.
Stylesheets and files
ArcMoon bundles each page's CSS into one stylesheet, in this order:
- 1. Local CSS files:
[link = rel: "stylesheet", href: "./base.css" !], with their@imports followed. - 2. The page's
[style]blocks. - 3. Component styles, last.
index.html <style>…all of the page's CSS…</style>
assets/
logo-5DGK2KXQ.png ← a url() file from the CSS, copied and renamedurl()files (images, fonts) are copied toassets/with a hash in the name, and the CSS is rewritten to match.@import "normalize.css";loads an npm package's CSS, with nobundleentry.- With
externalStyles: truein arcmoon.config.js, the CSS goes toassets/page-[hash].cssinstead of inside the page. - Left as written: links to other sites, a
[link]or[style]with other props (likemedia:), and[style]with live values.
Mistakes
Runtime code in a component's [style]
✗ runtime ${ }$ is not allowed in a component's [style]: one stylesheet is shared by every use. Use a -- prop instead, like [div = --color: runtime ${ … }$]# ✗ in Card.arcm
[p]Hi[end]
[style] p { color: runtime ${ color() }$; } [end]
# ✓ a live -- prop, read by the CSS
[p = --color: runtime ${ color() }$]Hi[end]
[style] p { color: var(--color); } [end]Different CSS for different uses
⚠ Card.arcm:3:1 [style] gives different CSS for different uses of this component; only the first is kept. Use -- props for values that differ per use# ✗ the [style] reads a prop, so each use would need its own CSS
${ const { color } = ArcMoon.props(); }$
[p]Hi[end]
[style] p { color: ${ color }$; } [end]
# ✓ pass the value as a -- prop
${ const { color } = ArcMoon.props(); }$
[p = --color: ${ color }$]Hi[end]
[style] p { color: var(--color); } [end]CSS that can't be read
✗ Card.arcm:4:3 CSS error in [style]: Unclosed bracketIn a component, ArcMoon must read the CSS to scope it, so broken CSS stops the build at the line inside the [style]. Look for a missing }, ] or ;.
A value that adds more CSS
✗ css.width on [p] can't contain ";", "{" or "}": it would add other CSS to the element. Got "1px; background: red"A css. or -- value holds one value. Set each property with its own prop: css.width: "1px", css.background: "red".
A property name that doesn't exist
⚠ page.arcm:1:1 css.colr on [p] is not a CSS property (did you mean css.color?)A warning: the property is still written, but the browser ignores it. Fix the name.