Skip to content
CodeAtlas UI
On this page Release channels

Getting started

Installation

Install @codeatlasdev/ui and wire the theme into SvelteKit, Astro or plain Vite without a flash of the wrong theme.

CodeAtlas UI needs Svelte 5 (runes mode). In a SvelteKit or Astro app, bunx @codeatlasdev/ui init does the whole setup below for you, and prints a diff instead of overwriting any file that already exists. Setup is three things: import the stylesheet once, inject the theme script before first paint, and start the colorScheme store in your root layout.

bun add @codeatlasdev/ui

The core bundle stays small. Heavy features are separate entry points with optional peer dependencies, so install only what you use:

Feature Import from Also install
Charts @codeatlasdev/ui/charts echarts
DataTable @codeatlasdev/ui/table @tanstack/svelte-table
CodeSnippet @codeatlasdev/ui/code shiki
Masked and money fields @codeatlasdev/ui/forms nothing

Icons come from @lucide/svelte, which the package already depends on.

Release channels

Channel Install When to use it
latest bun add @codeatlasdev/ui Production. Semver: breaking changes only in a major.
beta bun add @codeatlasdev/ui@beta The next minor, feature-complete. Try it before it ships.
alpha bun add @codeatlasdev/ui@alpha The next major while it is still moving. Expect breaking changes between alphas.

Every version is published from CI with an npm provenance statement, so you can check which commit and workflow built it (npm audit signatures).

SvelteKit

@codeatlasdev/ui supports SvelteKit 2 (^2.12) and SvelteKit 3. The steps below are the same on both; the differences are in SvelteKit 3. bunx @codeatlasdev/ui init reads the installed Kit version and writes the right imports.

What init writes

bunx @codeatlasdev/ui init creates the files below when they are missing. A file that already exists is never overwritten: init prints a diff and applies it only with --write. Re-running it changes nothing once the setup is complete.

File What init puts there If the file exists
src/lib/theme.ts the shared theme object left alone
vite.config.ts caThemePlugin(theme) and caErrorPlugin({ scaffold: true }), after sveltekit(), in one @codeatlasdev/ui/vite import adds only the plugin that is missing
src/hooks.server.ts handle = sequence(caThemeHandle(theme)) and handleError = createErrorHandler() adds only what is missing; reports manual if you already define a handle of your own; never touches your handleError
src/routes/+layout.svelte the stylesheet import and colorScheme.init() in onMount adds only the missing import or call
src/app.html the viewport meta with viewport-fit=cover, interactive-widget=resizes-content rewrites only the viewport meta

caErrorPlugin({ scaffold: true }) then creates src/routes/+error.svelte and src/error.html the first time Vite runs (dev or build), and never overwrites them. The result is the setup below, which you can also write by hand.

Full setup

  1. Register both Vite plugins. caThemePlugin bundles the ESM-only dependencies for SSR (@tanstack/svelte-table, shiki) and warns at build time if the theme script is not injected. caErrorPlugin creates the error pages when they are missing (scaffold: true), or only warns without it. Import the theme with its .ts extension: Vite loads the config with Node’s native loader, which does not resolve extensionless imports (Vite 8 warns about it). That needs "allowImportingTsExtensions": true in tsconfig.json; SvelteKit’s generated base config already sets it.

    // vite.config.ts
    import { sveltekit } from "@sveltejs/kit/vite";
    import { caErrorPlugin, caThemePlugin } from "@codeatlasdev/ui/vite";
    import { defineConfig } from "vite";
    import { theme } from "./src/lib/theme.ts";
    
    export default defineConfig({
      plugins: [sveltekit(), caThemePlugin(theme), caErrorPlugin({ scaffold: true })],
    });
  2. Inject the theme script and sanitize server errors in the server hook. caThemeHandle puts the brand presets and the init script right after <head> of every SSR and prerendered page, so the first paint already has the right scheme and preset. createErrorHandler turns every unexpected server error into { message, code, id } without leaking the message of a 5xx; pass a locale or a report callback when you need them.

    // src/hooks.server.ts
    import { sequence } from "@sveltejs/kit/hooks";
    import { caThemeHandle, createErrorHandler } from "@codeatlasdev/ui/kit";
    import { theme } from "$lib/theme";
    
    export const handle = sequence(caThemeHandle(theme));
    export const handleError = createErrorHandler();
  3. Define the theme once. Both the hook and the plugin take the same object. An empty object is valid and gives you the built-in amber preset in dark mode.

    // src/lib/theme.ts
    import type { ThemeOptions } from "@codeatlasdev/ui/vite";
    
    export const theme: ThemeOptions = {
      defaultScheme: "system",
    };
  4. Import the stylesheet and start the store in the root layout.

    <!-- src/routes/+layout.svelte -->
    <script>
      import "@codeatlasdev/ui/styles/global.css";
      import { colorScheme } from "@codeatlasdev/ui";
      import { onMount } from "svelte";
    
      let { children } = $props();
      onMount(() => colorScheme.init());
    </script>
    
    {@render children()}
  5. Set the viewport meta in src/app.html. See Mobile for what each part does.

    <meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover, interactive-widget=resizes-content" />

SvelteKit 3

Kit 3 changed three things this setup touches. init handles the first and reports the other two (it never rewrites svelte.config.js or tsconfig.json).

  1. Configuration moves into the Vite plugin. svelte.config.js is ignored (config_file_unsupported) and the kit namespace is gone (config_kit_namespace). Pass the options directly to sveltekit(), next to the two plugins:

    // vite.config.ts
    import adapter from "@sveltejs/adapter-auto";
    import { sveltekit } from "@sveltejs/kit/vite";
    import { caErrorPlugin, caThemePlugin } from "@codeatlasdev/ui/vite";
    import { defineConfig } from "vite";
    import { theme } from "./src/lib/theme.ts";
    
    export default defineConfig({
      plugins: [sveltekit({ adapter: adapter() }), caThemePlugin(theme), caErrorPlugin({ scaffold: true })],
    });
  2. tsconfig.json extends $app/tsconfig. .svelte-kit/tsconfig.json is no longer generated (tsconfig_extends_missing):

    { "extends": "$app/tsconfig" }
  3. $lib is removed (module_removed_lib). Use the #lib/* import that sv create adds to package.json ("imports": { "#lib/*": "./src/lib/*" }), written with the extension. In the hook, import the theme as #lib/theme.ts:

    // src/hooks.server.ts
    import { theme } from "#lib/theme.ts";

    Without a #lib map, use a relative path (./lib/theme.ts from src/hooks.server.ts). Everything else (the layout, the stylesheet import, colorScheme.init() and the viewport meta) is identical to Kit 2.

Astro

Use the integration together with @astrojs/svelte. caUI injects the theme into the <head> of every page and handles the SSR bundling.

bun add @codeatlasdev/ui @astrojs/svelte svelte
// astro.config.mjs
import svelte from "@astrojs/svelte";
import { caUI } from "@codeatlasdev/ui/astro";
import { defineConfig } from "astro/config";
import { theme } from "./src/theme.ts";

export default defineConfig({ integrations: [svelte(), caUI(theme)] });
// src/theme.ts
import type { ThemeOptions } from "@codeatlasdev/ui/astro";

export const theme: ThemeOptions = { defaultPreset: "amber", defaultScheme: "dark" };

Import the stylesheet once in your base layout and add the viewport meta:

---
// src/layouts/Base.astro
import "@codeatlasdev/ui/styles/global.css";
---
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover, interactive-widget=resizes-content" />
  </head>
  <body><slot /></body>
</html>

Components are Svelte islands. Add a client directive to anything interactive (client:visible or client:load); purely visual components render on the server with no JavaScript. In islands, call colorScheme.init() from one island that is always on the page, such as your header, or from a small script.

Plain Vite

caThemePlugin also works without SvelteKit: it injects the theme head through transformIndexHtml.

// vite.config.ts
import { svelte } from "@sveltejs/vite-plugin-svelte";
import { caThemePlugin } from "@codeatlasdev/ui/vite";
import { defineConfig } from "vite";

export default defineConfig({ plugins: [caThemePlugin(), svelte()] });
// src/main.ts
import "@codeatlasdev/ui/styles/global.css";
import { colorScheme } from "@codeatlasdev/ui";
import { mount } from "svelte";
import App from "./App.svelte";

colorScheme.init();
mount(App, { target: document.getElementById("app")! });

Verify

<script>
  import { Button, ThemeToggle } from "@codeatlasdev/ui";
</script>

<Button variant="filled">It works</Button>
<ThemeToggle />

Reload with the toggle on dark and check that there is no white flash. Continue with Theming.