ArcMoon 1.1.0

Start

Language

Reference

Nothing matches “”.

↑↓ move Enter open Esc close

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.name props: one CSS property on one element, like css.color: "red".
  • --name props: a CSS variable on one element, read by your CSS with var(--name).

[style] blocks

The body of a [style] is CSS. ArcMoon only reads ${ }$ in it; everything else is plain CSS:

index.arcm
${ 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]
HTML ArcMoon builds
<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">
  • #ccc and #main are 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 \[end and \${ 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:

Card.arcm
[div = class: "card"]
  [h2]Card title[end]
  [slot!]
[end]
[style]
  .card { padding: 1rem; border: 1px solid #ddd; }
  h2 { color: tomato; }
[end]
index.arcm
[import = Card: "./Card.arcm" !]
[h2]Page title[end]
[Card][p]Inside the card[end][end:Card]
HTML ArcMoon builds
<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-rvah5h to the component's elements and to its selectors, so h2 { 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

Spinner.arcm
[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]
index.arcm
[import = Spinner: "./Spinner.arcm" !]
[Spinner!]
HTML ArcMoon builds
<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: .dark can be on the page's <body>.
  • @keyframes spin gets the component's suffix, and the animation follows, so two components can both have a spin.
  • Rules inside @media and @supports are 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%" !]
HTML ArcMoon builds
<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, not css.fontSize.
  • width stays the attribute, css.width is the CSS property. You choose each time.
  • Numbers are written as they are: css.opacity: 0.5. false leaves 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:

Progress.arcm
${ 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]
index.arcm
[import = Progress: "./Progress.arcm" !]
[Progress = value: 30 !]
[Progress = value: 80 !]
HTML ArcMoon builds
<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 .fill reads 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:

index.arcm
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]
Result
  • css.font-size follows size; --hue follows hue, and the CSS reads it.
  • A page's [style] can hold runtime ${ }$ 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.
dist/ after arcm build
index.html            <style>…all of the page's CSS…</style>
assets/
  logo-5DGK2KXQ.png   ← a url() file from the CSS, copied and renamed
  • url() files (images, fonts) are copied to assets/ with a hash in the name, and the CSS is rewritten to match.
  • @import "normalize.css"; loads an npm package's CSS, with no bundle entry.
  • With externalStyles: true in arcmoon.config.js, the CSS goes to assets/page-[hash].css instead of inside the page.
  • Left as written: links to other sites, a [link] or [style] with other props (like media:), 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 bracket

In 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.