ValyroUI Русская версия

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

Other frameworks