Getting started: SolidStart
Flash-free SSR: entry-server.tsx writes the theme from a cookie on <html>; component markup goes straight into Solid JSX (class, as in HTML)
1. Create the app
Tested with SolidStart 2.0 and solid-js 1.9 (the template needs Node 24+). The scaffolder doesn't install dependencies for you.
npm create solid@latest -- my-app --solidstart --v2 --ts --template basic
cd my-app
npm install
2. Install the package
You only need the package's CSS: valyro-ui/styles.css (plus the theme file if your theme isn't one of the two built-in ones). Fonts are separate files: the vercel theme (the ThemeProvider default) uses Geist, so import valyro-ui/fonts/geist.css and valyro-ui/fonts/geist-mono.css next to the styles, or text falls back to the system font; glass uses system fonts and needs none. npm installs react and react-dom into node_modules as peer dependencies, but they stay out of your bundle unless you import them. Until the package is published, install the tarball: npm install ../valyro-ui-0.1.0.tgz.
npm install valyro-ui
3. Add the styles
The template's app.css styles main, h1, p and a globally and clashes with the theme — import the package styles instead.
import { MetaProvider, Title } from "@solidjs/meta";
import { Router } from "@solidjs/router";
import { FileRoutes } from "@solidjs/start/router";
import { Suspense } from "solid-js";
import "valyro-ui/styles.css";
export default function App() {
return (
<Router
root={props => (
<MetaProvider>
<Title>ValyroUI on SolidStart</Title>
<Suspense>{props.children}</Suspense>
</MetaProvider>
)}
>
<FileRoutes />
</Router>
);
}
4. Flash-free theme
data-theme (vercel, glass or a theme loaded from its own file) and data-mode (light or dark) on <html> turn the theme on: the --ui-* tokens and the page background depend on them. The document renders only on the server: the values come from the request cookies (known values only), and hydration doesn't touch <html>.
// @refresh reload
import { createHandler, StartServer } from "@solidjs/start/server";
import { getRequestEvent } from "solid-js/web";
// Cookie values are user input: only known values reach the HTML.
function cookie(name: string, allowed: string[], fallback: string) {
const header = getRequestEvent()?.request.headers.get("cookie") ?? "";
const value = header
.split(";")
.map(part => part.trim().split("="))
.find(([key]) => key === name)?.[1];
return value && allowed.includes(value) ? value : fallback;
}
export default createHandler(() => (
<StartServer
document={({ assets, children, scripts }) => (
<html
lang="en"
data-theme={cookie("ui-theme", ["glass", "vercel"], "glass")}
data-mode={cookie("ui-mode", ["light", "dark"], "light")}
>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="icon" href="/favicon.ico" />
{assets}
</head>
<body>
<div id="app">{children}</div>
{scripts}
</body>
</html>
)}
/>
));
5. Switcher and markup
The markup is exactly what the React components render (Button, Badge, TextInput, Alert, Card). Only visual components work without React (see the list in the HTML + CSS guide); for any of them, the “Usage” example on the component's page shows the markup when this framework is selected. In Solid JSX it goes in almost as is: just remove focusable="false" from <svg> — Solid's types don't include that attribute, and tsc rejects it.
import { createSignal, onMount } from "solid-js";
function save(key: "theme" | "mode", value: string) {
document.documentElement.dataset[key] = value;
document.cookie = `ui-${key}=${value}; path=/; max-age=31536000; samesite=lax`;
}
export default function Home() {
// The server already wrote the cookie values on <html> (entry-server.tsx); pick them up after hydration.
const [theme, setTheme] = createSignal("glass");
const [mode, setMode] = createSignal("light");
onMount(() => {
setTheme(document.documentElement.dataset.theme ?? "glass");
setMode(document.documentElement.dataset.mode ?? "light");
});
return (
<main style={{ display: "grid", "justify-items": "start", gap: "16px", "max-width": "560px", padding: "32px" }}>
<div style={{ display: "flex", gap: "8px" }}>
<button class="ui-button" data-variant="secondary" data-size="md" type="button" onClick={() => save("theme", setTheme(theme() === "glass" ? "vercel" : "glass"))}>
<span class="ui-button__label">Toggle theme</span>
</button>
<button class="ui-button" data-variant="secondary" data-size="md" type="button" onClick={() => save("mode", setMode(mode() === "light" ? "dark" : "light"))}>
<span class="ui-button__label">Toggle mode</span>
</button>
</div>
<button class="ui-button" data-variant="primary" data-size="md" type="button"><span class="ui-button__label">Deploy</span></button>
<span class="ui-badge" data-color="green" data-variant="subtle" data-size="md"><span class="ui-badge__label">Ready</span></span>
<div class="ui-field ui-text-input" data-size="md"><input aria-label="Email" placeholder="[email protected]" type="text" class="ui-field__input ui-text-input__input" /></div>
<div role="status" class="ui-alert" data-variant="info">
<span class="ui-alert__icon" aria-hidden="true"><svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" class="ui-icon"><circle cx="12" cy="12" r="10"></circle><path d="M12 16v-4"></path><path d="M12 8h.01"></path></svg></span>
<div class="ui-alert__content"><div class="ui-alert__title">Heads up</div><div class="ui-alert__description">Static markup, no React at runtime.</div></div>
</div>
<div class="ui-card ui-panel" data-variant="default" data-padding="md">
<div class="ui-card__header"><h3 class="ui-card__title">Project</h3><p class="ui-card__description">Production deployment</p></div>
<div class="ui-card__content">Body</div>
<div class="ui-card__footer"><button class="ui-button" data-variant="secondary" data-size="md" type="button"><span class="ui-button__label">Open</span></button></div>
</div>
</main>
);
}
6. Build and run
The console is clean in production. In npm run dev, SolidStart 2.0.6 on its own (even with a fresh template) logs SyntaxError … resolve-uri.umd.js does not provide an export named 'default'; fix it by adding optimizeDeps: { include: ["@solidjs/start > @jridgewell/trace-mapping > @jridgewell/resolve-uri"] } to vite.config.ts.
npm run build
npm start