NeumorUINeumorUI
📖📦

Theming

Customize colors, radius, and fonts without forking. Available since v0.6.0.

Built-in: light + dark

Two themes ship out of the box. Toggle via the useNeuTheme hook, or follow the user's OS preference:

tsx
<NeuProvider defaultTheme="dark" followSystemTheme>
  <App />
</NeuProvider>
tsx
import { useNeuTheme } from "neumorui";

function ThemeToggle() {
  const { theme, toggleTheme } = useNeuTheme();
  return <button onClick={toggleTheme}>{theme === "dark" ? "🌙" : "☀️"}</button>;
}

Theme override prop (typed)

Pass a theme object to override colors, radius, or font globally. Every component that uses the affected CSS variables updates automatically.

tsx
<NeuProvider
  theme={{
    accent: "#ff6b6b",
    danger: "#e74c3c",
    success: "#27ae60",
    radius: { sm: "8px", md: "16px", lg: "24px" },
    fontFamily: '"Inter", system-ui, sans-serif',
  }}
>
  <App />
</NeuProvider>

ThemeOverride shape

All fields are optional. Import the type for editor autocomplete:

tsx
import type { ThemeOverride } from "neumorui";

const myTheme: ThemeOverride = {
  // Colors
  accent: "#6c7ef8",
  accentLight: "#8490fa",
  accentDark: "#5a6cf5",
  danger: "#f87c6c",
  success: "#5ecba1",
  warning: "#f9c74f",
  info: "#5b9ee0",
  bg: "#f0f4ff",
  textPrimary: "#2a2d4a",
  textSecondary: "#7880a8",
  textMuted: "#b0b8d8",
  border: "rgba(176,184,216,0.25)",

  // Radius scale
  radius: { sm: "10px", md: "14px", lg: "18px", xl: "24px" },

  // Font
  fontFamily: 'system-ui, sans-serif',
};

CSS variables (escape hatch)

For variables not in the typed shape (e.g. shadow definitions, glow colors), use cssVars to set them directly:

tsx
<NeuProvider
  cssVars={{
    "--neu-shadow-raised": "10px 10px 24px rgba(0,0,0,.15), -8px -8px 20px rgba(255,255,255,.9)",
    "--neu-accent-glow": "rgba(255, 107, 107, 0.5)",
  }}
>
  <App />
</NeuProvider>

All available CSS variables

Set any of these directly via cssVars, or override in your own stylesheet:

css
/* Surfaces */
--neu-bg
--neu-bg-light
--neu-bg-dark

/* Shadows */
--neu-shadow-raised
--neu-shadow-raised-sm
--neu-shadow-raised-lg
--neu-shadow-inset
--neu-shadow-inset-sm
--neu-shadow-dark    /* shadow tone */
--neu-shadow-light   /* highlight tone */

/* Colors */
--neu-accent, --neu-accent-light, --neu-accent-dark, --neu-accent-glow
--neu-success, --neu-success-light, --neu-success-dark, --neu-success-glow
--neu-danger,  --neu-danger-light,  --neu-danger-dark,  --neu-danger-glow
--neu-warning, --neu-warning-light, --neu-warning-dark, --neu-warning-glow
--neu-info,    --neu-info-light,    --neu-info-dark,    --neu-info-glow

/* Text */
--neu-text-primary
--neu-text-secondary
--neu-text-muted

/* Tints (alerts, badges) */
--neu-tint-primary, --neu-tint-primary-text, --neu-tint-primary-border
--neu-tint-success, --neu-tint-success-text, --neu-tint-success-border
--neu-tint-danger,  --neu-tint-danger-text,  --neu-tint-danger-border
--neu-tint-warning, --neu-tint-warning-text, --neu-tint-warning-border

/* Radius */
--neu-radius-sm, --neu-radius-md, --neu-radius-lg, --neu-radius-xl, --neu-radius-full

/* Misc */
--neu-border
--neu-font-family
--neu-transition

Animation control

Disable or reduce motion globally without waiting for user OS settings:

tsx
<NeuProvider defaultAnimation="reduced">  {/* short transitions, no fancy easing */}
<NeuProvider defaultAnimation="none">     {/* kill all motion entirely */}
<NeuProvider defaultAnimation="full">     {/* default — respects user OS prefers-reduced-motion */}

Change at runtime via context:

tsx
const { animation, setAnimation } = useNeuTheme();
<button onClick={() => setAnimation("none")}>Disable motion</button>

Quick accent presets

For common accent colors, use the defaultAccent shortcut:

tsx
<NeuProvider defaultAccent="violet">  {/* default */}
<NeuProvider defaultAccent="blue">
<NeuProvider defaultAccent="teal">
<NeuProvider defaultAccent="rose">
<NeuProvider defaultAccent="amber">

For full control, use theme.accent with any hex/HSL value.