Install @eona-x/ui-toolkit-svelte, render a first component, build a static site, theme it.
Recommended for JavaScript projects.
npm create @eona-x/app my-site
cd my-site
npm run dev # http://localhost:5173
npm run build # static site in build/
That is a complete, branded EONA-X site — SvelteKit with adapter-static on Vite, the EONA-X header
and footer, two pages, and the toolkit at 0.9.2 — with its dependencies installed. The rest of
this guide is what it contains, step by step.
npm install @eona-x/ui-toolkit-svelte@0.9.2
Svelte 5 is a peer dependency. The stylesheet, @eona-x/ui-toolkit-css, comes with the package at
the same version. The components are .svelte files generated from the Yew source, compiled by
your own Svelte, with no WebAssembly unless you opt into the /wasm entry.
Import the stylesheet once, and render the components inside an element with the class eona-ui
— every rule in the stylesheet is scoped under it, so nothing outside is touched.
<script>
import '@eona-x/ui-toolkit-css';
import { Button, MetricTile, Pill } from '@eona-x/ui-toolkit-svelte';
</script>
<div class="eona-ui">
<MetricTile label="Datasets" value="12" help="Unchanged since the last run" />
<Pill tone="ok">Mobility</Pill>
<Button label="Explore the catalogue" href="https://eona-x.eu/" />
</div>
A Rust Children prop is a Svelte snippet: pass children as element content, and any other slot
— ShapeCard's demo — as a {#snippet demo()}…{/snippet} block. Every component is
presentational; interaction belongs to your app. The Components page renders every one with
its call.
SvelteKit with adapter-static renders every route to HTML at build time — on Vite underneath.
npm install @eona-x/ui-toolkit-svelte@0.9.2
npm install -D @sveltejs/kit @sveltejs/adapter-static @sveltejs/vite-plugin-svelte svelte vite
package.json:
{
"type": "module",
"scripts": { "dev": "vite dev", "build": "vite build" }
}
svelte.config.js:
import adapter from '@sveltejs/adapter-static';
export default { kit: { adapter: adapter() } };
vite.config.js:
import { sveltekit } from '@sveltejs/kit/vite';
import { defineConfig } from 'vite';
export default defineConfig({ plugins: [sveltekit()] });
src/app.html:
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
%sveltekit.head%
</head>
<body>
<div style="display: contents">%sveltekit.body%</div>
</body>
</html>
src/routes/+layout.js — every route is rendered at build time:
export const prerender = true;
src/routes/+layout.svelte — the stylesheet and the scope, once for the whole site:
<script>
import '@eona-x/ui-toolkit-css';
let { children } = $props();
</script>
<div class="eona-ui">
{@render children()}
</div>
src/routes/+page.svelte:
<script>
import { Button, MetricTile, Pill } from '@eona-x/ui-toolkit-svelte';
</script>
<MetricTile label="Datasets" value="12" help="Unchanged since the last run" />
<Pill tone="ok">Mobility</Pill>
<Button label="Explore the catalogue" href="https://eona-x.eu/" />
npm run build writes build/. The prerender crawls every link and fails on one to a route that
does not exist — link to real pages, or to absolute URLs as here. npm create @eona-x/app produces
this, plus a shared header and footer in +layout.svelte and an about page.
Everything in the toolkit is presentational, so the default entry covers it all. For an interactive
Yew component — one with state and callbacks, which cannot be generated — the /wasm entry mounts
the real thing, compiled to WebAssembly:
<script>
import { YewMount } from '@eona-x/ui-toolkit-svelte/wasm';
// A function, so the WebAssembly loads only when the component mounts.
const load = () => import('my-yew-widgets').then(async (m) => (await m.default(), m));
</script>
<YewMount module={load} component="Counter" props={{ start: 3, label: 'Clicks' }} />
The Rust side exports one function, built with wasm-bindgen and Yew's csr feature:
#[wasm_bindgen]
pub fn mount(target: web_sys::Element, component: &str, props: JsValue) -> Result<Handle, JsValue>
where Handle has update(props) and destroy(). examples/svelte-wasm
is a complete one — the toolkit's Button and a stateful Counter — built with
wasm-pack build --target web. use:yew is the same thing as an action, for an element you
already have.
It has a cost: one component over WebAssembly is about 75 KB gzip against about 100 bytes
pre-rendered, it renders nothing on the server, and a Content-Security-Policy must allow
wasm-unsafe-eval. Reach for it only for what the default entry does not export.
@eona-x/ui-toolkit-css has a semantic layer named after the job rather than the ink —
--eona-surface-page, --eona-text-primary, --eona-border-subtle, --eona-accent — and a dark
variant of it, switched on the document:
<html data-theme="dark">
Use those tokens for your own surfaces and they follow the theme. The components do not yet: their rules still read the raw palette, so they render the same in both modes.
@eona-x/ui-toolkit-css/page.css is opt-in and global: the EONA-X page shell, for a site whose
whole page is an EONA-X page. It resets body, headings and links, and pads body by 156px so the
fixed SiteHeader does not cover the content. Leave it out when the components live inside your
own layout.
SiteHeader loads its logo from logo/logo-transparent.svg relative to the page. Serve it there —
the file ships in the stylesheet package, at
node_modules/@eona-x/ui-toolkit-css/logo/logo-transparent.svg (in SvelteKit, copy it to
static/logo/).