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/vite
vite.config.ts
import tailwindcss from '@tailwindcss/vite';
export default defineConfig({ plugins: [preact(), tailwindcss()] });
src/styles.css
@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

lib/cn.ts
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));
ui/button.tsx
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

LayerPieces
PrimitivesButton, Input, Label, Badge, Separator, Skeleton, Avatar
LayoutCard family, Table family, Tabs (context-driven)
OverlayDialog (signal-controlled)
Data vizChart — 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.