Convergence UI 2.0
Convergence UI 2.0 pairs a typed theme engine with a floating React editor so teams can ship semantic CSS-variable themes, import existing token sets, validate accessibility, and export clean theme artifacts.
npm install convergence-uiyarn add convergence-uipnpm add convergence-uiDrop the floating editor into your shell to edit semantic theme tokens live inside your app.
import { Convergence } from "convergence-ui";
export default function AppShell({
children,
}: {
children: React.ReactNode;
}) {
return (
<>
{children}
<Convergence />
</>
);
}Use the headless engine in scripts, tooling, tests, or custom app flows when you want to read, update, validate, import, or export themes without mounting the editor.
import {
ConvergenceEngine,
DEFAULT_THEME_DEFINITION,
scoreThemeAccessibility,
} from "convergence-ui";
const engine = new ConvergenceEngine(DEFAULT_THEME_DEFINITION, {
autoApply: false,
});
engine.setOklch("primary", { l: 0.64, c: 0.18, h: 262 }, { mode: "light" });
engine.setTypography({ fontSans: "Inter, sans-serif" });
const tailwindTheme = engine.export("tailwind-v4");
const jsonTheme = engine.export("json");
const report = scoreThemeAccessibility(engine.getDefinition().themes.light);Use the SSR helper to emit theme CSS from the server for Next.js and other React SSR setups.
import {
ConvergenceThemeStyle,
DEFAULT_THEME_DEFINITION,
} from "convergence-ui";
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="en">
<head>
<ConvergenceThemeStyle definition={DEFAULT_THEME_DEFINITION} />
</head>
<body>{children}</body>
</html>
);
}Themes are defined with a typed ThemeDefinition that stores semantic light and dark tokens alongside typography, layout, and shadow settings.
import type { ThemeDefinition } from "convergence-ui";
const theme: ThemeDefinition = {
themes: {
light: { /* semantic OKLCH tokens */ },
dark: { /* semantic OKLCH tokens */ },
},
typography: {
fontSans: "Inter, sans-serif",
fontSerif: "Georgia, serif",
fontMono: '"JetBrains Mono", monospace',
letterSpacing: "0px",
},
layout: {
radius: "0.75rem",
borderWidth: "1px",
borderStyle: "solid",
},
shadows: {
"shadow-color": { l: 0, c: 0, h: 0, a: 1 },
"shadow-2xs": "0 1px 3px 0px oklch(0 0 0 / 0.05)",
"shadow-xs": "0 1px 3px 0px oklch(0 0 0 / 0.05)",
"shadow-sm": "0 1px 3px 0px oklch(0 0 0 / 0.10)",
shadow: "0 1px 3px 0px oklch(0 0 0 / 0.10)",
"shadow-md": "0 1px 3px 0px oklch(0 0 0 / 0.10)",
"shadow-lg": "0 1px 3px 0px oklch(0 0 0 / 0.10)",
"shadow-xl": "0 1px 3px 0px oklch(0 0 0 / 0.10)",
"shadow-2xl": "0 1px 3px 0px oklch(0 0 0 / 0.25)",
},
};The editor exposes two export targets today:
tailwind-v4jsonThe headless engine still supports the full export registry and can import existing CSS variables from :root, .dark, and Tailwind-style token definitions.
import { ConvergenceEngine, DEFAULT_THEME_DEFINITION } from "convergence-ui";
const engine = new ConvergenceEngine(DEFAULT_THEME_DEFINITION, {
autoApply: false,
});
engine.export("css");
engine.export("tailwind-v4");
engine.export("json");
engine.export("shadcn");
engine.import(`
:root {
--background: oklch(0.98 0 0);
--foreground: oklch(0.14 0.01 260);
}
.dark {
--background: oklch(0.10 0 0);
--foreground: oklch(0.96 0 0);
}
`);Convergence can scope token overrides to a specific component via data-convergence-component.
<button data-convergence-component="button">Save</button>Use the helper to keep the attribute naming consistent across your app code.
import { createComponentThemeAttributes } from "convergence-ui";
const attrs = createComponentThemeAttributes("button");Validation checks theme structure and token shapes. Accessibility scoring reports contrast outcomes for the main semantic foreground/background pairs.
import {
DEFAULT_THEME_DEFINITION,
scoreThemeAccessibility,
validateThemeDefinition,
} from "convergence-ui";
const validation = validateThemeDefinition(DEFAULT_THEME_DEFINITION);
const report = scoreThemeAccessibility(DEFAULT_THEME_DEFINITION.themes.light);