Marble UI
← Components

Client Only

Preview: Default

Renders children only on the client (for window/localStorage/canvas APIs); `fallback` during SSR and hydration.

Import

import { ClientOnly } from '@marbleui/ui'
import { useIsClient } from '@marbleui/ui'

Props

<ClientOnly>

PropTypeDefaultDescription
children *required() => ReactNode—
fallbackReactNodenull

Hooks

useIsClient(): boolean

True after hydration on the client; false during SSR and the hydration pass (no mismatch).

Usage notes

Renders browser-only content (anything that reads window, localStorage or a canvas) after hydration, and a fallback during server rendering and the hydration pass, so server and client markup match.

<ClientOnly fallback={<Skeleton className="h-5 w-48" />}>
  {() => <span>Your time zone: {Intl.DateTimeFormat().resolvedOptions().timeZone}</span>}
</ClientOnly>
  • children is a function, so the code inside it never runs on the server. Passing elements directly is a type error.
  • Give fallback the same size as the content to avoid a layout shift; it defaults to nothing.
  • useIsClient() returns the same flag (false on the server and during hydration, true after) for conditional logic inside a component.
  • Components that mount after hydration, such as on a client-side route change, render the children right away without the fallback.
Example source · show
import { Skeleton } from '../skeleton'
import { ClientOnly } from './client-only'

export const styles = { Default: () => <div className="w-72 rounded-xl border border-border bg-surface p-4 text-sm"><ClientOnly fallback={<Skeleton className="h-5 w-48" />}>{() => <span className="text-fg">Viewport: {window.innerWidth}×{window.innerHeight} · {navigator.language}</span>}</ClientOnly></div> }

Source: packages/ui/src/client-only/client-only.tsx

Catalog

Category
utilities
Priority
Extra
Primitive
custom