# Utilities

> Helper components for conditional rendering, translations, and theming.

*Canonical: https://makerkit.dev/docs/nextjs-prisma/ui-components/utilities*

---

Use `If` for type-safe conditional rendering, `Trans` for i18n translations, `LazyRender` for IntersectionObserver-based lazy loading, `ErrorBoundary` for error catching, `Stepper` for multi-step flows, `ModeToggle` for theme switching, `CookieBanner` for GDPR consent, and `LanguageSelector` for locale switching.

This guide is part of the [UI Components](./overview) documentation.

Utility components handle cross-cutting concerns - conditional rendering, internationalization, lazy loading, error boundaries, and user preferences - providing reusable patterns for common application needs.

- **Use If when:** you need type-narrowed conditional rendering (cleaner than ternaries). 
- **Use Trans when:** displaying any user-facing text (enables i18n). 
- **Use LazyRender when:** content is below the fold and heavy to render. 
- **Use ErrorBoundary when:** wrapping components that might throw. 

## If

Conditional rendering component with type inference.

```typescript
import { If } from '@kit/ui/if';
```

```tsx
{/* Basic usage */}
<If condition={isLoading} fallback={<Content />}>
  <Spinner />
</If>

{/* With type inference */}
<If condition={error}>
  {(err) => <ErrorMessage error={err} />}
</If>

{/* Render prop pattern */}
<If condition={user}>
  {(user) => <UserProfile user={user} />}
</If>
```

## Trans

Translation component for internationalization.

```typescript
import { Trans } from '@kit/ui/trans';
```

```tsx
{/* Simple translation */}
<Trans i18nKey="common.welcome" />

{/* With variables */}
<Trans
  i18nKey="user.greeting"
  values={{ name: user.name }}
/>

{/* With default text */}
<Trans
  i18nKey="common.submit"
  defaults="Submit"
/>

{/* Rich text with components */}
<Trans
  i18nKey="terms.agreement"
  components={{
    TermsLink: <a href="/terms" className="underline" />,
    PrivacyLink: <a href="/privacy" className="underline" />,
  }}
/>
```

## Lazy Render

Lazy load content when visible using IntersectionObserver.

```typescript
import { LazyRender } from '@kit/ui/lazy-render';
```

```tsx
<LazyRender>
  <HeavyComponent />
</LazyRender>

{/* With options */}
<LazyRender
  threshold={0.5}
  rootMargin="100px"
  onVisible={() => console.log('Component visible')}
>
  <ExpensiveChart />
</LazyRender>
```

## Error Boundary

Catch and handle React errors.

```typescript
import { ErrorBoundary } from '@kit/ui/error-boundary';
```

```tsx
<ErrorBoundary
  fallback={
    <Alert variant="destructive">
      <AlertTitle>Something went wrong</AlertTitle>
      <AlertDescription>
        Please refresh the page.
      </AlertDescription>
    </Alert>
  }
>
  <ComponentThatMightError />
</ErrorBoundary>
```

## Stepper

Multi-step progress indicator.

```typescript
import { Stepper } from '@kit/ui/stepper';
```

```tsx
<Stepper
  steps={['Account', 'Profile', 'Review']}
  currentStep={1}
/>

{/* Numbers variant */}
<Stepper
  steps={['Step 1', 'Step 2', 'Step 3']}
  currentStep={2}
  variant="numbers"
/>

{/* Dots variant */}
<Stepper
  steps={['', '', '', '']}
  currentStep={0}
  variant="dots"
/>
```

## Mode Toggle

Dark/light theme switcher.

```typescript
import { ModeToggle } from '@kit/ui/mode-toggle';
```

```tsx
<ModeToggle />
```

### Sub Menu Variant

```typescript
import { SubMenuModeToggle } from '@kit/ui/mode-toggle';
```

```tsx
<SubMenuModeToggle />
```

### Theme Preference Card

```typescript
import { ThemePreferenceCard } from '@kit/ui/mode-toggle';
```

```tsx
<ThemePreferenceCard currentTheme="system" />
```

Displays cards for Light, Dark, and System theme options. The `currentTheme` prop sets the initial theme value.

## Cookie Banner

GDPR cookie consent banner.

```typescript
import { CookieBanner, useCookieConsent } from '@kit/ui/cookie-banner';
```

```tsx
<CookieBanner />
```

### Using Consent Hook

```tsx
const { status, accept, reject, clear } = useCookieConsent();

// status: 'unknown' | 'accepted' | 'rejected'
```

## Language Selector

Language/locale switcher.

```typescript
import { LanguageSelector, LanguagePreferenceCard } from '@kit/ui/language-selector';
```

```tsx
<LanguageSelector locales={['en', 'de', 'es']} />
```

### Language Preference Card

Card UI for language selection in settings pages.

```tsx
<LanguagePreferenceCard locales={['en', 'de', 'es']} />
```

## cn Utility

Class name merging utility.

```typescript
import { cn } from '@kit/ui/utils';
```

```tsx
<div className={cn(
  'base-classes',
  isActive && 'active-classes',
  className
)}>
  Content
</div>
```

In MakerKit, we use the `If` component throughout the codebase instead of ternaries. The type narrowing in the render prop pattern catches null-safety issues that ternaries miss.

## Common Pitfalls

- **If component with falsy values**: `If` condition treats 0, '', and false as falsy. For explicit boolean checks, use `condition={value !== null}`.
- **Trans missing translation key**: When i18nKey doesn't exist, Trans renders the key itself. Always add keys to your translation files first.
- **LazyRender on critical content**: Don't use LazyRender for above-the-fold content. It delays rendering until intersection, causing layout shift.
- **ErrorBoundary not catching async errors**: ErrorBoundary only catches errors during rendering. For async errors, use try/catch in your async functions.
- **ModeToggle without ThemeProvider**: ModeToggle requires next-themes ThemeProvider in your root layout to work properly.
- **CookieBanner blocking renders**: CookieBanner checks localStorage on mount. If blocking, use `useCookieConsent` hook to defer checks.

{% faq
   title="Frequently Asked Questions"
   items=[
     {"question": "How does the If component provide type inference?", "answer": "When using render prop pattern, the truthy value is passed to the child function with its type narrowed, so TypeScript knows it's not null/undefined."},
     {"question": "Can I use Trans in Server Components?", "answer": "Yes, but ensure your i18n setup supports RSC. The Trans component itself is render-only and works server-side."},
     {"question": "How do I persist theme preference?", "answer": "ModeToggle with next-themes automatically persists to localStorage. The preference survives page refreshes and browser restarts."},
     {"question": "What happens if LazyRender never intersects?", "answer": "The children never render. For content that must eventually load, consider adding a timeout fallback or using a different pattern."},
     {"question": "How do I customize the Stepper appearance?", "answer": "Stepper supports variant prop (default, numbers, dots). For further customization, pass className or override via CSS variables."}
   ]
/%}

---

**Next:** [Marketing →](./marketing)
