Skip to content
Velkin LogoVelkin Logo
Velkin
VELKIN/UI

Modern UI components for product teams and their AI agents. Lit core, typed wrappers, live MCP.

sales@velkinui.com

Product

  • Docs
  • Components
  • Studio
  • Pricing

Resources

  • Quick start
  • Blog
  • Changelog
  • Roadmap
  • Status

Project

  • About
  • Contact
  • License
  • Privacy
  • Terms

© 2026 Velkin. MIT core · Pro licenses available.

VELKIN

Theming

Customize Velkin with design tokens, VuThemeProvider, and CSS variables.

Quick example

import { VuThemeProvider } from "@velkin/react/theme-provider";
import { VuButton } from "@velkin/react/button";

export function App({ children }: { children: React.ReactNode }) {
  return (
    <VuThemeProvider persist preference="system">
      {children}
      <VuButton variant="solid" color="primary">
        Save
      </VuButton>
    </VuThemeProvider>
  );
}

Vue: @velkin/vue/theme-provider. Lit / HTML: import "@velkin/ui/theme-provider" and <vu-theme-provider persist>. Framework install steps: Installation.

How tokens work

You pass brand seeds (primary, radius, spacing, tint, …). The provider derives surfaces, borders, soft intents, and the full --vu-* scale. Prefer semantic tokens in your CSS over hard-coded hex.

LayerExamples
Global--vu-space-*, --vu-radius-*, font tokens
Semantic--vu-color-background, --vu-color-foreground, --vu-color-border, intent colors
ComponentAppearance props and documented ::part() / cssProps on each component

This docs site also ships design presets in the header palette control so you can preview the same components under different seeds. Light / dark preference stays a separate axis.

Using VuThemeProvider

Place the provider once near the root. Use a client / browser boundary if your framework requires it for interactive providers. Subpath import only — never the package root barrel.

  • persist — stores preference in localStorage (key theme) when scope includes root
  • preference — light, dark, or system (follows prefers-color-scheme)
  • scope — root (default, <html>), host (provider element only), or both
import { VuThemeProvider } from "@velkin/react/theme-provider";

export function App({ children }: { children: React.ReactNode }) {
  return (
    <VuThemeProvider persist preference="system">
      {children}
    </VuThemeProvider>
  );
}

Using CSS variables

Components resolve intents like primary and danger through the provider. In your own layout CSS, use the same tokens:

.page {
  background: var(--vu-color-background);
  color: var(--vu-color-foreground);
  padding: var(--vu-space-6);
  border-radius: var(--vu-radius-lg);
  border: var(--vu-border-width) solid var(--vu-color-border);
}

Explore live tokens in Studio, or open demos under Components and toggle the site theme switcher in the header.

Light and dark mode

Preference is what the user chose; mode is the resolved light or dark sheet. Use VuThemeSwitcher inside the provider so it reads context and updates preference.

type="button" — cycles preference
type="switch" — light · system · dark
import { VuThemeSwitcher } from "@velkin/react/theme-switcher";

// Cycle button (light → system → dark)
<VuThemeSwitcher type="button" variant="ghost" size="sm" />

// Explicit three-way control
<VuThemeSwitcher type="switch" variant="outline" size="sm" />

Listen with onVuTheme. Payload is in e.detail (preference and resolved mode).

<VuThemeSwitcher
  type="switch"
  onVuTheme={(e) => {
    console.log(e.detail.preference, e.detail.mode);
  }}
/>

Programmatically: call setPreference("dark") / togglePreference() on the provider element, or keep preference in React state and pass preference={pref}.

Avoid flash / first paint

On SSR or static HTML, inject theme tokens before interactive JS runs so light/dark does not flash. In React, use VuThemeHead and htmlThemeProps from @velkin/react/theme-head. Pass the same theme seeds to VuThemeProvider when branding.

import { VuThemeHead, htmlThemeProps } from "@velkin/react/theme-head";
import { VuThemeProvider } from "@velkin/react/theme-provider";

// Apply htmlThemeProps on the document <html> element in your app shell.
<html lang="en" {...htmlThemeProps}>
  <head>
    <VuThemeHead />
  </head>
  <body>
    <VuThemeProvider persist>{/* app */}</VuThemeProvider>
  </body>
</html>

Vite, Next.js, Vue, and Lit wiring differ slightly — follow the matching guide under Installation.

Customizing a single component

For one-off tweaks, use documented CSS parts and cssProps — not undocumented shadow internals:

vu-button::part(base) {
  /* only parts listed in the component docs */
}

Full guidance: Styling. Prefer tokens and appearance props when the change should apply system-wide.

Custom theme seeds

Pass a theme object of brand seeds. Velkin derives the rest — you rarely set raw --vu-* by hand.

import { VuThemeProvider } from "@velkin/react/theme-provider";

const brand = {
  primary: "#0d9488",
  success: "#16a34a",
  warning: "#d97706",
  danger: "#dc2626",
  radius: "0.75rem",
  spacing: "0.25rem",
  tint: "subtle", // "none" | "subtle" | "vivid"
  fontSans: "ui-sans-serif, system-ui, sans-serif",
};

export function App({ children }: { children: React.ReactNode }) {
  return (
    <VuThemeProvider theme={brand} persist>
      {children}
    </VuThemeProvider>
  );
}

Per-mode overrides when light and dark need different seeds:

const theme = {
  primary: "#6366f1",
  light: { tint: "subtle" },
  dark: { tint: "vivid", primary: "#818cf8" },
  // Escape hatch for individual tokens after derivation
  vars: {
    light: { "--vu-space-4": "1.25rem" },
    dark: { "--vu-shadow-surface": "0 1px 2px rgb(0 0 0 / 40%)" },
  },
};
  • primary — brand accent (focus, links, primary buttons)
  • success / warning / danger — intent colors; defaults apply if omitted
  • radius / spacing — base values; every --vu-radius-* and --vu-space-* step derives from them
  • tint — how strongly neutrals pick up the brand hue
  • vibrantpalette — provider prop for higher-contrast soft intent foregrounds
  • glass — provider prop for glass-style elevated surfaces (does not paint a page gradient)

Scoped themes

Nest a second provider with scope="host" to theme a panel without changing the whole document — useful for marketing strips, email previews, or Studio-style editors.

<VuThemeProvider persist>
  <AppChrome />
  <VuThemeProvider
    scope="host"
    preference="dark"
    persist={false}
    theme={{ primary: "#f97316" }}
  >
    <PreviewPane />
  </VuThemeProvider>
</VuThemeProvider>

Next steps

  • Finish setup on Installation for your framework
  • Styling — parts, cssProps, and tokens
  • Browse theme-provider and theme-switcher API demos
  • Design a full brand palette in Studio
PreviousInstallationNextPro license