Styling & UI kits
Styling is framework-agnostic — JSX plus CSS. The enterprise example shows the full modern stack: Tailwind v4, shadcn-style design tokens,cn(), CVA variants, and a primitives layer in src/ui/.
Tailwind v4
npm install tailwindcss @tailwindcss/viteimport tailwindcss from '@tailwindcss/vite';
export default defineConfig({ plugins: [preact(), tailwindcss()] });@import 'tailwindcss';
/* Class-strategy dark mode — ThemeService toggles .dark on <html>. */
@custom-variant dark (&:where(.dark, .dark *));
/* shadcn-style design tokens as HSL channels. */
:root {
--background: 0 0% 100%;
--card: 0 0% 100%;
--muted-foreground: 240 3.8% 46.1%;
/* … light tokens … */
}
.dark {
--background: 240 10% 3.9%;
--card: 240 10% 5.9%;
--muted-foreground: 240 5% 64.9%;
/* … dark tokens … */
}
/* tokens → utilities: bg-card, text-muted-foreground, border-border, … */
@theme inline {
--color-background: hsl(var(--background));
--color-card: hsl(var(--card));
--color-muted-foreground: hsl(var(--muted-foreground));
/* … */
}cn() + CVA primitives
import { clsx, type ClassValue } from 'clsx';
import { twMerge } from 'tailwind-merge';
// clsx for conditional joining, tailwind-merge so later classes win
// conflicts (px-2 vs px-4) — the shadcn composition primitive.
export const cn = (...inputs: ClassValue[]) => twMerge(clsx(inputs));import { cva, type VariantProps } from 'class-variance-authority';
const buttonVariants = cva(
'inline-flex items-center justify-center rounded-md text-sm font-medium',
{
variants: {
variant: {
default: 'bg-primary text-primary-foreground hover:bg-primary/90',
destructive: 'bg-destructive text-destructive-foreground',
outline: 'border border-input bg-transparent hover:bg-accent',
ghost: 'hover:bg-accent',
},
size: { default: 'h-9 px-4', sm: 'h-8 px-3', lg: 'h-10 px-6' },
},
defaultVariants: { variant: 'default', size: 'default' },
},
);
export function Button({ class: c, variant, size, ...props }:
JSX.IntrinsicElements['button'] & VariantProps<typeof buttonVariants>) {
return <button class={cn(buttonVariants({ variant, size }), c)} {...props} />;
}The JSX.IntrinsicElements['button'] trick types the component as "a button plus extras" — every real DOM prop passes through, which is what makes a primitives layer feel native. Add children?: ComponentChildren to the intersection (it's not part of the intrinsic type).
Theming as a service
Dark mode is domain state, so it lives in an injectable — ThemeService holds the theme signal, persists to localStorage, follows prefers-color-scheme under 'system', and a scope-bound effect() toggles the .dark class. Views read theme.isDark.value for icon state; CSS does the rest through the token swap.
What the example's ui/ covers
| Layer | Pieces |
|---|---|
| Primitives | Button, Input, Label, Badge, Separator, Skeleton, Avatar |
| Layout | Card family, Table family, Tabs (context-driven) |
| Overlay | Dialog (signal-controlled) |
| Data viz | Chart — an ECharts wrapper component |
All of it is plain function components over cn() — copy the folder into your app like shadcn intends. Browse it live in the enterprise demo → Components tab.