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>
| Prop | Type | Default | Description |
|---|---|---|---|
| children *required | () => ReactNode | — | |
| fallback | ReactNode | null |
Hooks
useIsClient(): booleanTrue 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>
childrenis a function, so the code inside it never runs on the server. Passing elements directly is a type error.- Give
fallbackthe 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· hide
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