NimUI
Getting Started

Configuration

Configure and customize Nim UI for your project

Tailwind Configuration

Nim UI is built on Tailwind CSS v4, which is configured in CSS. There is no tailwind.config.js — v4 does not read one unless you point at it explicitly with @config, and a config file left lying around is simply ignored. Everything below goes in your entry stylesheet.

Basic Setup

src/index.css
@import 'tailwindcss';
@import '@nim-ui/components/styles';

That is the whole setup. Tailwind scans your source for class names on its own; add @source only for a directory it would not find, such as a sibling package:

src/index.css
@source '../../shared-ui/src';

Theme Customization

Every customization below is a @theme block. The variable name is the utility: define --color-brand-500 and bg-brand-500 exists.

Colors

Customize the color palette to match your brand:

src/index.css
@theme {
  --color-primary-500: oklch(0.62 0.19 250);
  --color-primary-600: oklch(0.54 0.19 250);

  /* Or add a scale of your own */
  --color-brand-50: oklch(0.97 0.02 300);
  --color-brand-500: oklch(0.58 0.21 300);
  --color-brand-900: oklch(0.28 0.11 300);
}

Redefining --color-primary-* re-tints the kit itself, since its components reference those variables rather than fixed colours.

Typography

Customize fonts and type scale:

src/index.css
@theme {
  --font-sans: 'Inter', ui-sans-serif, system-ui, sans-serif;
  --font-mono: 'Fira Code', ui-monospace, monospace;

  --text-xs: 0.75rem;
  --text-xs--line-height: 1rem;
  --text-base: 1rem;
  --text-base--line-height: 1.5rem;
}

Line height rides along on the size as a --line-height suffix, rather than sitting in a second argument the way it did in v3.

Spacing

Tailwind derives its whole spacing scale from one base, so p-18 already works without being declared. Define a step only when you want a value off that rhythm:

src/index.css
@theme {
  --spacing-18: 4.5rem;
  --spacing-128: 32rem;
}

Border Radius

src/index.css
@theme {
  --radius-4xl: 2rem;
}

Dark Mode Configuration

Nothing to configure. The kit's stylesheet already binds the dark: variant to a .dark class — and to [data-theme="dark"], so a docs framework's own toggle works too. Importing @nim-ui/components/styles is all it takes; a darkMode setting in a config file does nothing here.

The binding is not overridable from your own stylesheet: redefining @custom-variant dark has no effect whether you write it before or after the import — measured, the kit's definition wins either way and no prefers-color-scheme rule is emitted. To follow the operating system, keep the class and set it from JavaScript with matchMedia, as the toggle below does.

Dark Mode Toggle

Implement a dark mode toggle:

src/components/DarkModeToggle.tsx
import { useEffect, useState } from 'react';
import { Button } from '@nim-ui/components';

export default function DarkModeToggle() {
  const [darkMode, setDarkMode] = useState(() => {
    // Check local storage or system preference
    if (typeof window !== 'undefined') {
      const stored = localStorage.getItem('darkMode');
      if (stored !== null) return stored === 'true';
      return window.matchMedia('(prefers-color-scheme: dark)').matches;
    }
    return false;
  });

  useEffect(() => {
    // Apply dark mode class to html element
    if (darkMode) {
      document.documentElement.classList.add('dark');
      localStorage.setItem('darkMode', 'true');
    } else {
      document.documentElement.classList.remove('dark');
      localStorage.setItem('darkMode', 'false');
    }
  }, [darkMode]);

  return (
    <Button
      variant="ghost"
      onClick={() => setDarkMode(!darkMode)}
      aria-label="Toggle dark mode"
    >
      {darkMode ? '🌙' : '☀️'}
    </Button>
  );
}

CSS Customization

Custom Component Styles

Nim UI components render no stable class hook to @apply onto — there is no .nim-button (or equivalent) selector on any component, so a rule targeting one matches nothing.

Override a component's output through the className prop instead; every component merges it with its own classes through the kit's cn(), which resolves conflicting Tailwind classes correctly:

<Button className="rounded-full">Rounded button</Button>

For a style you want to reuse across many instances, extend the component's CVA variants rather than reaching for a custom selector and @apply. See Customization for the full pattern — className overrides, extending CVA variants, and composing wrapper components.

CSS Variables

Nim UI's palette is not a set of hex custom properties redefined per scheme under :root and .dark. It is a single @theme block of OKLCH values, one per shade, declared in packages/ui/src/tokens.css and used identically in both themes — see Theme Customization above for how to override a shade in your own stylesheet, and Theming for the full mechanism. Dark-mode contrast comes from a component pairing a different shade number under the dark: variant (text-primary-600 dark:text-primary-400), not from a shade's own value changing under a class selector; the dark: binding itself is the @custom-variant covered in Dark Mode Configuration above.

For your own values that need to change at runtime rather than by theme — a user-configurable accent, say — plain CSS custom properties still apply; see Customization → CSS Custom Properties for that pattern.

Build Configuration

Vite

Optimize build for production:

vite.config.ts
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';

export default defineConfig({
  plugins: [react()],
  build: {
    // Optimize chunk splitting
    rollupOptions: {
      output: {
        manualChunks: {
          'nim-ui': ['@nim-ui/components'],
        },
      },
    },
  },
  optimizeDeps: {
    include: ['@nim-ui/components'],
  },
});

Next.js

Configure Next.js for optimal performance:

next.config.js
/** @type {import('next').NextConfig} */
const nextConfig = {
  transpilePackages: ['@nim-ui/components'],
  compiler: {
    removeConsole: process.env.NODE_ENV === 'production',
  },
  experimental: {
    optimizePackageImports: ['@nim-ui/components'],
  },
};

export default nextConfig;

Webpack

For custom Webpack setups:

webpack.config.js
module.exports = {
  module: {
    rules: [
      {
        test: /\.css$/,
        use: ['style-loader', 'css-loader', 'postcss-loader'],
      },
    ],
  },
  resolve: {
    extensions: ['.ts', '.tsx', '.js', '.jsx'],
  },
};

Environment Variables

Configure environment-specific settings:

.env
# API endpoints
VITE_API_URL=https://api.example.com

# Feature flags
VITE_ENABLE_DARK_MODE=true
VITE_ENABLE_ANALYTICS=false

Access in your code:

const apiUrl = import.meta.env.VITE_API_URL;
const darkModeEnabled = import.meta.env.VITE_ENABLE_DARK_MODE === 'true';

TypeScript Configuration

Strict Type Checking

Enable strict mode for better type safety:

tsconfig.json
{
  "compilerOptions": {
    "strict": true,
    "noUncheckedIndexedAccess": true,
    "noImplicitReturns": true,
    "noFallthroughCasesInSwitch": true,
    "forceConsistentCasingInFileNames": true
  }
}

Path Aliases

Set up path aliases for cleaner imports:

tsconfig.json
{
  "compilerOptions": {
    "baseUrl": ".",
    "paths": {
      "@/*": ["./src/*"],
      "@components/*": ["./src/components/*"],
      "@ui/*": ["./node_modules/@nim-ui/components/*"]
    }
  }
}

Then use:

import { Button } from '@ui';
import Header from '@components/Header';

Performance Optimization

Tree Shaking

Ensure tree shaking works correctly:

// ✅ Good - imports only Button
import { Button } from '@nim-ui/components';

// ❌ Bad - imports everything
import * as NimUI from '@nim-ui/components';

Code Splitting

Split components into separate chunks:

import { lazy, Suspense } from 'react';

// Lazy load heavy components
const HeavyComponent = lazy(() => import('./HeavyComponent'));

function App() {
  return (
    <Suspense fallback={<div>Loading...</div>}>
      <HeavyComponent />
    </Suspense>
  );
}

Bundle Analysis

Analyze your bundle size:

pnpm add -D rollup-plugin-visualizer
vite.config.ts
import { visualizer } from 'rollup-plugin-visualizer';

export default defineConfig({
  plugins: [
    react(),
    visualizer({ open: true }),
  ],
});
pnpm add -D @next/bundle-analyzer
next.config.js
const withBundleAnalyzer = require('@next/bundle-analyzer')({
  enabled: process.env.ANALYZE === 'true',
});

module.exports = withBundleAnalyzer({
  // Your Next.js config
});

Run with: ANALYZE=true pnpm build

What's Next?

On this page