Customization
Nim UI components are designed to be customized without fighting the framework. Every component accepts a className prop for style overrides, uses class-variance-authority (CVA) for variant management, and provides the cn() utility for merging Tailwind classes safely.
Override Styles with className
The simplest way to customize any component is through the className prop. Additional classes are merged with the component’s default styles.
import { Button } from '@nim-ui/components';
// Add rounded corners<Button className="rounded-full"> Rounded Button</Button>
// Add shadow<Button className="shadow-lg hover:shadow-xl"> Elevated Button</Button>
// Custom colors<Button className="bg-purple-600 hover:bg-purple-700 text-white"> Purple Button</Button>Customizing Cards
import { Card } from '@nim-ui/components';
// Card with custom border<Card className="border-2 border-primary-500"> Highlighted card</Card>
// Card with gradient background<Card className="bg-gradient-to-br from-primary-50 to-primary-100 dark:from-primary-950 dark:to-primary-900"> Gradient card</Card>
// Card with no rounding<Card className="rounded-none"> Sharp corners</Card>Customizing Inputs
import { Input } from '@nim-ui/components';
// Input with custom focus ring<Input className="focus:ring-2 focus:ring-purple-500 focus:border-purple-500" />
// Larger input<Input className="h-14 text-lg px-6" />
// Input with rounded style<Input className="rounded-full px-6" />The cn() Utility
Nim UI uses the cn() utility internally for class merging. It combines clsx for conditional classes with tailwind-merge for resolving Tailwind class conflicts.
How It Works
import { cn } from '@nim-ui/components';
// Basic mergingcn('p-4 text-red-500', 'p-8')// Result: 'p-8 text-red-500'// tailwind-merge resolves p-4 vs p-8 → keeps p-8
// Conditional classescn('base-class', isActive && 'bg-primary-500', isDisabled && 'opacity-50')
// Combining arrays and objectscn( 'flex items-center', { 'bg-red-500': hasError, 'bg-green-500': isSuccess }, className // prop from parent)Using cn() in Custom Components
When building your own components that wrap Nim UI, use cn() to merge default and custom classes safely:
import { cn } from '@nim-ui/components';import { Button, type ButtonProps } from '@nim-ui/components';
interface IconButtonProps extends ButtonProps { icon: React.ReactNode;}
function IconButton({ icon, children, className, ...props }: IconButtonProps) { return ( <Button className={cn('inline-flex items-center gap-2', className)} {...props} > {icon} {children} </Button> );}Extending CVA Variants
Nim UI uses class-variance-authority (CVA) for managing component variants. You can extend the variant system to add your own custom variants.
Understanding CVA
CVA generates className strings based on variant props:
import { cva, type VariantProps } from 'class-variance-authority';
const buttonVariants = cva( // Base styles (always applied) 'inline-flex items-center justify-center rounded-md font-medium transition-colors', { variants: { variant: { primary: 'bg-primary-500 text-white hover:bg-primary-600', secondary: 'bg-neutral-200 text-neutral-900 hover:bg-neutral-300', }, size: { sm: 'h-9 px-3 text-sm', md: 'h-10 px-4 text-base', lg: 'h-11 px-6 text-lg', }, }, defaultVariants: { variant: 'primary', size: 'md', }, });Creating Extended Components
Wrap a Nim UI component and add custom variants:
import { forwardRef } from 'react';import { cva, type VariantProps } from 'class-variance-authority';import { cn } from '@nim-ui/components';import { Button as BaseButton, type ButtonProps as BaseButtonProps } from '@nim-ui/components';
const extendedButtonVariants = cva('', { variants: { rounded: { none: 'rounded-none', sm: 'rounded-sm', md: 'rounded-md', full: 'rounded-full', }, glow: { true: 'shadow-lg shadow-primary-500/25 hover:shadow-primary-500/40', false: '', }, }, defaultVariants: { rounded: 'md', glow: false, },});
interface ExtendedButtonProps extends BaseButtonProps, VariantProps<typeof extendedButtonVariants> {}
export const Button = forwardRef<HTMLButtonElement, ExtendedButtonProps>( ({ className, rounded, glow, ...props }, ref) => { return ( <BaseButton ref={ref} className={cn(extendedButtonVariants({ rounded, glow }), className)} {...props} /> ); });
Button.displayName = 'Button';Usage:
<Button variant="primary" rounded="full" glow> Glowing Rounded Button</Button>Adding a Brand Variant
import { cva } from 'class-variance-authority';import { cn } from '@nim-ui/components';import { Button as BaseButton } from '@nim-ui/components';
const brandVariants = cva('', { variants: { brand: { github: 'bg-gray-900 text-white hover:bg-gray-800 dark:bg-white dark:text-gray-900 dark:hover:bg-gray-100', twitter: 'bg-sky-500 text-white hover:bg-sky-600', discord: 'bg-indigo-600 text-white hover:bg-indigo-700', }, },});
interface BrandButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> { brand: 'github' | 'twitter' | 'discord';}
export function BrandButton({ brand, className, ...props }: BrandButtonProps) { return ( <BaseButton className={cn(brandVariants({ brand }), className)} {...props} /> );}Composing Custom Components
Build higher-level components by composing Nim UI primitives:
Stat Card
import { Card, Badge } from '@nim-ui/components';import { cn } from '@nim-ui/components';
interface StatCardProps { label: string; value: string | number; change?: number; className?: string;}
function StatCard({ label, value, change, className }: StatCardProps) { return ( <Card className={cn('p-6', className)}> <p className="text-sm font-medium text-neutral-500 dark:text-neutral-400"> {label} </p> <p className="text-3xl font-bold text-neutral-900 dark:text-neutral-100 mt-1"> {value} </p> {change !== undefined && ( <Badge variant={change >= 0 ? 'success' : 'danger'} className="mt-2" > {change >= 0 ? '+' : ''}{change}% </Badge> )} </Card> );}Navigation Item
import { Button } from '@nim-ui/components';import { cn } from '@nim-ui/components';
interface NavItemProps { href: string; label: string; active?: boolean;}
function NavItem({ href, label, active }: NavItemProps) { return ( <a href={href}> <Button variant="ghost" className={cn( 'justify-start w-full', active && 'bg-primary-50 text-primary-700 dark:bg-primary-950 dark:text-primary-300' )} > {label} </Button> </a> );}CSS Custom Properties
For values that need to change dynamically (e.g., based on user settings), use CSS custom properties:
:root { --brand-hue: 210; --brand-color: hsl(var(--brand-hue), 80%, 55%); --card-radius: 0.5rem;}<Card style={{ borderRadius: 'var(--card-radius)' }}> Custom radius from CSS variable</Card>
<Button style={{ backgroundColor: 'var(--brand-color)' }}> Dynamic brand color</Button>Tips
- Start with
classNameoverrides. This is the simplest and most maintainable approach for one-off customizations. - Use CVA extensions for repeated patterns. If you find yourself applying the same
classNameoverrides in multiple places, create an extended variant. - Always use
cn()for class merging. Plain string concatenation can lead to conflicting Tailwind classes. Thecn()utility resolves these conflicts correctly. - Keep customizations minimal. If you find yourself heavily overriding a component, consider whether a wrapper component or a feature request would be more appropriate.
- Respect dark mode. When adding custom colors, always include the corresponding
dark:variant.
What’s Next?
- Theming - Design tokens and theme configuration
- Dark Mode - Implementing dark mode
- Best Practices - Tips for building with Nim UI