Utilities

Helper components for conditional rendering, translations, and theming.

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 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.

import { If } from '@kit/ui/if';
{/* 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.

import { Trans } from '@kit/ui/trans';
{/* 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.

import { LazyRender } from '@kit/ui/lazy-render';
<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.

import { ErrorBoundary } from '@kit/ui/error-boundary';
<ErrorBoundary
fallback={
<Alert variant="destructive">
<AlertTitle>Something went wrong</AlertTitle>
<AlertDescription>
Please refresh the page.
</AlertDescription>
</Alert>
}
>
<ComponentThatMightError />
</ErrorBoundary>

Stepper

Multi-step progress indicator.

import { Stepper } from '@kit/ui/stepper';
<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.

import { ModeToggle } from '@kit/ui/mode-toggle';
<ModeToggle />
import { SubMenuModeToggle } from '@kit/ui/mode-toggle';
<SubMenuModeToggle />

Theme Preference Card

import { ThemePreferenceCard } from '@kit/ui/mode-toggle';
<ThemePreferenceCard currentTheme="system" />

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

GDPR cookie consent banner.

import { CookieBanner, useCookieConsent } from '@kit/ui/cookie-banner';
<CookieBanner />
const { status, accept, reject, clear } = useCookieConsent();
// status: 'unknown' | 'accepted' | 'rejected'

Language Selector

Language/locale switcher.

import { LanguageSelector, LanguagePreferenceCard } from '@kit/ui/language-selector';
import { useRouter, usePathname } from '@kit/i18n/navigation';
const router = useRouter();
const pathname = usePathname();
<LanguageSelector
locales={['en', 'de', 'es']}
router={router}
pathname={pathname}
/>

Language Preference Card

Card UI for language selection in settings pages.

<LanguagePreferenceCard
locales={['en', 'de', 'es']}
router={router}
pathname={pathname}
/>

cn Utility

Class name merging utility.

import { cn } from '@kit/ui/utils';
<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.

Frequently Asked Questions

How does the If component provide type inference?
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.
Can I use Trans in Server Components?
Yes, but ensure your i18n setup supports RSC. The Trans component itself is render-only and works server-side.
How do I persist theme preference?
ModeToggle with next-themes automatically persists to localStorage. The preference survives page refreshes and browser restarts.
What happens if LazyRender never intersects?
The children never render. For content that must eventually load, consider adding a timeout fallback or using a different pattern.
How do I customize the Stepper appearance?
Stepper supports variant prop (default, numbers, dots). For further customization, pass className or override via CSS variables.

Next: Marketing →