Components
A component is a .azox file that declares what it accepts and renders markup. It is resolved at build time and inlined into its caller, so nothing about it exists at runtime.
Writing one
Declare props with props(), then use them in the markup.
<script>
const { title, body } = props();
</script>
<article class="card">
<h2>{title}</h2>
<p>{body}</p>
</article>Using one
Import it, then use it as a capitalised tag. Lowercase tags are always HTML elements, so the distinction is visible in the markup.
<script>
import Card from '../components/Card.azox';
import { signal } from 'azox/reactivity';
const count = signal(0);
</script>
<main>
<Card title="Static" body="Plain text" />
<Card title="Live" body={count()} />
<button on:click={() => count.set(count() + 1)}>Add one</button>
</main>Props stay reactive
A prop passed as an expression keeps the caller's expression. In the example above, clicking the button updates the text inside the second card and nothing else — the component boundary changes nothing about how fine-grained the update is.
A prop passed as a plain string compiles to static text with no effect attached at all, so constants cost nothing.
Slots
<slot /> is where the children nested inside the tag are placed.
<script>
const { heading } = props();
</script>
<section>
<header><h1>{heading}</h1></header>
<slot />
</section><script> import Layout from '../components/Layout.azox'; import Card from '../components/Card.azox'; </script> <Layout heading="My site"> <Card title="Nested" body="Components work inside slots too" /> <p>Any markup can go here.</p> </Layout>
Undeclared props are rejected
Passing something a component never declared is almost always a typo, and silently dropping it hides the mistake. The compiler stops instead:
Azox: <Card> was given "subtitle", which it does not declare. It accepts: title, body.
Components can hold state
A component may declare signals of its own. Each use of it gets its own copy, so two <Counter /> tags hold two independent counts.
<script>
import { signal } from 'azox/reactivity';
const { label } = props();
const count = signal(0);
</script>
<div class="counter">
<span>{label}: {count()}</span>
<button on:click={() => count.set(count() + 1)}>+</button>
</div><script> import Counter from '../components/Counter.azox'; </script> <main> <Counter label="Left" /> <Counter label="Right" /> </main>
Clicking one counter leaves the other alone. There is no component instance behind this — the compiler wraps each use in an ordinary JavaScript scope, so the two count declarations are simply two different local bindings.
A component with no logic of its own is still inlined directly, with no wrapper at all. You only pay for a scope when you declare something that needs one.
Lifting state up
When two components need to agree on a value, keep it in the page and pass it down as a prop, the way you would anywhere else.
<!-- components/Readout.azox: display only -->
<script>
const { label, value } = props();
</script>
<p>{label}: {value}</p>
<!-- pages/index.azox: owns the shared value -->
<script>
import Readout from '../components/Readout.azox';
import { signal } from 'azox/reactivity';
const count = signal(0);
</script>
<main>
<Readout label="Clicks" value={count()} />
<button on:click={() => count.set(count() + 1)}>Add</button>
</main>Layouts and the document head
A component can carry a <head> block. Put the stylesheet, fonts and scripts every page needs in one shared component, and stop repeating them in each page.
<head> <link rel="stylesheet" href="/style.css" /> <link rel="preload" href="/fonts/sora.woff2" as="font" type="font/woff2" crossorigin /> </head> <div class="shell"> <header>My site</header> <slot /> <footer>MIT licensed</footer> </div>
<head> <title>Home — my site</title> </head> <script> import Shell from '../components/Shell.azox'; </script> <Shell> <main>Just the page's own content.</main> </Shell>
The blocks are merged: the component's lines come first, and the page's come after so a page has the last word. Identical lines appear once, and a component used twice contributes once — a stylesheet does not get linked repeatedly.
A <title> or a <meta name="…"> that the page sets replaces the component's rather than joining it, since a document may hold only one of each. So a layout can set a sensible default title and each page override it.
What the compiler produces
The markup is inlined, so the emitted code is what you would have written by hand. There is no component instance, no lifecycle, and nothing to reconcile.
| Rule | Detail |
|---|---|
| Naming | Capitalised tag is a component, lowercase is HTML |
| Importing | A normal import statement, pointing at a .azox file |
| Props | Declared with props(); undeclared ones are an error |
| Missing prop | Renders as undefined, not a crash |
| Events | on: attributes on a component are not treated as props |
| Cycles | Detected at build time, with the component named |
Next
- Routing — files to URLs
- Styling & assets — CSS, fonts and images